feat(BRIDGE-424): FIDO2 GUI support.

This commit is contained in:
Atanas Janeshliev
2025-09-16 13:07:45 +02:00
parent e091e58be1
commit edf903fd21
42 changed files with 3567 additions and 3510 deletions

View File

@ -42,7 +42,10 @@ Proton Mail Bridge includes the following 3rd party software:
* [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)
* [cbor](https://github.com/fxamacker/cbor/v2) available under [license](https://github.com/fxamacker/cbor/v2/blob/master/LICENSE)
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
* [ctaphid](https://github.com/go-ctap/ctaphid) available under [license](https://github.com/go-ctap/ctaphid/blob/master/LICENSE)
* [winhello](https://github.com/go-ctap/winhello) available under [license](https://github.com/go-ctap/winhello/blob/master/LICENSE)
* [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
* [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE)
* [mock](https://github.com/golang/mock) available under [license](https://github.com/golang/mock/blob/master/LICENSE)
@ -52,6 +55,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE)
* [go-locale](https://github.com/jeandeaual/go-locale) available under [license](https://github.com/jeandeaual/go-locale/blob/master/LICENSE)
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
* [go-libfido2](https://github.com/keys-pub/go-libfido2) available under [license](https://github.com/keys-pub/go-libfido2/blob/master/LICENSE)
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
* [memory](https://github.com/pbnjay/memory) available under [license](https://github.com/pbnjay/memory/blob/master/LICENSE)
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
@ -70,7 +74,6 @@ Proton Mail Bridge includes the following 3rd party software:
* [grpc](https://google.golang.org/grpc) available under [license](https://github.com/grpc/grpc-go/blob/master/LICENSE)
* [protobuf](https://google.golang.org/protobuf) available under [license](https://github.com/protocolbuffers/protobuf/blob/main/LICENSE)
* [plist](https://howett.net/plist) available under [license](https://github.com/DHowett/go-plist/blob/main/LICENSE)
* [compute](https://cloud.google.com/go/compute) available under [license](https://pkg.go.dev/cloud.google.com/go/compute?tab=licenses)
* [metadata](https://cloud.google.com/go/compute/metadata) available under [license](https://pkg.go.dev/cloud.google.com/go/compute/metadata?tab=licenses)
* [bcrypt](https://github.com/ProtonMail/bcrypt) available under [license](https://github.com/ProtonMail/bcrypt/blob/master/LICENSE)
* [go-crypto](https://github.com/ProtonMail/go-crypto) available under [license](https://github.com/ProtonMail/go-crypto/blob/master/LICENSE)
@ -111,6 +114,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [multierror](https://github.com/joeshaw/multierror) available under [license](https://github.com/joeshaw/multierror/blob/master/LICENSE)
* [go](https://github.com/json-iterator/go) available under [license](https://github.com/json-iterator/go/blob/master/LICENSE)
* [cpuid](https://github.com/klauspost/cpuid/v2) available under [license](https://github.com/klauspost/cpuid/v2/blob/master/LICENSE)
* [cose](https://github.com/ldclabs/cose) available under [license](https://github.com/ldclabs/cose/blob/master/LICENSE)
* [go-urn](https://github.com/leodido/go-urn) available under [license](https://github.com/leodido/go-urn/blob/master/LICENSE)
* [go-colorable](https://github.com/mattn/go-colorable) available under [license](https://github.com/mattn/go-colorable/blob/master/LICENSE)
* [go-isatty](https://github.com/mattn/go-isatty) available under [license](https://github.com/mattn/go-isatty/blob/master/LICENSE)
@ -131,6 +135,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [golang-asm](https://github.com/twitchyliquid64/golang-asm) available under [license](https://github.com/twitchyliquid64/golang-asm/blob/master/LICENSE)
* [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)
* [float16](https://github.com/x448/float16) available under [license](https://github.com/x448/float16/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) available under [license](https://gitlab.com/c0b/go-ordered-json/blob/master/LICENSE)
* [go.opencensus.io](https://pkg.go.dev/go.opencensus.io?tab=licenses) available under [license](https://pkg.go.dev/go.opencensus.io?tab=licenses)
@ -142,9 +147,11 @@ Proton Mail Bridge includes the following 3rd party software:
* [appengine](https://google.golang.org/appengine) available under [license](https://pkg.go.dev/google.golang.org/appengine?tab=licenses)
* [genproto](https://google.golang.org/genproto) available under [license](https://pkg.go.dev/google.golang.org/genproto?tab=licenses)
* [yaml](https://gopkg.in/yaml.v3) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE) available under [license](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)
* [go-autostart](https://github.com/ElectroNafta/go-autostart) available under [license](https://github.com/ElectroNafta/go-autostart/blob/master/LICENSE)
* [go-autostart](https://github.com/ProtonMail/go-autostart) available under [license](https://github.com/ProtonMail/go-autostart/blob/master/LICENSE)
* [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE)
* [go-smtp](https://github.com/ProtonMail/go-smtp) available under [license](https://github.com/ProtonMail/go-smtp/blob/master/LICENSE)
* [winhello](https://github.com/ProtonMail/winhello) available under [license](https://github.com/ProtonMail/winhello/blob/master/LICENSE)
* [resty](https://github.com/LBeernaertProton/resty/v2) available under [license](https://github.com/LBeernaertProton/resty/v2/blob/master/LICENSE)
* [go-keychain](https://github.com/cuthix/go-keychain) available under [license](https://github.com/cuthix/go-keychain/blob/master/LICENSE)
* [go-libfido2](https://github.com/ProtonMail/go-libfido2) available under [license](https://github.com/ProtonMail/go-libfido2/blob/master/LICENSE)
<!-- END AUTOGEN -->

View File

@ -1,5 +1,5 @@
---
include:
- project: "go/bridge-internal"
ref: "chore/libfido2-build"
ref: "master"
file: "ci/runners-setup.yml"

38
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.17.1-0.20250627102828-b014b7cc8132
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.4.1-0.20250910105600-4de5d9039a5f
github.com/ProtonMail/go-proton-api v0.4.1-0.20250925134057-a44ee01d3b3b
github.com/ProtonMail/gopenpgp/v2 v2.9.0-proton
github.com/PuerkitoBio/goquery v1.8.1
github.com/abiosoft/ishell v2.0.0+incompatible
@ -26,12 +26,12 @@ require (
github.com/fatih/color v1.13.0
github.com/fxamacker/cbor/v2 v2.9.0
github.com/getsentry/sentry-go v0.15.0
github.com/go-ctap/ctaphid v0.7.0
github.com/go-ctap/ctaphid v0.8.1
github.com/go-ctap/winhello v0.1.0
github.com/go-resty/resty/v2 v2.7.0
github.com/godbus/dbus v4.1.0+incompatible
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.6.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
@ -43,24 +43,23 @@ require (
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.7.0
github.com/sirupsen/logrus v1.9.2
github.com/stretchr/testify v1.10.0
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v2 v2.24.4
github.com/vmihailenco/msgpack/v5 v5.3.5
go.uber.org/goleak v1.2.1
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sys v0.33.0
golang.org/x/text v0.26.0
golang.org/x/net v0.42.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sys v0.35.0
golang.org/x/text v0.28.0
google.golang.org/api v0.114.0
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.33.0
google.golang.org/grpc v1.75.1
google.golang.org/protobuf v1.36.6
howett.net/plist v1.0.0
)
require (
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
github.com/ProtonMail/go-crypto v1.3.0-proton // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
@ -89,7 +88,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/uuid v4.3.0+incompatible // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
@ -126,20 +125,21 @@ require (
gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/tools v0.35.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace (
github.com/ProtonMail/go-autostart => github.com/ElectroNafta/go-autostart v0.0.0-20250402094843-326608c16033
github.com/ProtonMail/go-autostart => github.com/ProtonMail/go-autostart v0.0.0-20250402094843-326608c16033
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423
github.com/emersion/go-smtp => github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865
github.com/go-ctap/winhello => github.com/ProtonMail/winhello v0.0.0-20250918145518-a739b7dc2e56
github.com/go-resty/resty/v2 => github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20240103134243-0b6a41580b77
github.com/keys-pub/go-libfido2 => github.com/ElectroNafta/go-libfido2 v0.0.0-20250915152115-4584ec5a59ac
github.com/keys-pub/go-libfido2 => github.com/ProtonMail/go-libfido2 v0.0.0-20250916110427-df894d6d07a1
)

104
go.sum
View File

@ -7,10 +7,8 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
@ -23,10 +21,6 @@ github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ElectroNafta/go-autostart v0.0.0-20250402094843-326608c16033 h1:d2RB9rQmSusb0K+qSgB+DAY+8i+AXZ/o+oDHj2vAUaA=
github.com/ElectroNafta/go-autostart v0.0.0-20250402094843-326608c16033/go.mod h1:o0nKiWcK0e2G/90uL6akWRkzOV4mFcZmvpBPpigJvdw=
github.com/ElectroNafta/go-libfido2 v0.0.0-20250915152115-4584ec5a59ac h1:FYEkCIEW4MGkv7oehbGsfxJYsufq7wnBI8XAHrwVDZg=
github.com/ElectroNafta/go-libfido2 v0.0.0-20250915152115-4584ec5a59ac/go.mod h1:92J9LtSBl0UyUWljElJpTbMMNhC6VeY8dshsu40qjjo=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a h1:eQO/GF/+H8/9udc9QAgieFr+jr1tjXlJo35RAhsUbWY=
github.com/LBeernaertProton/resty/v2 v2.0.0-20231129100320-dddf8030d93a/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
@ -40,21 +34,27 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
github.com/ProtonMail/gluon v0.17.1-0.20250627102828-b014b7cc8132 h1:2HA7IRoYUA1Lm7yFl1JGskGotNnqIqKJJB/ZxYhoJ6w=
github.com/ProtonMail/gluon v0.17.1-0.20250627102828-b014b7cc8132/go.mod h1:OMwmLjgk6yJHX/P5KPck9WOcBVWIJLvuGZjj/8Ts/cw=
github.com/ProtonMail/go-autostart v0.0.0-20250402094843-326608c16033 h1:4r/ALoiixOOyjc1WhpwlkrcSFtRnc1GHWhk7ERELwbs=
github.com/ProtonMail/go-autostart v0.0.0-20250402094843-326608c16033/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 v1.3.0-proton h1:tAQKQRZX/73VmzK6yHSCaRUOvS/3OYSQzhXQsrR7yUM=
github.com/ProtonMail/go-crypto v1.3.0-proton/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-libfido2 v0.0.0-20250916110427-df894d6d07a1 h1:MpPKmpti7MswJG5il3A+P24+iGxMj8V7/3JSMSRM1+c=
github.com/ProtonMail/go-libfido2 v0.0.0-20250916110427-df894d6d07a1/go.mod h1:92J9LtSBl0UyUWljElJpTbMMNhC6VeY8dshsu40qjjo=
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423 h1:p8nBDxvRnvDOyrcePKkPpErWGhDoTqpX8a1c54CcSu0=
github.com/ProtonMail/go-message v0.13.1-0.20240919135104-3bc88e6a9423/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.20250910105600-4de5d9039a5f h1:BEX62gQfydYK8vUGaxQ6aWqqtVq0g6eHuv6JBwpDaJ8=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250910105600-4de5d9039a5f/go.mod h1:9t9+oQfH+6ssa7O2nLv34Uyjv8UmqTPGbVNcFToewck=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250925134057-a44ee01d3b3b h1:TOYM14eu1k/zgXOOQH86MP68+mc9ASpX0gasYI51TNs=
github.com/ProtonMail/go-proton-api v0.4.1-0.20250925134057-a44ee01d3b3b/go.mod h1:9t9+oQfH+6ssa7O2nLv34Uyjv8UmqTPGbVNcFToewck=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
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.9.0-proton h1:K3YRIBJo3YVObikaV9y1KWYGxFWRML+pFaiyh8ON2xA=
github.com/ProtonMail/gopenpgp/v2 v2.9.0-proton/go.mod h1:NJ4RywdeD2sXCJyRRwb0ZYCx+QwGi14HUmlyNPegiwI=
github.com/ProtonMail/winhello v0.0.0-20250918145518-a739b7dc2e56 h1:OFDKuwogje2Nor+2X81P0wWGcSZPXu2HKNUE71o3qZI=
github.com/ProtonMail/winhello v0.0.0-20250918145518-a739b7dc2e56/go.mod h1:kJnpbFRhpEatnRc05/CTeq4cWR2LUE7P6+KsPP/zRnE=
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=
@ -176,10 +176,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-ctap/ctaphid v0.7.0 h1:NLDPio3g9JaWQdE7nSO2HGFk2kUa/jm5EKt2pXwxP9E=
github.com/go-ctap/ctaphid v0.7.0/go.mod h1:0Lw4YghP45DtCQD8JHEvZ8bJvpcIm9R3fIKjDgcYQyw=
github.com/go-ctap/winhello v0.1.0 h1:ZWHwCbqMAHJH2opfGuW1tkAzDjs2wo++W2238Po5dH0=
github.com/go-ctap/winhello v0.1.0/go.mod h1:hcUpGmVxxS+zOn9foMfP98l0S0lpmHpMQpCqkWNF9kE=
github.com/go-ctap/ctaphid v0.8.1 h1:HIDoSfqInkUIRBVPv61fVB2CNZ5nYxoaIgqmt8vzcs4=
github.com/go-ctap/ctaphid v0.8.1/go.mod h1:jRVrVfCs30jdZkSH2PoBopv9ry+tK99mpYumE4GIbb8=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
@ -188,6 +186,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -202,8 +204,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/goforj/godump v1.5.0 h1:QALI7uJkpiwAW3Anko+vOFQzLlWvB7YviYCJpE1sFIE=
github.com/goforj/godump v1.5.0/go.mod h1:lCaXaxNTozTNAMJTPY91/ntMqw3JF8FOL93jCNKpNW0=
github.com/goforj/godump v1.6.0 h1:3Dn8gaw5Xxxefr1ezTGTWrTKSr3ihK+eJ2xzRUoFfHQ=
github.com/goforj/godump v1.6.0/go.mod h1:/Vy+p50JtOkwsFN5dA1HQ7LS5gtPk3f61DaP4UR2o4s=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -231,9 +233,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -242,9 +243,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -466,8 +466,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@ -495,6 +495,18 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
@ -513,8 +525,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -540,8 +552,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -570,13 +582,13 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -586,8 +598,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -626,8 +638,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -647,8 +659,8 @@ 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/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
@ -678,12 +690,14 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -716,8 +730,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -727,10 +741,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

267
internal/fido/fido.go Normal file
View File

@ -0,0 +1,267 @@
// Copyright (c) 2025 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/>.
//go:build darwin || linux
package fido
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"time"
"github.com/ProtonMail/go-proton-api"
"github.com/fxamacker/cbor/v2"
"github.com/keys-pub/go-libfido2"
)
var ErrAssertionCancelled = errors.New("FIDO assertion cancelled")
const (
clientPinOption = "clientPin"
touchNotificationDelay = 500 * time.Millisecond
)
func getFidoDevice() (fido2Device *libfido2.Device, err error) {
locs, err := libfido2.DeviceLocations()
if err != nil {
return nil, fmt.Errorf("could not find security key device location: %w", err)
}
if len(locs) == 0 {
return nil, errors.New("no device found")
}
if len(locs) > 1 {
return nil, errors.New("multiple security keys detected, please disconnect all but one device and try again")
}
fido2Device, err = libfido2.NewDevice(locs[0].Path)
if err != nil {
return nil, fmt.Errorf("cannot open security key: %w", err)
}
return fido2Device, nil
}
func deviceHasOption(dev *libfido2.Device, name string) (bool, error) {
info, err := dev.Info()
if err != nil {
return false, fmt.Errorf("cannot get device info: %w", err)
}
for _, opt := range info.Options {
if opt.Name == name && opt.Value == libfido2.True {
return true, err
}
}
return false, err
}
func IsPinSupported() (bool, error) {
dev, err := getFidoDevice()
if err != nil {
return false, err
}
return deviceHasOption(dev, clientPinOption)
}
func constructCredentialIDs(allowCredentials []interface{}) ([][]byte, error) {
var credentialIDs [][]byte //nolint:prealloc
for _, cred := range allowCredentials {
credMap, ok := cred.(map[string]interface{})
if !ok {
continue
}
idArray, ok := credMap["id"].([]interface{})
if !ok {
continue
}
credID := sliceAnyToByteArray(idArray)
credentialIDs = append(credentialIDs, credID)
}
if len(credentialIDs) == 0 {
return nil, errors.New("no valid credential IDs found")
}
return credentialIDs, nil
}
func prepareFidoAuth(auth proton.Auth) (*libfido2.Device, authData, [][]byte, [32]byte, error) {
dev, err := getFidoDevice()
if err != nil {
return nil, authData{}, nil, [32]byte{}, fmt.Errorf("could not obtain security key device: %w", err)
}
data, err := extractFidoAuthData(auth)
if err != nil {
return nil, authData{}, nil, [32]byte{}, fmt.Errorf("could not extract security key authentication data: %w", err)
}
credentialIDs, err := constructCredentialIDs(data.AllowCredentials)
if err != nil {
return nil, authData{}, nil, [32]byte{}, err
}
clientDataHash := sha256.Sum256(data.ClientDataJSONBytes)
return dev, data, credentialIDs, clientDataHash, nil
}
func processFidoAssertion(assertion *libfido2.Assertion) ([]byte, error) {
var authData []byte
if err := cbor.Unmarshal(assertion.AuthDataCBOR, &authData); err != nil {
return nil, fmt.Errorf("failed to decode CBOR authenticator data: %w", err)
}
return authData, nil
}
func performAssertion(dev *libfido2.Device, rpID string, clientDataHash []byte, credentialIDs [][]byte, pin string) (*libfido2.Assertion, error) {
assertion, err := dev.Assertion(
rpID,
clientDataHash,
credentialIDs,
pin,
&libfido2.AssertionOpts{UP: libfido2.True},
)
if err != nil {
return nil, fmt.Errorf("FIDO2 assertion failed: %w", err)
}
return assertion, nil
}
// performAssertationWithTimeout - initializes the assertion and sends data to the touchEventCh (with some delay) in parallel.
func performAssertionWithTimeout(ctx context.Context, dev *libfido2.Device, rpID string, clientDataHash []byte, credentialIDs [][]byte, pin string, touchEventCh chan struct{}) (*libfido2.Assertion, error) {
type assertionResult struct {
assertion *libfido2.Assertion
err error
}
resultCh := make(chan assertionResult, 1)
go func() {
assertion, err := performAssertion(dev, rpID, clientDataHash, credentialIDs, pin)
resultCh <- assertionResult{assertion: assertion, err: err}
}()
nearTimeout := time.NewTimer(touchNotificationDelay)
defer nearTimeout.Stop()
select {
case result := <-resultCh:
if result.err != nil {
return nil, result.err
}
return result.assertion, nil
case <-nearTimeout.C:
// Notify that touch is required.
select {
case touchEventCh <- struct{}{}:
default:
}
// Wait for either completion or cancellation.
select {
case result := <-resultCh:
if result.err != nil {
return nil, result.err
}
return result.assertion, nil
case <-ctx.Done():
if err := dev.Cancel(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrAssertionCancelled, err)
}
return nil, ErrAssertionCancelled
}
}
}
func AuthWithHardwareKeyGUI(ctx context.Context, client *proton.Client, auth proton.Auth, touchEventCh chan struct{}, touchConfirmCh chan struct{}, pin string) error {
dev, fidoAuthData, credentialIDs, clientDataHash, err := prepareFidoAuth(auth)
if err != nil {
return err
}
assertion, err := performAssertionWithTimeout(ctx, dev, fidoAuthData.RpID, clientDataHash[:], credentialIDs, pin, touchEventCh)
if err != nil {
return err
}
// Notify that spinner should be displayed, as assertion has finished.
touchConfirmCh <- struct{}{}
// Decode CBOR to get raw authenticator data.
authData, err := processFidoAssertion(assertion)
if err != nil {
return err
}
return authWithFido(client,
auth,
assertion.CredentialID,
fidoAuthData.ClientDataJSONBytes,
authData,
assertion.Sig)
}
func AuthWithHardwareKeyCLI(cliProvider CLIProvider, client *proton.Client, auth proton.Auth) error {
cliProvider.PromptAndWaitReturn("Please insert your security key")
dev, fidoAuthData, credentialIDs, clientDataHash, err := prepareFidoAuth(auth)
if err != nil {
return err
}
pinSupported, err := IsPinSupported()
if err != nil {
return fmt.Errorf("could not determine security key PIN support: %w", err)
}
var pin string
if pinSupported {
pin = cliProvider.ReadSecurityKeyPin()
if pin == "" {
return errors.New("a PIN is required for this security key")
}
}
fmt.Println("Please touch the button or sensor on your security key.")
assertion, err := performAssertion(dev, fidoAuthData.RpID, clientDataHash[:], credentialIDs, pin)
if err != nil {
return err
}
authData, err := processFidoAssertion(assertion)
if err != nil {
return err
}
fmt.Println("Submitting FIDO2 authentication request.")
return authWithFido(
client,
auth,
assertion.CredentialID,
fidoAuthData.ClientDataJSONBytes,
authData,
assertion.Sig)
}

View File

@ -0,0 +1,94 @@
// Copyright (c) 2025 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/>.
//go:build windows
package fido
import (
"fmt"
"time"
"github.com/ProtonMail/go-proton-api"
"github.com/go-ctap/ctaphid/pkg/webauthntypes"
"github.com/go-ctap/winhello"
"github.com/go-ctap/winhello/window"
)
func AuthWithHardwareKeyCLI(_ CLIProvider, client *proton.Client, auth proton.Auth) error {
return AuthWithHardwareKeyGUI(client, auth, true)
}
func AuthWithHardwareKeyGUI(client *proton.Client, auth proton.Auth, onCLI bool) error {
fidoAuthData, err := extractFidoAuthData(auth)
if err != nil {
return fmt.Errorf("could not extract security key authentication data: %w", err)
}
var credentialDescriptors []webauthntypes.PublicKeyCredentialDescriptor
for _, cred := range fidoAuthData.AllowCredentials {
credMap, ok := cred.(map[string]interface{})
if !ok {
continue
}
idArray, ok := credMap["id"].([]interface{})
if !ok {
continue
}
credID := sliceAnyToByteArray(idArray)
credentialDescriptors = append(credentialDescriptors, webauthntypes.PublicKeyCredentialDescriptor{
ID: credID,
Type: webauthntypes.PublicKeyCredentialTypePublicKey,
})
}
if len(credentialDescriptors) == 0 {
return fmt.Errorf("no valid credential descriptors found")
}
windowHandler, err := window.GetForegroundWindow()
if err != nil {
return fmt.Errorf("failed to obtain window handle: %w", err)
}
if onCLI {
fmt.Println("Please use Windows Hello to authenticate.")
}
assertion, err := winhello.GetAssertion(windowHandler,
fidoAuthData.RpID,
fidoAuthData.ClientDataJSONBytes,
credentialDescriptors,
nil,
&winhello.AuthenticatorGetAssertionOptions{
Timeout: time.Second * 60,
AuthenticatorAttachment: winhello.WinHelloAuthenticatorAttachmentCrossPlatform,
UserVerificationRequirement: winhello.WinHelloUserVerificationRequirementPreferred,
CredentialHints: []webauthntypes.PublicKeyCredentialHint{
webauthntypes.PublicKeyCredentialHintSecurityKey,
},
},
)
if err != nil {
return fmt.Errorf("windows Hello assertion failed: %w", err)
}
if onCLI {
fmt.Println("Submitting FIDO2 authentication request.")
}
return authWithFido(client, auth, assertion.Credential.ID, fidoAuthData.ClientDataJSONBytes, assertion.AuthDataRaw, assertion.Signature)
}

23
internal/fido/types.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright (c) 2025 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 fido
type CLIProvider interface {
PromptAndWaitReturn(string)
ReadSecurityKeyPin() string
}

111
internal/fido/utils.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright (c) 2025 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 fido
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/ProtonMail/go-proton-api"
)
type authData struct {
AllowCredentials []interface{}
ClientDataJSONBytes []byte
RpID string
}
func extractFidoAuthData(auth proton.Auth) (authData, error) {
authOptions, ok := auth.TwoFA.FIDO2.AuthenticationOptions.(map[string]interface{})
if !ok {
return authData{}, fmt.Errorf("invalid authentication options format")
}
publicKey, ok := authOptions["publicKey"].(map[string]interface{})
if !ok {
return authData{}, fmt.Errorf("no publicKey found in authentication options")
}
rpID, ok := publicKey["rpId"].(string)
if !ok {
return authData{}, fmt.Errorf("could not find rpId in authentication options")
}
challengeArray, ok := publicKey["challenge"].([]interface{})
if !ok {
return authData{}, fmt.Errorf("no challenge found in authentication options")
}
challenge := sliceAnyToByteArray(challengeArray)
allowCredentials, ok := publicKey["allowCredentials"].([]interface{})
if !ok || len(allowCredentials) == 0 {
return authData{}, fmt.Errorf("no allowed credentials found in authentication options")
}
clientDataJSON := map[string]interface{}{
"type": "webauthn.get",
"challenge": base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(challenge),
"origin": "https://" + rpID,
}
clientDataJSONBytes, err := json.Marshal(clientDataJSON)
if err != nil {
return authData{}, fmt.Errorf("failed to marshal client data JSON: %w", err)
}
return authData{
AllowCredentials: allowCredentials,
ClientDataJSONBytes: clientDataJSONBytes,
RpID: rpID,
}, nil
}
func sliceAnyToByteArray(s []any) []byte {
result := make([]byte, len(s))
for i, val := range s {
if intVal, ok := val.(float64); ok {
result[i] = byte(intVal)
} else {
panic("boom")
}
}
return result
}
func authWithFido(client *proton.Client, auth proton.Auth, credentialIDs []byte, clientDataJSON []byte, authDataRaw []byte, signature []byte) error {
credentialIDInts := make([]int, len(credentialIDs))
for i, b := range credentialIDs {
credentialIDInts[i] = int(b)
}
fido2Req := proton.FIDO2Req{
AuthenticationOptions: auth.TwoFA.FIDO2.AuthenticationOptions,
ClientData: base64.StdEncoding.EncodeToString(clientDataJSON),
AuthenticatorData: base64.StdEncoding.EncodeToString(authDataRaw),
Signature: base64.StdEncoding.EncodeToString(signature),
CredentialID: credentialIDInts,
}
if err := client.Auth2FA(context.Background(), proton.Auth2FAReq{FIDO2: fido2Req}); err != nil {
return fmt.Errorf("FIDO2 authentication failed: %w", err)
}
return nil
}

View File

@ -87,8 +87,7 @@ func withClientConn(ctx context.Context, settingsPath string, fn func(context.Co
if err != nil {
return err
}
cc, err := grpc.DialContext(
ctx,
cc, err := grpc.NewClient(
net.JoinHostPort(Host, fmt.Sprint(config.Port)),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)

View File

@ -17,8 +17,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc v3.21.12
// protoc-gen-go v1.36.6
// protoc v5.29.5
// source: focus.proto
package proto
@ -30,6 +30,7 @@ import (
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@ -40,20 +41,17 @@ const (
)
type VersionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
unknownFields protoimpl.UnknownFields
Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *VersionResponse) Reset() {
*x = VersionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_focus_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_focus_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *VersionResponse) String() string {
@ -64,7 +62,7 @@ func (*VersionResponse) ProtoMessage() {}
func (x *VersionResponse) ProtoReflect() protoreflect.Message {
mi := &file_focus_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -88,44 +86,29 @@ func (x *VersionResponse) GetVersion() string {
var File_focus_proto protoreflect.FileDescriptor
var file_focus_proto_rawDesc = []byte{
0x0a, 0x0b, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x66,
0x6f, 0x63, 0x75, 0x73, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x22, 0x2b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x81,
0x01, 0x0a, 0x05, 0x46, 0x6f, 0x63, 0x75, 0x73, 0x12, 0x3d, 0x0a, 0x05, 0x52, 0x61, 0x69, 0x73,
0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a,
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x66, 0x6f, 0x63,
0x75, 0x73, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x6e, 0x2d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
const file_focus_proto_rawDesc = "" +
"\n" +
"\vfocus.proto\x12\x05focus\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1egoogle/protobuf/wrappers.proto\"+\n" +
"\x0fVersionResponse\x12\x18\n" +
"\aversion\x18\x01 \x01(\tR\aversion2\x81\x01\n" +
"\x05Focus\x12=\n" +
"\x05Raise\x12\x1c.google.protobuf.StringValue\x1a\x16.google.protobuf.Empty\x129\n" +
"\aVersion\x12\x16.google.protobuf.Empty\x1a\x16.focus.VersionResponseB=Z;github.com/ProtonMail/proton-bridge/v3/internal/focus/protob\x06proto3"
var (
file_focus_proto_rawDescOnce sync.Once
file_focus_proto_rawDescData = file_focus_proto_rawDesc
file_focus_proto_rawDescData []byte
)
func file_focus_proto_rawDescGZIP() []byte {
file_focus_proto_rawDescOnce.Do(func() {
file_focus_proto_rawDescData = protoimpl.X.CompressGZIP(file_focus_proto_rawDescData)
file_focus_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_focus_proto_rawDesc), len(file_focus_proto_rawDesc)))
})
return file_focus_proto_rawDescData
}
var file_focus_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_focus_proto_goTypes = []interface{}{
var file_focus_proto_goTypes = []any{
(*VersionResponse)(nil), // 0: focus.VersionResponse
(*wrapperspb.StringValue)(nil), // 1: google.protobuf.StringValue
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
@ -147,25 +130,11 @@ func file_focus_proto_init() {
if File_focus_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_focus_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VersionResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_focus_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_focus_proto_rawDesc), len(file_focus_proto_rawDesc)),
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
@ -176,7 +145,6 @@ func file_focus_proto_init() {
MessageInfos: file_focus_proto_msgTypes,
}.Build()
File_focus_proto = out.File
file_focus_proto_rawDesc = nil
file_focus_proto_goTypes = nil
file_focus_proto_depIdxs = nil
}

View File

@ -17,8 +17,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v3.21.12
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.5
// source: focus.proto
package proto
@ -34,8 +34,8 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Focus_Raise_FullMethodName = "/focus.Focus/Raise"
@ -45,6 +45,12 @@ const (
// FocusClient is the client API for Focus service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// **********************************************************************************************************************
//
// Service Declaration
//
// **********************************************************************************************************************≠––
type FocusClient interface {
Raise(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionResponse, error)
@ -59,8 +65,9 @@ func NewFocusClient(cc grpc.ClientConnInterface) FocusClient {
}
func (c *focusClient) Raise(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Focus_Raise_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Focus_Raise_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -68,8 +75,9 @@ func (c *focusClient) Raise(ctx context.Context, in *wrapperspb.StringValue, opt
}
func (c *focusClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(VersionResponse)
err := c.cc.Invoke(ctx, Focus_Version_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Focus_Version_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -78,16 +86,25 @@ func (c *focusClient) Version(ctx context.Context, in *emptypb.Empty, opts ...gr
// FocusServer is the server API for Focus service.
// All implementations must embed UnimplementedFocusServer
// for forward compatibility
// for forward compatibility.
//
// **********************************************************************************************************************
//
// Service Declaration
//
// **********************************************************************************************************************≠––
type FocusServer interface {
Raise(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
Version(context.Context, *emptypb.Empty) (*VersionResponse, error)
mustEmbedUnimplementedFocusServer()
}
// UnimplementedFocusServer must be embedded to have forward compatible implementations.
type UnimplementedFocusServer struct {
}
// UnimplementedFocusServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedFocusServer struct{}
func (UnimplementedFocusServer) Raise(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Raise not implemented")
@ -96,6 +113,7 @@ func (UnimplementedFocusServer) Version(context.Context, *emptypb.Empty) (*Versi
return nil, status.Errorf(codes.Unimplemented, "method Version not implemented")
}
func (UnimplementedFocusServer) mustEmbedUnimplementedFocusServer() {}
func (UnimplementedFocusServer) testEmbeddedByValue() {}
// UnsafeFocusServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to FocusServer will
@ -105,6 +123,13 @@ type UnsafeFocusServer interface {
}
func RegisterFocusServer(s grpc.ServiceRegistrar, srv FocusServer) {
// If the following call pancis, it indicates UnimplementedFocusServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Focus_ServiceDesc, srv)
}

View File

@ -863,6 +863,12 @@ void QMLBackend::login2FA(QString const &username, QString const &code) const {
)
}
void QMLBackend::loginFido(const QString &username, QString const &pin) const {
HANDLE_EXCEPTION(
app().grpc().loginFido(username, pin);
)
}
//****************************************************************************************************************************************************
/// \param[in] username The username.
@ -884,6 +890,11 @@ void QMLBackend::loginAbort(QString const &username) const {
)
}
void QMLBackend::abortFidoAssertion(QString const &username) const {
HANDLE_EXCEPTION(
app().grpc().abortFidoAssertion(username);
)
}
//****************************************************************************************************************************************************
/// \param[in] active Should DoH be active.
@ -1206,7 +1217,6 @@ void QMLBackend::onLoginAlreadyLoggedIn(QString const &userID) {
)
}
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
//****************************************************************************************************************************************************
@ -1347,11 +1357,19 @@ void QMLBackend::connectGrpcEvents() {
connect(client, &GRPCClient::login2FARequested, this, &QMLBackend::login2FARequested);
connect(client, &GRPCClient::login2FAError, this, &QMLBackend::login2FAError);
connect(client, &GRPCClient::login2FAErrorAbort, this, &QMLBackend::login2FAErrorAbort);
connect(client, &GRPCClient::loginFidoRequested, this, &QMLBackend::loginFidoRequested);
connect(client, &GRPCClient::login2FAOrFidoRequested, this, &QMLBackend::login2FAOrFidoRequested);
connect(client, &GRPCClient::login2PasswordRequested, this, &QMLBackend::login2PasswordRequested);
connect(client, &GRPCClient::login2PasswordError, this, &QMLBackend::login2PasswordError);
connect(client, &GRPCClient::login2PasswordErrorAbort, this, &QMLBackend::login2PasswordErrorAbort);
connect(client, &GRPCClient::loginFinished, this, &QMLBackend::onLoginFinished);
connect(client, &GRPCClient::loginAlreadyLoggedIn, this, &QMLBackend::onLoginAlreadyLoggedIn);
connect(client, &GRPCClient::loginFidoTouchRequested, this, &QMLBackend::loginFidoTouchRequested);
connect(client, &GRPCClient::loginFidoTouchCompleted, this, &QMLBackend::loginFidoTouchCompleted);
connect(client, &GRPCClient::loginFidoPinRequired, this, &QMLBackend::loginFidoPinRequired);
connect(client, &GRPCClient::loginFidoPinInvalid, this, &QMLBackend::loginFidoPinInvalid);
connect(client, &GRPCClient::loginFidoPinBlocked, this, &QMLBackend::loginFidoPinBlocked);
connect(client, &GRPCClient::loginFidoError, this, &QMLBackend::loginFidoError);
connect(client, &GRPCClient::loginHvRequested, this, &QMLBackend::loginHvRequested);
connect(client, &GRPCClient::loginHvError, this, &QMLBackend::loginHvError);

View File

@ -193,8 +193,10 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
void login(QString const &username, QString const &password) const; ///< Slot for the login button (initial login).
void loginHv(QString const &username, QString const &password) const; ///< Slot for the login button (after HV challenge completed).
void login2FA(QString const &username, QString const &code) const; ///< Slot for the login button (2FA login).
void loginFido(QString const &username, QString const &pin) const; ///< Slot for the authenticate button (FIDO2/Security Key login).
void login2Password(QString const &username, QString const &password) const; ///< Slot for the login button (mailbox password login).
void loginAbort(QString const &username) const; ///< Slot for the login abort procedure.
void abortFidoAssertion(QString const &username) const; ///< Slot for aborting the FIDO login procedure.
void toggleDoH(bool active); ///, Slot for the DoH toggle.
void toggleAutomaticUpdate(bool makeItActive); ///< Slot for the automatic update toggle
void updateCurrentMailClient(); ///< Slot for the change of the current mail client.
@ -242,11 +244,19 @@ 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 loginFidoRequested(QString const &username); ///< Signal for the 'loginFidoRequested' gRPC stream event.
void login2FAOrFidoRequested(QString const &username); ///<Signal for the 'login2FAOrFidoRequested' 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.
void loginAlreadyLoggedIn(int index); ///< Signal for the 'loginAlreadyLoggedIn' gRPC stream event.
void loginFidoTouchRequested(QString const &username); ///< Signal for the `loginFidoTouchRequested' gRPC stream event.
void loginFidoTouchCompleted(QString const &username); ///< Signal for the `loginFidoTouchCompleted' gRPC stream event.
void loginFidoPinRequired(QString const &username); ///< Signal for the `loginFidoPinRequired' gRPC stream event.
void loginFidoPinInvalid(QString const &errorMsg); ///< Signal for the `loginFidoPinInvalid' gRPC stream event.
void loginFidoPinBlocked(QString const &errorMsg); ///< Signal for the `loginFidoPinBlocked' gRPC stream event.
void loginFidoError(QString const &errorMsg); ///< Signal for the 'loginFidoError' gRPC stream event.
void loginHvRequested(QString const &hvUrl); ///< Signal for the 'loginHvRequested' gRPC stream event.
void loginHvError(QString const &errorMsg); ///< Signal for the 'loginHvError' gRPC stream event.
void updateManualReady(QString const &version); ///< Signal for the 'updateManualReady' gRPC stream event.

View File

@ -72,6 +72,7 @@
<file>qml/icons/systray-mono-warn.png</file>
<file>qml/icons/systray.svg</file>
<file>qml/icons/ic-notification-bell.svg</file>
<file>qml/icons/fingerprint.svg</file>
<file alias="bridge.svg">../../../../dist/bridge.svg</file>
<file alias="bridgeMacOS.svg">../../../../dist/bridgeMacOS.svg</file>
<file>qml/KeychainSettings.qml</file>
@ -112,6 +113,7 @@
<file>qml/Proton/TextArea.qml</file>
<file>qml/Proton/TextField.qml</file>
<file>qml/Proton/Toggle.qml</file>
<file>qml/Proton/Spinner.qml</file>
<file>qml/Resources/bug_report_flow.json</file>
<file>qml/Resources/Help/Template.html</file>
<file>qml/Resources/Help/WhyBridge.html</file>

View File

@ -22,6 +22,7 @@ Write-host "Bridge-gui directory is $scriptDir"
Write-host "Bridge repos root dir $bridgeRepoRootDir"
Push-Location $scriptDir
$ErrorActionPreference = "Stop"
$cmakeExe=$(Get-Command cmake).source

View File

@ -24,6 +24,9 @@ Dialog {
property var notification
property bool isUserNotification: false
// Placeholder for text input label text.
property string textFieldText: ""
modal: true
shouldShow: notification && notification.active && !notification.dismissed
@ -53,6 +56,7 @@ Dialog {
sourceSize.width: 64
visible: source != ""
}
Label {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 8
@ -83,6 +87,43 @@ Dialog {
implicitWidth: additionalChildrenContainer.childrenRect.width
visible: children.length > 0
}
Image {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 16
Layout.preferredHeight: 64
Layout.preferredWidth: 64
source: root.notification.additionalImageSrc
sourceSize.height: 64
sourceSize.width: 64
visible: root.notification.additionalImageSrc != ""
}
TextField {
id: textField
Layout.fillWidth: true
Layout.preferredWidth: 240
Layout.bottomMargin: 16
colorScheme: root.colorScheme
text: root.textFieldText
visible: root.notification && root.notification.useTextField
onTextChanged: root.notification.textFieldChanged(text)
Connections {
target: root.notification
function onClearTextFieldRequested() {
root.notification.textFieldChanged("")
textField.clear();
}
function onFocusTextField() {
textField.focus = true;
}
}
}
LinkLabel {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 32
@ -94,6 +135,18 @@ Dialog {
}
Spinner {
Layout.alignment: Qt.AlignHCenter
colorScheme: root.colorScheme
Layout.bottomMargin: 16
Layout.preferredHeight: 64
Layout.preferredWidth: 64
size: 64
running: true
visible: root.notification && root.notification.busyIndicator
}
ColumnLayout {
spacing: 8
@ -105,8 +158,7 @@ Dialog {
action: modelData
colorScheme: root.colorScheme
loading: modelData.loading
secondary: index > 0
}
secondary: modelData.forceSecondary !== undefined ? modelData.forceSecondary : index > 0 }
}
}
}

View File

@ -109,6 +109,18 @@ Item {
colorScheme: root.colorScheme
notification: root.notifications.repairBridge
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.touchFidoKey
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.fidoPinRequested
}
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.fidoPinBlocked
}
UserNotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.userNotification

View File

@ -40,6 +40,19 @@ QtObject {
property string subtitle
property string username
// Whether to display a spinner.
property bool busyIndicator: false
// Whether to display a text input field.
property bool useTextField: false
// Source for an additional image, won't be displayed if empty.
property string additionalImageSrc: ""
// Text input field operations via signals.
signal clearTextFieldRequested()
signal textFieldChanged(string value)
signal focusTextField()
onActiveChanged: {
dismissed = false;

View File

@ -62,7 +62,7 @@ QtObject {
target: Backend
}
}
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion, root.hvErrorEvent, root.repairBridge, root.userNotification]
property var all: [root.noInternet, root.imapPortStartupError, root.smtpPortStartupError, root.imapPortChangeError, root.smtpPortChangeError, root.imapConnectionModeChangeError, root.smtpConnectionModeChangeError, root.updateManualReady, root.updateManualRestartNeeded, root.updateManualError, root.updateForce, root.updateForceError, root.updateSilentRestartNeeded, root.updateSilentError, root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, root.alreadyLoggedIn, root.enableBeta, root.bugReportSendSuccess, root.bugReportSendError, root.bugReportSendFallback, root.cacheCantMove, root.cacheLocationChangeSuccess, root.enableSplitMode, root.resetBridge, root.changeAllMailVisibility, root.deleteAccount, root.noKeychain, root.rebuildKeychain, root.addressChanged, root.apiCertIssue, root.userBadEvent, root.imapLoginWhileSignedOut, root.genericError, root.genericQuestion, root.hvErrorEvent, root.repairBridge, root.userNotification, root.touchFidoKey, root.fidoPinRequested, root.fidoPinBlocked, root.fidoErrorEvent]
property Notification alreadyLoggedIn: Notification {
brief: qsTr("Already signed in")
description: qsTr("This account is already signed in.")
@ -1229,6 +1229,170 @@ QtObject {
}
}
property Notification touchFidoKey: Notification {
title: qsTr("Touch your security key")
description: qsTr("To complete authentication, touch the button or sensor on your security key.")
group: Notifications.Group.Dialogs
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Info
additionalImageSrc: "./icons/fingerprint.svg"
function reset() {
root.touchFidoKey.active = false;
root.touchFidoKey.busyIndicator = false;
root.touchFidoKey.additionalImageSrc = "./icons/fingerprint.svg";
}
action: [
Action {
id: touchFidoKey_cancel
text: qsTr("Cancel")
property bool forceSecondary: true
onTriggered: {
Backend.abortFidoAssertion(root.touchFidoKey.username);
root.touchFidoKey.reset();
}
}
]
Connections {
function onLoginFidoTouchRequested(username) {
root.touchFidoKey.username = username;
root.touchFidoKey.active = true;
touchFidoKey_cancel.enabled = true;
}
function onLoginFidoTouchCompleted(_) {
root.touchFidoKey.additionalImageSrc = "";
root.touchFidoKey.busyIndicator = true;
touchFidoKey_cancel.enabled = false;
}
function onLoginFidoPinInvalid(_) {
root.touchFidoKey.reset();
}
function onLoginFinished(_) {
root.touchFidoKey.reset();
}
function onLoginFidoError(errorMsg) {
root.touchFidoKey.reset();
}
target: Backend
}
}
property Notification fidoPinRequested: Notification {
property string fidoPinInput: ""
title: qsTr("Enter security key PIN")
description: qsTr("To continue, enter the PIN for your security key.")
group: Notifications.Group.Dialogs
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Info
useTextField: true
onTextFieldChanged: function(value) {
root.fidoPinRequested.fidoPinInput = value;
}
function reset() {
root.fidoPinRequested.active = false;
root.fidoPinRequested.clearTextFieldRequested();
root.fidoPinRequested.type = Notification.NotificationType.Info;
}
function clearAndFocusTextField() {
root.fidoPinRequested.clearTextFieldRequested();
root.fidoPinRequested.focusTextField()
}
action: [
Action {
text: qsTr("Continue")
onTriggered: {
Backend.loginFido("", Qt.btoa(root.fidoPinRequested.fidoPinInput));
root.fidoPinRequested.reset();
}
},
Action {
text: qsTr("Cancel")
onTriggered: {
root.fidoPinRequested.reset();
}
}
]
Connections {
function onLoginFidoPinRequired(_) {
root.fidoPinRequested.clearAndFocusTextField();
root.fidoPinRequested.active = true;
}
function onLoginFidoPinInvalid(_) {
root.fidoPinRequested.clearAndFocusTextField();
root.fidoPinRequested.active = true;
root.fidoPinRequested.description = qsTr("The PIN you entered is incorrect. Try again.");
root.fidoPinRequested.type = Notification.NotificationType.Warning;
}
function onLoginFidoTouchRequested(_) {
root.fidoPinRequested.reset();
}
function onLoginFinished(_) {
root.fidoPinRequested.reset();
}
function onLoginFidoError(errorMsg) {
root.fidoPinRequested.reset();
}
target: Backend
}
}
property Notification fidoPinBlocked: Notification {
title: qsTr("Security key PIN blocked")
description: qsTr("Your security key PIN is blocked due to too many failed attempts. Try removing and re-inserting your key, or check your security key's documentation for unlock instructions.")
group: Notifications.Group.Dialogs
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger
action: [
Action {
text: qsTr("OK")
onTriggered: {
root.fidoPinBlocked.active = false;
root.touchFidoKey.reset();
root.fidoPinRequested.reset();
}
}
]
Connections {
function onLoginFidoPinBlocked(_) {
root.fidoPinBlocked.active = true;
}
target: Backend
}
}
property Notification fidoErrorEvent: Notification {
group: Notifications.Group.Configuration
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger
action: Action {
text: qsTr("OK")
onTriggered: {
root.fidoErrorEvent.active = false;
}
}
Connections {
function onLoginFidoError(errorMsg) {
root.fidoErrorEvent.active = true;
root.fidoErrorEvent.description = errorMsg;
}
target: Backend
}
}
signal askChangeAllMailVisibility(var isVisibleNow)
signal askDeleteAccount(var user)
signal askEnableBeta

View File

@ -0,0 +1,52 @@
// Copyright (c) 2025 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.impl
Item {
id: root
property ColorScheme colorScheme: ProtonStyle.currentStyle
property color color: colorScheme.interaction_norm
property int size: 16
property bool running: true
property int duration: 1000
property string source: "/qml/icons/Loader_48.svg"
implicitWidth: size
implicitHeight: size
ColorImage {
id: spinnerImage
anchors.centerIn: parent
width: root.size
height: root.size
source: root.source
color: root.color
sourceSize.width: root.size
sourceSize.height: root.size
visible: root.running
RotationAnimation {
target: spinnerImage
property: "rotation"
from: 0
to: 360
duration: root.duration
loops: Animation.Infinite
running: root.running
direction: RotationAnimation.Clockwise
}
}
}

View File

@ -40,3 +40,4 @@ TextField 4.0 TextField.qml
Toggle 4.0 Toggle.qml
WebFrame 4.0 WebFrame.qml
ContextMenu 4.0 ContextMenu.qml
Spinner 1.0 Spinner.qml

View File

@ -21,6 +21,8 @@ FocusScope {
enum RootStack {
Login,
TOTP,
FIDO,
TOTPOrFIDO,
MailboxPassword,
HV
}
@ -51,6 +53,7 @@ FocusScope {
passwordTextField.hidePassword();
secondPasswordTextField.hidePassword();
hvLinkClicked = false;
fidoLayout.reset();
}
function resetViaHv() {
usernameTextField.enabled = false;
@ -93,6 +96,29 @@ FocusScope {
twoFactorUsernameLabel.text = username;
stackLayout.currentIndex = Login.RootStack.TOTP;
twoFactorPasswordTextField.focus = true;
switchToTotpButton.visible = false;
switchToFidoButton.visible = false;
}
function onLoginFidoRequested(username) {
fidoUsernameLabel.text = username;
stackLayout.currentIndex = Login.RootStack.FIDO;
switchToTotpButton.visible = false;
switchToFidoButton.visible = false;
}
function onLogin2FAOrFidoRequested(username) {
fidoUsernameLabel.text = username;
twoFactorUsernameLabel.text = username;
stackLayout.currentIndex = Login.RootStack.FIDO;
switchToTotpButton.visible = true;
switchToFidoButton.visible = true;
}
function onLoginFidoPinBlocked(_) {
console.assert(stackLayout.currentIndex === Login.RootStack.FIDO, "Unexpected onLoginFidoPinBlocked");
root.reset();
}
function onLoginFidoError(_) {
console.assert(stackLayout.currentIndex === Login.RootStack.FIDO || stackLayout.currentIndex === Login.RootStack.Login, "Unexpected loginFidoError");
root.reset();
}
function onLogin2PasswordError(_) {
console.assert(stackLayout.currentIndex === Login.RootStack.MailboxPassword, "Unexpected login2PasswordError");
@ -352,7 +378,7 @@ FocusScope {
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.")
text: qsTr("You have enabled two-factor authentication. Enter the 6-digit code provided by your authenticator application.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
@ -406,6 +432,117 @@ FocusScope {
root.abort();
}
}
Label {
id: switchToFidoButton
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: "<a href='#'>" + qsTr("Use security key instead") + "</a>"
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: !twoFAButton.loading
onClicked: {
stackLayout.currentIndex = Login.RootStack.FIDO;
fidoLayout.reset();
totpLayout.reset();
}
}
}
}
}
Item {
ColumnLayout {
id: fidoLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: ProtonStyle.wizard_spacing_medium
function reset() {
fidoButton.loading = false;
}
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("Security key authentication")
type: Label.LabelType.Title
}
Label {
id: fidoUsernameLabel
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: fidoDescriptionLabel
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: qsTr("Security key authentication is enabled. Please connect your security key.")
type: Label.LabelType.Body
wrapMode: Text.WordWrap
}
Button {
id: fidoButton
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !loading
text: loading ? qsTr("Authenticating") : qsTr("Authenticate")
onClicked: {
if (Backend.goos === "windows") {
fidoButton.loading = true;
}
Backend.loginFido(usernameTextField.text, "");
}
}
Button {
Layout.fillWidth: true
colorScheme: wizard.colorScheme
enabled: !fidoButton.loading
secondary: true
secondaryIsOpaque: true
text: qsTr("Cancel")
onClicked: {
root.abort();
}
}
Label {
id: switchToTotpButton
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
colorScheme: wizard.colorScheme
horizontalAlignment: Text.AlignHCenter
text: "<a href='#'>" + qsTr("Use authenticator app instead") + "</a>"
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: !fidoButton.loading
onClicked: {
stackLayout.currentIndex = Login.RootStack.TOTP;
fidoLayout.reset();
totpLayout.reset();
}
}
}
}
}
Item {
@ -499,7 +636,9 @@ FocusScope {
text: qsTr("Cancel")
onClicked: {
root.abort();
stackLayout.currentIndex = Login.RootStack.TOTP;
twoFactorPasswordTextField.focus = true;
}
}
}

View File

@ -0,0 +1,4 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.6112 6.03055C13.016 5.38841 3.66669 15.003 4.55919 26.5258C4.62317 27.3517 4.00546 28.0732 3.17951 28.1371C2.35356 28.2011 1.63213 27.5834 1.56815 26.7574C0.53451 13.4124 11.3953 2.26091 24.8337 3.03834L24.8838 3.04124L25.8497 3.16232C36.7014 3.83482 45.4565 12.5137 46.3592 23.3767L46.3613 23.4014L46.4808 25.2023L46.4812 25.2082C46.7848 29.3648 43.405 32.765 39.3857 32.61L39.3601 32.609C36.569 32.4535 34.1159 30.5901 33.2016 27.8396L33.1927 27.8128L31.9883 23.9472L31.9864 23.9409C30.7522 19.9188 26.8518 17.3489 22.6425 17.8625C18.5134 18.3812 15.2451 21.8004 15.0421 25.8707L15.0412 25.8894C14.8217 29.4114 15.687 34.3093 19.2905 40.3707C19.7138 41.0828 19.4797 42.0032 18.7676 42.4265C18.0555 42.8499 17.1351 42.6158 16.7118 41.9037C12.8494 35.4069 11.7861 29.9196 12.0463 25.7119C12.3297 20.1236 16.7679 15.5754 22.2717 14.8856L22.2762 14.885C27.9429 14.192 33.1957 17.6603 34.8535 23.0578L34.8544 23.0609L36.0526 26.9056C36.5854 28.4923 37.9777 29.5218 39.5142 29.6127C41.7516 29.6914 43.6632 27.7826 43.4889 25.4234L43.4881 25.4124L43.3685 23.6131C42.5807 14.2044 34.9806 6.71261 25.6214 6.15397L25.5726 6.15106L24.6112 6.03055ZM22.2168 8.83893C30.2803 8.17709 37.8013 13.3446 40.0438 21.2811C40.269 22.0783 39.8054 22.9072 39.0082 23.1324C38.2109 23.3577 37.3821 22.894 37.1568 22.0968C35.3037 15.5383 29.093 11.2839 22.4606 11.829L22.4568 11.8293C15.6092 12.3743 10.1514 17.8288 9.49593 24.7299L9.49164 24.775L9.48517 24.8164L9.4844 24.8216L9.47937 24.8579C9.47461 24.8934 9.46728 24.9513 9.45861 25.0301C9.44126 25.1879 9.41858 25.4296 9.4004 25.7446C9.36402 26.3752 9.3459 27.2966 9.42381 28.4258C9.57985 30.6875 10.1192 33.7576 11.6477 36.9934C12.0016 37.7425 11.6812 38.6365 10.9321 38.9904C10.1831 39.3442 9.289 39.0238 8.93516 38.2748C7.21116 34.6251 6.60615 31.1722 6.43092 28.6323C6.34319 27.3607 6.36271 26.3115 6.40538 25.5718C6.42674 25.2017 6.45393 24.9083 6.47658 24.7023C6.48791 24.5993 6.4981 24.518 6.50588 24.4599L6.5129 24.4094C7.31887 16.108 13.8993 9.50183 22.2168 8.83893Z" fill="#EAE7E4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.8543 24.1542C24.2626 23.5596 25.9956 24.3584 26.4884 25.9571ZM26.4884 25.9571C26.5232 26.0913 26.5603 26.2364 26.6 26.3919C27.3886 29.4774 29.218 36.6354 35.0609 43.3315C35.6056 43.9557 36.5531 44.0201 37.1773 43.4755C37.8015 42.9308 37.866 41.9833 37.3213 41.3591C31.9564 35.2107 30.2746 28.6461 29.4994 25.6202C29.4586 25.4612 29.4204 25.3119 29.3843 25.173L29.3774 25.1468L29.3697 25.1207C28.4071 21.9033 24.8498 20.0553 21.6874 21.3905C18.9068 22.5646 18.0009 25.068 18.0002 26.9985C17.9967 28.7383 18.4925 30.7277 19.1614 32.6486C19.839 34.5945 20.7355 36.5845 21.6192 38.3595C22.5049 40.1382 23.3892 41.7235 24.0514 42.8635C24.3829 43.4341 24.6598 43.8948 24.8546 44.2142C24.952 44.3739 25.029 44.4984 25.0821 44.5837L25.1435 44.6819L25.16 44.7081L25.1664 44.7182C25.1664 44.7182 25.1666 44.7186 26.4336 43.9156L25.1664 44.7182C25.6098 45.4179 26.5368 45.6261 27.2366 45.1826C27.9362 44.7392 28.144 43.8126 27.7007 43.1128L27.6971 43.1072L27.6838 43.086L27.6291 42.9984C27.5804 42.9203 27.5082 42.8035 27.4157 42.6519C27.2307 42.3486 26.9649 41.9064 26.6454 41.3565C26.0056 40.2552 25.1541 38.7282 24.3048 37.0223C23.4535 35.3128 22.6158 33.4461 21.9946 31.6621C21.3649 29.8538 20.9975 28.2406 21.0002 27.0035L21.0002 27.0004C21.0003 25.9312 21.47 24.7388 22.8543 24.1542" fill="#EAE7E4"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -302,6 +302,32 @@ SPStreamEvent newLoginTfaRequestedEvent(QString const &username) {
}
//****************************************************************************************************************************************************
/// \param[in] username The username.
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newLoginFidoRequestedEvent(QString const &username) {
auto event = new ::grpc::LoginFidoRequestedEvent;
event->set_username(username.toStdString());
auto loginEvent = new grpc::LoginEvent;
loginEvent->set_allocated_fidorequested(event);
return wrapLoginEvent(loginEvent);
}
//****************************************************************************************************************************************************
/// \param[in] username The username.
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newLoginTfaOrFidoRequestedEvent(QString const &username) {
auto event = new ::grpc::LoginTfaOrFidoRequestedEvent;
event->set_username(username.toStdString());
auto loginEvent = new grpc::LoginEvent;
loginEvent->set_allocated_tfaorfidorequested(event);
return wrapLoginEvent(loginEvent);
}
//****************************************************************************************************************************************************
/// \return The event.
//****************************************************************************************************************************************************

View File

@ -48,7 +48,9 @@ SPStreamEvent newLoginTfaRequestedEvent(QString const &username); ///< Create a
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.
SPStreamEvent newLoginHvRequestedEvent(); ///< Create a new LoginHvRequestedEvent
SPStreamEvent newLoginHvRequestedEvent(); ///< Create a new LoginHvRequestedEvent.
SPStreamEvent newLoginFidoRequestedEvent(QString const &username); ///< Create a new LoginFidoRequestedEvent.
SPStreamEvent newLoginTfaOrFidoRequestedEvent(QString const &username); ///< Create a new LoginTfaOrFidoRequestedEvent.
// Update related events
SPStreamEvent newUpdateErrorEvent(grpc::UpdateErrorType errorType); ///< Create a new UpdateErrorEvent event.

View File

@ -632,6 +632,28 @@ grpc::Status GRPCClient::login2FA(QString const &username, QString const &code)
return this->logGRPCCallStatus(stub_->Login2FA(this->clientContext().get(), request, &empty), __FUNCTION__);
}
//****************************************************************************************************************************************************
/// \param[in] username The username.
/// \param[in] code The Security key PIN.
/// \return the status for the gRPC call.
//****************************************************************************************************************************************************
grpc::Status GRPCClient::loginFido(const QString &username, const QString &pin) {
LoginRequest request;
request.set_username(username.toStdString());
request.set_password(pin.toStdString());
return this->logGRPCCallStatus(stub_->LoginFido(this->clientContext().get(), request, &empty), __FUNCTION__ );
}
//****************************************************************************************************************************************************
/// \param[in] username The username.
/// \return the status for the gRPC call.
//****************************************************************************************************************************************************
grpc::Status GRPCClient::abortFidoAssertion(const QString &username) {
LoginAbortRequest request;
request.set_username(username.toStdString());
return this->logGRPCCallStatus(stub_->FidoAssertionAbort(this->clientContext().get(), request, &empty), __FUNCTION__);
}
//****************************************************************************************************************************************************
/// \param[in] username The username.
@ -1256,6 +1278,15 @@ void GRPCClient::processLoginEvent(LoginEvent const &event) {
case HV_ERROR:
emit loginHvError(QString::fromStdString(error.message()));
break;
case FIDO_PIN_INVALID:
emit loginFidoPinInvalid(QString::fromStdString(error.message()));
break;
case FIDO_PIN_BLOCKED:
emit loginFidoPinBlocked(QString::fromStdString(error.message()));
break;
case FIDO_ERROR:
emit loginFidoError(QString::fromStdString(error.message()));
break;
default:
this->logError("Unknown login error event received.");
break;
@ -1266,6 +1297,14 @@ void GRPCClient::processLoginEvent(LoginEvent const &event) {
this->logTrace("Login event received: TfaRequested.");
emit login2FARequested(QString::fromStdString(event.tfarequested().username()));
break;
case LoginEvent::kFidoRequested:
this->logTrace("Login event received: FidoRequested.");
emit loginFidoRequested(QString::fromStdString(event.fidorequested().username()));
break;
case LoginEvent::kTfaOrFidoRequested:
this->logTrace("Login event received: TfaOrFidoRequested.");
emit login2FAOrFidoRequested(QString::fromStdString(event.tfaorfidorequested().username()));
break;
case LoginEvent::kTwoPasswordRequested:
this->logTrace("Login event received: TwoPasswordRequested.");
emit login2PasswordRequested(QString::fromStdString(event.twopasswordrequested().username()));
@ -1284,6 +1323,18 @@ void GRPCClient::processLoginEvent(LoginEvent const &event) {
this->logTrace("Login event Received: HvRequested");
emit loginHvRequested(QString::fromStdString(event.hvrequested().hvurl()));
break;
case LoginEvent::kLoginFidoTouchRequested:
this->logTrace("Login event received: FidoTouchRequested");
emit loginFidoTouchRequested(QString::fromStdString(event.loginfidotouchrequested().username()));
break;
case LoginEvent::kLoginFidoTouchCompleted:
this->logTrace("Login event received: FidoTouchCompleted");
emit loginFidoTouchCompleted(QString::fromStdString(event.loginfidotouchcompleted().username()));
break;
case LoginEvent::kLoginFidoPinRequired:
this->logTrace("Login event received: FidoPinRequired");
emit loginFidoPinRequired(QString::fromStdString(event.loginfidopinrequired().username()));
break;
default:
this->logError("Unknown Login event received.");
break;

View File

@ -175,9 +175,11 @@ signals:
public: // login related calls
grpc::Status login(QString const &username, QString const &password); ///< Performs the 'login' call.
grpc::Status login2FA(QString const &username, QString const &code); ///< Performs the 'login2FA' call.
grpc::Status loginFido(QString const &username, QString const &pin); ///< Performs the 'loginFido' call.
grpc::Status login2Passwords(QString const &username, QString const &password); ///< Performs the 'login2Passwords' call.
grpc::Status loginAbort(QString const &username); ///< Performs the 'loginAbort' call.
grpc::Status loginHv(QString const &username, QString const &password); ///< Performs the 'login' call with additional useHv flag
grpc::Status loginHv(QString const &username, QString const &password); ///< Performs the 'login' call with additional useHv flag.
grpc::Status abortFidoAssertion(const QString &username); ///< Performs the 'abortFidoAssertion' call.
signals:
void loginUsernamePasswordError(QString const &errMsg);
@ -186,6 +188,8 @@ signals:
void login2FARequested(QString const &username);
void login2FAError(QString const &errMsg);
void login2FAErrorAbort(QString const &errMsg);
void loginFidoRequested(QString const &username);
void login2FAOrFidoRequested(QString const &username);
void login2PasswordRequested(QString const &username);
void login2PasswordError(QString const &errMsg);
void login2PasswordErrorAbort(QString const &errMsg);
@ -193,6 +197,12 @@ signals:
void loginAlreadyLoggedIn(QString const &userID);
void loginHvRequested(QString const &hvUrl);
void loginHvError(QString const &errMsg);
void loginFidoTouchRequested(QString const &username);
void loginFidoTouchCompleted(QString const &username);
void loginFidoPinRequired(QString const &username);
void loginFidoPinInvalid(QString const &errMsg);
void loginFidoPinBlocked(QString const &errMsg);
void loginFidoError(QString const &errMsg);
public: // Update related calls
grpc::Status checkUpdate();

View File

@ -19,6 +19,7 @@ package cli
import (
"context"
"errors"
"fmt"
"strings"
@ -26,7 +27,9 @@ import (
"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/fido"
"github.com/ProtonMail/proton-bridge/v3/internal/hv"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/abiosoft/ishell"
)
@ -174,22 +177,39 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) {
return
}
if auth.TwoFA.Enabled&proton.HasTOTP != 0 {
if len(auth.TwoFA.FIDO2.RegisteredKeys) > 0 && f.yesNoQuestion("Do you want to use a security key for Two-factor authentication") {
if err := f.authWithHardwareKey(client, auth); err != nil {
f.printAndLogError("Cannot login: ", err)
return
}
} else {
code := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
if code == "" {
f.printAndLogError("Cannot login: need two factor code")
}
u2fLoginEnabled := f.bridge.GetFeatureFlagValue(unleash.InboxBridgeU2FLoginEnabled)
if err := client.Auth2FA(context.Background(), proton.Auth2FAReq{TwoFactorCode: code}); err != nil {
switch auth.TwoFA.Enabled {
case proton.HasTOTP:
if err := f.loginTOTP(c, client); err != nil {
f.printAndLogError("Cannot login: ", err)
return
}
case proton.HasFIDO2:
if !u2fLoginEnabled {
// This case may only occur for internal users.
f.printAndLogError("Cannot login: Security key authentication required but not enabled in server configuration.")
return
}
if len(auth.TwoFA.FIDO2.RegisteredKeys) == 0 {
f.printAndLogError("Cannot login: Security key login is required, but no registered keys were provided.")
}
if err := fido.AuthWithHardwareKeyCLI(f, client, auth); err != nil {
f.printAndLogError("Cannot login: ", err)
return
}
case proton.HasFIDO2AndTOTP:
if u2fLoginEnabled && len(auth.TwoFA.FIDO2.RegisteredKeys) > 0 && f.yesNoQuestion("Do you want to use a security key for Two-factor authentication") {
if err := fido.AuthWithHardwareKeyCLI(f, client, auth); err != nil {
f.printAndLogError("Cannot login: ", err)
return
}
} else if err := f.loginTOTP(c, client); err != nil {
f.printAndLogError("Cannot login: ", err)
return
}
}
@ -230,6 +250,15 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) {
f.Printf("Account %s was added successfully.\n", bold(user.Username))
}
func (f *frontendCLI) loginTOTP(c *ishell.Context, client *proton.Client) error {
code := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
if code == "" {
return errors.New("need two factor code")
}
return client.Auth2FA(context.Background(), proton.Auth2FAReq{TwoFactorCode: code})
}
func (f *frontendCLI) loginAccountHv(c *ishell.Context, loginName string, password string, keyPass []byte, hvDetails *proton.APIHVDetails) {
f.promptHvURL(hvDetails)
client, auth, err := f.bridge.LoginAuth(context.Background(), loginName, []byte(password), hvDetails)

View File

@ -1,171 +0,0 @@
//go:build linux || darwin
package cli
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/ProtonMail/go-proton-api"
"github.com/fxamacker/cbor/v2"
"github.com/keys-pub/go-libfido2"
)
func (f *frontendCLI) authWithHardwareKey(client *proton.Client, auth proton.Auth) error {
var fido2Device *libfido2.DeviceLocation
retryCount := 0
for {
locs, err := libfido2.DeviceLocations()
if err != nil {
f.printAndLogError("Cannot retrieve security key list: ", err)
}
if len(locs) == 0 {
fmt.Print("Please insert your security key and press enter to continue.")
f.ReadLine()
} else {
fido2Device = locs[0]
break
}
retryCount++
if retryCount >= 3 {
break
}
}
if fido2Device == nil {
return errors.New("no device found")
}
dev, err := libfido2.NewDevice(fido2Device.Path)
if err != nil {
return fmt.Errorf("cannot open security key: %w", err)
}
// Check if the key has a PIN set first
var pin string
info, err := dev.Info()
if err != nil {
return fmt.Errorf("cannot get device info: %w", err)
}
// Check if clientPin option is available and set
pinSupported := false
for _, option := range info.Options {
if option.Name == "clientPin" && option.Value == libfido2.True {
pinSupported = true
break
}
}
if pinSupported {
pin = f.readStringInAttempts("Security key PIN", f.ReadPassword, isNotEmpty)
if pin == "" {
return errors.New("PIN is required for this security key")
}
}
fmt.Println("Please touch your security key...")
authOptions, ok := auth.TwoFA.FIDO2.AuthenticationOptions.(map[string]interface{})
if !ok {
return errors.New("invalid authentication options format")
}
publicKey, ok := authOptions["publicKey"].(map[string]interface{})
if !ok {
return errors.New("no publicKey found in authentication options")
}
allowCredentials, ok := publicKey["allowCredentials"].([]interface{})
if !ok || len(allowCredentials) == 0 {
return errors.New("no allowed credentials found in authentication options")
}
var credentialIDs [][]byte //nolint:prealloc
for _, cred := range allowCredentials {
credMap, ok := cred.(map[string]interface{})
if !ok {
continue
}
idArray, ok := credMap["id"].([]interface{})
if !ok {
continue
}
credID := sliceAnyToByteArray(idArray)
credentialIDs = append(credentialIDs, credID)
}
if len(credentialIDs) == 0 {
return errors.New("no valid credential IDs found")
}
challengeArray, ok := publicKey["challenge"].([]interface{})
if !ok {
return errors.New("no challenge found in authentication options")
}
challenge := sliceAnyToByteArray(challengeArray)
rpID, ok := publicKey["rpId"].(string)
if !ok {
return errors.New("could not find rpId in authentication options")
}
clientDataJSON := map[string]interface{}{
"type": "webauthn.get",
"challenge": base64.URLEncoding.EncodeToString(challenge),
"origin": "https://" + rpID,
}
clientDataJSONBytes, err := json.Marshal(clientDataJSON)
if err != nil {
return fmt.Errorf("cannot marshal client data: %w", err)
}
clientDataHash := sha256.Sum256(clientDataJSONBytes)
assertion, err := dev.Assertion(
rpID,
clientDataHash[:],
credentialIDs,
pin,
&libfido2.AssertionOpts{UP: libfido2.True},
)
if err != nil {
return fmt.Errorf("FIDO2 assertion failed: %w", err)
}
// Decode CBOR to get raw authenticator data
var authData []byte
err = cbor.Unmarshal(assertion.AuthDataCBOR, &authData)
if err != nil {
return fmt.Errorf("failed to decode CBOR authenticator data: %w", err)
}
// Convert CredentialID bytes to array of integers
credentialIDInts := make([]int, len(assertion.CredentialID))
for i, b := range assertion.CredentialID {
credentialIDInts[i] = int(b)
}
fido2Req := proton.FIDO2Req{
AuthenticationOptions: auth.TwoFA.FIDO2.AuthenticationOptions,
ClientData: base64.StdEncoding.EncodeToString(clientDataJSONBytes),
AuthenticatorData: base64.StdEncoding.EncodeToString(authData),
Signature: base64.StdEncoding.EncodeToString(assertion.Sig),
CredentialID: credentialIDInts,
}
fmt.Println("Submitting FIDO2 authentication request.")
if err := client.Auth2FA(context.Background(), proton.Auth2FAReq{FIDO2: fido2Req}); err != nil {
return fmt.Errorf("FIDO2 authentication failed: %w", err)
}
fmt.Println("FIDO2 authentication succeeded")
return nil
}

View File

@ -1,126 +0,0 @@
//go:build windows
package cli
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"github.com/ProtonMail/go-proton-api"
"github.com/go-ctap/ctaphid/pkg/webauthntypes"
"github.com/go-ctap/winhello"
"github.com/go-ctap/winhello/hiddenwindow"
)
func (f *frontendCLI) authWithHardwareKey(client *proton.Client, auth proton.Auth) error {
// Windows Hello requires a window handle to work, as indicated by the docs of the lib.
wnd, err := hiddenwindow.New(slog.New(slog.DiscardHandler), "Proton Bridge Auth")
if err != nil {
return fmt.Errorf("failed to create window for Windows Hello: %w", err)
}
defer wnd.Close()
authOptions, ok := auth.TwoFA.FIDO2.AuthenticationOptions.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid authentication options format")
}
publicKey, ok := authOptions["publicKey"].(map[string]interface{})
if !ok {
return fmt.Errorf("no publicKey found in authentication options")
}
rpId, ok := publicKey["rpId"].(string)
if !ok {
return fmt.Errorf("could not find rpId in authentication options")
}
challengeArray, ok := publicKey["challenge"].([]interface{})
if !ok {
return fmt.Errorf("no challenge found in authentication options")
}
challenge := sliceAnyToByteArray(challengeArray)
allowCredentials, ok := publicKey["allowCredentials"].([]interface{})
if !ok || len(allowCredentials) == 0 {
return fmt.Errorf("no allowed credentials found in authentication options")
}
var credentialDescriptors []webauthntypes.PublicKeyCredentialDescriptor
for _, cred := range allowCredentials {
credMap, ok := cred.(map[string]interface{})
if !ok {
continue
}
idArray, ok := credMap["id"].([]interface{})
if !ok {
continue
}
credID := sliceAnyToByteArray(idArray)
credentialDescriptors = append(credentialDescriptors, webauthntypes.PublicKeyCredentialDescriptor{
ID: credID,
Type: webauthntypes.PublicKeyCredentialTypePublicKey,
})
}
if len(credentialDescriptors) == 0 {
return fmt.Errorf("no valid credential descriptors found")
}
clientDataJSON := map[string]interface{}{
"type": "webauthn.get",
"challenge": base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(challenge),
"origin": "https://" + rpId,
}
clientDataJSONBytes, err := json.Marshal(clientDataJSON)
if err != nil {
return fmt.Errorf("failed to marshal client data JSON: %w", err)
}
fmt.Println("Please use Windows Hello to authenticate.")
assertion, err := winhello.GetAssertion(
wnd.WindowHandle(),
rpId,
clientDataJSONBytes,
credentialDescriptors,
nil,
&winhello.AuthenticatorGetAssertionOptions{
AuthenticatorAttachment: winhello.WinHelloAuthenticatorAttachmentCrossPlatform,
UserVerificationRequirement: winhello.WinHelloUserVerificationRequirementDiscouraged,
CredentialHints: []webauthntypes.PublicKeyCredentialHint{
webauthntypes.PublicKeyCredentialHintSecurityKey,
},
},
)
if err != nil {
return fmt.Errorf("windows Hello assertion failed: %w", err)
}
authData := assertion.AuthDataRaw
credentialIDInts := make([]int, len(assertion.Credential.ID))
for i, b := range assertion.Credential.ID {
credentialIDInts[i] = int(b)
}
fido2Req := proton.FIDO2Req{
AuthenticationOptions: auth.TwoFA.FIDO2.AuthenticationOptions,
ClientData: base64.StdEncoding.EncodeToString(clientDataJSONBytes),
AuthenticatorData: base64.StdEncoding.EncodeToString(authData),
Signature: base64.StdEncoding.EncodeToString(assertion.Signature),
CredentialID: credentialIDInts,
}
fmt.Println("Submitting FIDO2 authentication request.")
if err := client.Auth2FA(context.Background(), proton.Auth2FAReq{FIDO2: fido2Req}); err != nil {
return fmt.Errorf("FIDO2 authentication failed: %w", err)
} else {
fmt.Println("FIDO2 authentication succeeded")
}
return nil
}

View File

@ -45,6 +45,11 @@ func (f *frontendCLI) yesNoQuestion(question string) bool {
return len(answer) > 0 // Empty is false.
}
func (f *frontendCLI) PromptAndWaitReturn(prompt string) {
f.Print(prompt, ". Press Enter/Return to continue: ")
f.ReadLine()
}
func (f *frontendCLI) readStringInAttempts(title string, readFunc func() string, isOK func(string) bool) (value string) {
f.Printf("%s: ", title)
value = readFunc()
@ -60,6 +65,10 @@ func (f *frontendCLI) readStringInAttempts(title string, readFunc func() string,
return
}
func (f *frontendCLI) ReadSecurityKeyPin() string {
return f.readStringInAttempts("Security key PIN", f.ReadPassword, isNotEmpty)
}
func (f *frontendCLI) printAndLogError(args ...interface{}) {
log.Error(args...)
f.Println(args...)
@ -110,15 +119,3 @@ Recommendation:
a different network to access Proton Mail.
`)
}
func sliceAnyToByteArray(s []any) []byte {
result := make([]byte, len(s))
for i, val := range s {
if intVal, ok := val.(float64); ok {
result[i] = byte(intVal)
} else {
panic("boom")
}
}
return result
}

File diff suppressed because it is too large Load Diff

View File

@ -63,8 +63,10 @@ service Bridge {
// login
rpc Login(LoginRequest) returns (google.protobuf.Empty);
rpc Login2FA(LoginRequest) returns (google.protobuf.Empty);
rpc LoginFido(LoginRequest) returns (google.protobuf.Empty);
rpc Login2Passwords(LoginRequest) returns (google.protobuf.Empty);
rpc LoginAbort(LoginAbortRequest) returns (google.protobuf.Empty);
rpc FidoAssertionAbort(LoginAbortRequest) returns (google.protobuf.Empty);
// update
rpc CheckUpdate(google.protobuf.Empty) returns (google.protobuf.Empty);
@ -313,6 +315,11 @@ message LoginEvent {
LoginFinishedEvent finished = 4;
LoginFinishedEvent alreadyLoggedIn = 5;
LoginHvRequestedEvent hvRequested = 6;
LoginFidoRequestedEvent fidoRequested = 7;
LoginTfaOrFidoRequestedEvent tfaOrFidoRequested = 8;
LoginFidoTouchEvent loginFidoTouchRequested = 9;
LoginFidoTouchEvent loginFidoTouchCompleted = 10;
LoginFidoPinRequired loginFidoPinRequired = 11;
}
}
@ -325,6 +332,9 @@ enum LoginErrorType {
TWO_PASSWORDS_ERROR = 5;
TWO_PASSWORDS_ABORT = 6;
HV_ERROR = 7;
FIDO_PIN_INVALID = 8;
FIDO_PIN_BLOCKED = 9;
FIDO_ERROR = 10;
}
message LoginErrorEvent {
@ -336,6 +346,22 @@ message LoginTfaRequestedEvent {
string username = 1;
}
message LoginFidoRequestedEvent {
string username = 1;
}
message LoginTfaOrFidoRequestedEvent {
string username = 1;
}
message LoginFidoTouchEvent {
string username = 1;
}
message LoginFidoPinRequired {
string username = 1;
}
message LoginTwoPasswordsRequestedEvent {
string username = 1;
}

View File

@ -17,8 +17,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v3.21.12
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.5
// source: bridge.proto
package grpc
@ -34,8 +34,8 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Bridge_CheckTokens_FullMethodName = "/grpc.Bridge/CheckTokens"
@ -69,8 +69,10 @@ const (
Bridge_RequestKnowledgeBaseSuggestions_FullMethodName = "/grpc.Bridge/RequestKnowledgeBaseSuggestions"
Bridge_Login_FullMethodName = "/grpc.Bridge/Login"
Bridge_Login2FA_FullMethodName = "/grpc.Bridge/Login2FA"
Bridge_LoginFido_FullMethodName = "/grpc.Bridge/LoginFido"
Bridge_Login2Passwords_FullMethodName = "/grpc.Bridge/Login2Passwords"
Bridge_LoginAbort_FullMethodName = "/grpc.Bridge/LoginAbort"
Bridge_FidoAssertionAbort_FullMethodName = "/grpc.Bridge/FidoAssertionAbort"
Bridge_CheckUpdate_FullMethodName = "/grpc.Bridge/CheckUpdate"
Bridge_InstallUpdate_FullMethodName = "/grpc.Bridge/InstallUpdate"
Bridge_SetIsAutomaticUpdateOn_FullMethodName = "/grpc.Bridge/SetIsAutomaticUpdateOn"
@ -104,6 +106,12 @@ const (
// BridgeClient is the client API for Bridge service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// **********************************************************************************************************************
//
// Service Declaration
//
// **********************************************************************************************************************≠––
type BridgeClient interface {
// App related calls
CheckTokens(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*wrapperspb.StringValue, error)
@ -138,8 +146,10 @@ type BridgeClient interface {
// login
Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
Login2FA(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
LoginFido(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
Login2Passwords(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
LoginAbort(ctx context.Context, in *LoginAbortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
FidoAssertionAbort(ctx context.Context, in *LoginAbortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// update
CheckUpdate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
InstallUpdate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
@ -172,7 +182,7 @@ type BridgeClient interface {
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)
RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StreamEvent], error)
StopEventStream(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Repair
TriggerRepair(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
@ -187,8 +197,9 @@ func NewBridgeClient(cc grpc.ClientConnInterface) BridgeClient {
}
func (c *bridgeClient) CheckTokens(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_CheckTokens_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_CheckTokens_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -196,8 +207,9 @@ func (c *bridgeClient) CheckTokens(ctx context.Context, in *wrapperspb.StringVal
}
func (c *bridgeClient) AddLogEntry(ctx context.Context, in *AddLogEntryRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_AddLogEntry_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_AddLogEntry_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -205,8 +217,9 @@ func (c *bridgeClient) AddLogEntry(ctx context.Context, in *AddLogEntryRequest,
}
func (c *bridgeClient) GuiReady(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GuiReadyResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GuiReadyResponse)
err := c.cc.Invoke(ctx, Bridge_GuiReady_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_GuiReady_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -214,8 +227,9 @@ func (c *bridgeClient) GuiReady(ctx context.Context, in *emptypb.Empty, opts ...
}
func (c *bridgeClient) Quit(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_Quit_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_Quit_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -223,8 +237,9 @@ func (c *bridgeClient) Quit(ctx context.Context, in *emptypb.Empty, opts ...grpc
}
func (c *bridgeClient) Restart(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_Restart_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_Restart_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -232,8 +247,9 @@ func (c *bridgeClient) Restart(ctx context.Context, in *emptypb.Empty, opts ...g
}
func (c *bridgeClient) ShowOnStartup(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_ShowOnStartup_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_ShowOnStartup_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -241,8 +257,9 @@ func (c *bridgeClient) ShowOnStartup(ctx context.Context, in *emptypb.Empty, opt
}
func (c *bridgeClient) SetIsAutostartOn(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetIsAutostartOn_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetIsAutostartOn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -250,8 +267,9 @@ func (c *bridgeClient) SetIsAutostartOn(ctx context.Context, in *wrapperspb.Bool
}
func (c *bridgeClient) IsAutostartOn(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsAutostartOn_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsAutostartOn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -259,8 +277,9 @@ func (c *bridgeClient) IsAutostartOn(ctx context.Context, in *emptypb.Empty, opt
}
func (c *bridgeClient) SetIsBetaEnabled(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetIsBetaEnabled_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetIsBetaEnabled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -268,8 +287,9 @@ func (c *bridgeClient) SetIsBetaEnabled(ctx context.Context, in *wrapperspb.Bool
}
func (c *bridgeClient) IsBetaEnabled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsBetaEnabled_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsBetaEnabled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -277,8 +297,9 @@ func (c *bridgeClient) IsBetaEnabled(ctx context.Context, in *emptypb.Empty, opt
}
func (c *bridgeClient) SetIsAllMailVisible(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetIsAllMailVisible_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetIsAllMailVisible_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -286,8 +307,9 @@ func (c *bridgeClient) SetIsAllMailVisible(ctx context.Context, in *wrapperspb.B
}
func (c *bridgeClient) IsAllMailVisible(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsAllMailVisible_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsAllMailVisible_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -295,8 +317,9 @@ func (c *bridgeClient) IsAllMailVisible(ctx context.Context, in *emptypb.Empty,
}
func (c *bridgeClient) SetIsTelemetryDisabled(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetIsTelemetryDisabled_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetIsTelemetryDisabled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -304,8 +327,9 @@ func (c *bridgeClient) SetIsTelemetryDisabled(ctx context.Context, in *wrappersp
}
func (c *bridgeClient) IsTelemetryDisabled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsTelemetryDisabled_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsTelemetryDisabled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -313,8 +337,9 @@ func (c *bridgeClient) IsTelemetryDisabled(ctx context.Context, in *emptypb.Empt
}
func (c *bridgeClient) GoOs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_GoOs_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_GoOs_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -322,8 +347,9 @@ func (c *bridgeClient) GoOs(ctx context.Context, in *emptypb.Empty, opts ...grpc
}
func (c *bridgeClient) TriggerReset(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_TriggerReset_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_TriggerReset_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -331,8 +357,9 @@ func (c *bridgeClient) TriggerReset(ctx context.Context, in *emptypb.Empty, opts
}
func (c *bridgeClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_Version_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_Version_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -340,8 +367,9 @@ func (c *bridgeClient) Version(ctx context.Context, in *emptypb.Empty, opts ...g
}
func (c *bridgeClient) LogsPath(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_LogsPath_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_LogsPath_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -349,8 +377,9 @@ func (c *bridgeClient) LogsPath(ctx context.Context, in *emptypb.Empty, opts ...
}
func (c *bridgeClient) LicensePath(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_LicensePath_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_LicensePath_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -358,8 +387,9 @@ func (c *bridgeClient) LicensePath(ctx context.Context, in *emptypb.Empty, opts
}
func (c *bridgeClient) ReleaseNotesPageLink(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_ReleaseNotesPageLink_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_ReleaseNotesPageLink_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -367,8 +397,9 @@ func (c *bridgeClient) ReleaseNotesPageLink(ctx context.Context, in *emptypb.Emp
}
func (c *bridgeClient) DependencyLicensesLink(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_DependencyLicensesLink_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_DependencyLicensesLink_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -376,8 +407,9 @@ func (c *bridgeClient) DependencyLicensesLink(ctx context.Context, in *emptypb.E
}
func (c *bridgeClient) LandingPageLink(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_LandingPageLink_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_LandingPageLink_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -385,8 +417,9 @@ func (c *bridgeClient) LandingPageLink(ctx context.Context, in *emptypb.Empty, o
}
func (c *bridgeClient) SetColorSchemeName(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetColorSchemeName_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetColorSchemeName_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -394,8 +427,9 @@ func (c *bridgeClient) SetColorSchemeName(ctx context.Context, in *wrapperspb.St
}
func (c *bridgeClient) ColorSchemeName(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_ColorSchemeName_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_ColorSchemeName_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -403,8 +437,9 @@ func (c *bridgeClient) ColorSchemeName(ctx context.Context, in *emptypb.Empty, o
}
func (c *bridgeClient) CurrentEmailClient(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_CurrentEmailClient_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_CurrentEmailClient_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -412,8 +447,9 @@ func (c *bridgeClient) CurrentEmailClient(ctx context.Context, in *emptypb.Empty
}
func (c *bridgeClient) ReportBug(ctx context.Context, in *ReportBugRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_ReportBug_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_ReportBug_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -421,8 +457,9 @@ func (c *bridgeClient) ReportBug(ctx context.Context, in *ReportBugRequest, opts
}
func (c *bridgeClient) ForceLauncher(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_ForceLauncher_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_ForceLauncher_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -430,8 +467,9 @@ func (c *bridgeClient) ForceLauncher(ctx context.Context, in *wrapperspb.StringV
}
func (c *bridgeClient) SetMainExecutable(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetMainExecutable_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetMainExecutable_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -439,8 +477,9 @@ func (c *bridgeClient) SetMainExecutable(ctx context.Context, in *wrapperspb.Str
}
func (c *bridgeClient) RequestKnowledgeBaseSuggestions(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_RequestKnowledgeBaseSuggestions_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_RequestKnowledgeBaseSuggestions_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -448,8 +487,9 @@ func (c *bridgeClient) RequestKnowledgeBaseSuggestions(ctx context.Context, in *
}
func (c *bridgeClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_Login_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_Login_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -457,8 +497,19 @@ func (c *bridgeClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc
}
func (c *bridgeClient) Login2FA(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_Login2FA_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_Login2FA_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) LoginFido(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_LoginFido_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -466,8 +517,9 @@ func (c *bridgeClient) Login2FA(ctx context.Context, in *LoginRequest, opts ...g
}
func (c *bridgeClient) Login2Passwords(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_Login2Passwords_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_Login2Passwords_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -475,8 +527,19 @@ func (c *bridgeClient) Login2Passwords(ctx context.Context, in *LoginRequest, op
}
func (c *bridgeClient) LoginAbort(ctx context.Context, in *LoginAbortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_LoginAbort_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_LoginAbort_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bridgeClient) FidoAssertionAbort(ctx context.Context, in *LoginAbortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_FidoAssertionAbort_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -484,8 +547,9 @@ func (c *bridgeClient) LoginAbort(ctx context.Context, in *LoginAbortRequest, op
}
func (c *bridgeClient) CheckUpdate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_CheckUpdate_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_CheckUpdate_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -493,8 +557,9 @@ func (c *bridgeClient) CheckUpdate(ctx context.Context, in *emptypb.Empty, opts
}
func (c *bridgeClient) InstallUpdate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_InstallUpdate_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_InstallUpdate_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -502,8 +567,9 @@ func (c *bridgeClient) InstallUpdate(ctx context.Context, in *emptypb.Empty, opt
}
func (c *bridgeClient) SetIsAutomaticUpdateOn(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetIsAutomaticUpdateOn_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetIsAutomaticUpdateOn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -511,8 +577,9 @@ func (c *bridgeClient) SetIsAutomaticUpdateOn(ctx context.Context, in *wrappersp
}
func (c *bridgeClient) IsAutomaticUpdateOn(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsAutomaticUpdateOn_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsAutomaticUpdateOn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -520,8 +587,9 @@ func (c *bridgeClient) IsAutomaticUpdateOn(ctx context.Context, in *emptypb.Empt
}
func (c *bridgeClient) DiskCachePath(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_DiskCachePath_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_DiskCachePath_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -529,8 +597,9 @@ func (c *bridgeClient) DiskCachePath(ctx context.Context, in *emptypb.Empty, opt
}
func (c *bridgeClient) SetDiskCachePath(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetDiskCachePath_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetDiskCachePath_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -538,8 +607,9 @@ func (c *bridgeClient) SetDiskCachePath(ctx context.Context, in *wrapperspb.Stri
}
func (c *bridgeClient) SetIsDoHEnabled(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetIsDoHEnabled_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetIsDoHEnabled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -547,8 +617,9 @@ func (c *bridgeClient) SetIsDoHEnabled(ctx context.Context, in *wrapperspb.BoolV
}
func (c *bridgeClient) IsDoHEnabled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsDoHEnabled_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsDoHEnabled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -556,8 +627,9 @@ func (c *bridgeClient) IsDoHEnabled(ctx context.Context, in *emptypb.Empty, opts
}
func (c *bridgeClient) MailServerSettings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ImapSmtpSettings, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ImapSmtpSettings)
err := c.cc.Invoke(ctx, Bridge_MailServerSettings_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_MailServerSettings_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -565,8 +637,9 @@ func (c *bridgeClient) MailServerSettings(ctx context.Context, in *emptypb.Empty
}
func (c *bridgeClient) SetMailServerSettings(ctx context.Context, in *ImapSmtpSettings, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetMailServerSettings_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetMailServerSettings_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -574,8 +647,9 @@ func (c *bridgeClient) SetMailServerSettings(ctx context.Context, in *ImapSmtpSe
}
func (c *bridgeClient) Hostname(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_Hostname_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_Hostname_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -583,8 +657,9 @@ func (c *bridgeClient) Hostname(ctx context.Context, in *emptypb.Empty, opts ...
}
func (c *bridgeClient) IsPortFree(ctx context.Context, in *wrapperspb.Int32Value, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsPortFree_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsPortFree_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -592,8 +667,9 @@ func (c *bridgeClient) IsPortFree(ctx context.Context, in *wrapperspb.Int32Value
}
func (c *bridgeClient) AvailableKeychains(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*AvailableKeychainsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AvailableKeychainsResponse)
err := c.cc.Invoke(ctx, Bridge_AvailableKeychains_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_AvailableKeychains_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -601,8 +677,9 @@ func (c *bridgeClient) AvailableKeychains(ctx context.Context, in *emptypb.Empty
}
func (c *bridgeClient) SetCurrentKeychain(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetCurrentKeychain_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetCurrentKeychain_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -610,8 +687,9 @@ func (c *bridgeClient) SetCurrentKeychain(ctx context.Context, in *wrapperspb.St
}
func (c *bridgeClient) CurrentKeychain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.StringValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.StringValue)
err := c.cc.Invoke(ctx, Bridge_CurrentKeychain_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_CurrentKeychain_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -619,8 +697,9 @@ func (c *bridgeClient) CurrentKeychain(ctx context.Context, in *emptypb.Empty, o
}
func (c *bridgeClient) GetUserList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*UserListResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UserListResponse)
err := c.cc.Invoke(ctx, Bridge_GetUserList_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_GetUserList_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -628,8 +707,9 @@ func (c *bridgeClient) GetUserList(ctx context.Context, in *emptypb.Empty, opts
}
func (c *bridgeClient) GetUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*User, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(User)
err := c.cc.Invoke(ctx, Bridge_GetUser_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_GetUser_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -637,8 +717,9 @@ func (c *bridgeClient) GetUser(ctx context.Context, in *wrapperspb.StringValue,
}
func (c *bridgeClient) SetUserSplitMode(ctx context.Context, in *UserSplitModeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SetUserSplitMode_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SetUserSplitMode_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -646,8 +727,9 @@ func (c *bridgeClient) SetUserSplitMode(ctx context.Context, in *UserSplitModeRe
}
func (c *bridgeClient) SendBadEventUserFeedback(ctx context.Context, in *UserBadEventFeedbackRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_SendBadEventUserFeedback_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_SendBadEventUserFeedback_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -655,8 +737,9 @@ func (c *bridgeClient) SendBadEventUserFeedback(ctx context.Context, in *UserBad
}
func (c *bridgeClient) LogoutUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_LogoutUser_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_LogoutUser_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -664,8 +747,9 @@ func (c *bridgeClient) LogoutUser(ctx context.Context, in *wrapperspb.StringValu
}
func (c *bridgeClient) RemoveUser(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_RemoveUser_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_RemoveUser_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -673,8 +757,9 @@ func (c *bridgeClient) RemoveUser(ctx context.Context, in *wrapperspb.StringValu
}
func (c *bridgeClient) ConfigureUserAppleMail(ctx context.Context, in *ConfigureAppleMailRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_ConfigureUserAppleMail_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_ConfigureUserAppleMail_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -682,8 +767,9 @@ func (c *bridgeClient) ConfigureUserAppleMail(ctx context.Context, in *Configure
}
func (c *bridgeClient) IsTLSCertificateInstalled(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, Bridge_IsTLSCertificateInstalled_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_IsTLSCertificateInstalled_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -691,8 +777,9 @@ func (c *bridgeClient) IsTLSCertificateInstalled(ctx context.Context, in *emptyp
}
func (c *bridgeClient) InstallTLSCertificate(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_InstallTLSCertificate_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_InstallTLSCertificate_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -700,20 +787,22 @@ func (c *bridgeClient) InstallTLSCertificate(ctx context.Context, in *emptypb.Em
}
func (c *bridgeClient) ExportTLSCertificates(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_ExportTLSCertificates_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_ExportTLSCertificates_FullMethodName, in, out, cOpts...)
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], Bridge_RunEventStream_FullMethodName, opts...)
func (c *bridgeClient) RunEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StreamEvent], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Bridge_ServiceDesc.Streams[0], Bridge_RunEventStream_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &bridgeRunEventStreamClient{stream}
x := &grpc.GenericClientStream[EventStreamRequest, StreamEvent]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
@ -723,26 +812,13 @@ func (c *bridgeClient) RunEventStream(ctx context.Context, in *EventStreamReques
return x, nil
}
type Bridge_RunEventStreamClient interface {
Recv() (*StreamEvent, error)
grpc.ClientStream
}
type bridgeRunEventStreamClient struct {
grpc.ClientStream
}
func (x *bridgeRunEventStreamClient) Recv() (*StreamEvent, error) {
m := new(StreamEvent)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Bridge_RunEventStreamClient = grpc.ServerStreamingClient[StreamEvent]
func (c *bridgeClient) StopEventStream(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_StopEventStream_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_StopEventStream_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -750,8 +826,9 @@ func (c *bridgeClient) StopEventStream(ctx context.Context, in *emptypb.Empty, o
}
func (c *bridgeClient) TriggerRepair(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Bridge_TriggerRepair_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, Bridge_TriggerRepair_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@ -760,7 +837,13 @@ func (c *bridgeClient) TriggerRepair(ctx context.Context, in *emptypb.Empty, opt
// BridgeServer is the server API for Bridge service.
// All implementations must embed UnimplementedBridgeServer
// for forward compatibility
// for forward compatibility.
//
// **********************************************************************************************************************
//
// Service Declaration
//
// **********************************************************************************************************************≠––
type BridgeServer interface {
// App related calls
CheckTokens(context.Context, *wrapperspb.StringValue) (*wrapperspb.StringValue, error)
@ -795,8 +878,10 @@ type BridgeServer interface {
// login
Login(context.Context, *LoginRequest) (*emptypb.Empty, error)
Login2FA(context.Context, *LoginRequest) (*emptypb.Empty, error)
LoginFido(context.Context, *LoginRequest) (*emptypb.Empty, error)
Login2Passwords(context.Context, *LoginRequest) (*emptypb.Empty, error)
LoginAbort(context.Context, *LoginAbortRequest) (*emptypb.Empty, error)
FidoAssertionAbort(context.Context, *LoginAbortRequest) (*emptypb.Empty, error)
// update
CheckUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
InstallUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
@ -829,16 +914,19 @@ type BridgeServer interface {
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
RunEventStream(*EventStreamRequest, grpc.ServerStreamingServer[StreamEvent]) error
StopEventStream(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
// Repair
TriggerRepair(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
mustEmbedUnimplementedBridgeServer()
}
// UnimplementedBridgeServer must be embedded to have forward compatible implementations.
type UnimplementedBridgeServer struct {
}
// UnimplementedBridgeServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedBridgeServer struct{}
func (UnimplementedBridgeServer) CheckTokens(context.Context, *wrapperspb.StringValue) (*wrapperspb.StringValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckTokens not implemented")
@ -933,12 +1021,18 @@ func (UnimplementedBridgeServer) Login(context.Context, *LoginRequest) (*emptypb
func (UnimplementedBridgeServer) Login2FA(context.Context, *LoginRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Login2FA not implemented")
}
func (UnimplementedBridgeServer) LoginFido(context.Context, *LoginRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoginFido not implemented")
}
func (UnimplementedBridgeServer) Login2Passwords(context.Context, *LoginRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Login2Passwords not implemented")
}
func (UnimplementedBridgeServer) LoginAbort(context.Context, *LoginAbortRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method LoginAbort not implemented")
}
func (UnimplementedBridgeServer) FidoAssertionAbort(context.Context, *LoginAbortRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method FidoAssertionAbort not implemented")
}
func (UnimplementedBridgeServer) CheckUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckUpdate not implemented")
}
@ -1014,7 +1108,7 @@ func (UnimplementedBridgeServer) InstallTLSCertificate(context.Context, *emptypb
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 {
func (UnimplementedBridgeServer) RunEventStream(*EventStreamRequest, grpc.ServerStreamingServer[StreamEvent]) error {
return status.Errorf(codes.Unimplemented, "method RunEventStream not implemented")
}
func (UnimplementedBridgeServer) StopEventStream(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
@ -1024,6 +1118,7 @@ func (UnimplementedBridgeServer) TriggerRepair(context.Context, *emptypb.Empty)
return nil, status.Errorf(codes.Unimplemented, "method TriggerRepair not implemented")
}
func (UnimplementedBridgeServer) mustEmbedUnimplementedBridgeServer() {}
func (UnimplementedBridgeServer) testEmbeddedByValue() {}
// UnsafeBridgeServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BridgeServer will
@ -1033,6 +1128,13 @@ type UnsafeBridgeServer interface {
}
func RegisterBridgeServer(s grpc.ServiceRegistrar, srv BridgeServer) {
// If the following call pancis, it indicates UnimplementedBridgeServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Bridge_ServiceDesc, srv)
}
@ -1594,6 +1696,24 @@ func _Bridge_Login2FA_Handler(srv interface{}, ctx context.Context, dec func(int
return interceptor(ctx, in, info, handler)
}
func _Bridge_LoginFido_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoginRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).LoginFido(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Bridge_LoginFido_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).LoginFido(ctx, req.(*LoginRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_Login2Passwords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoginRequest)
if err := dec(in); err != nil {
@ -1630,6 +1750,24 @@ func _Bridge_LoginAbort_Handler(srv interface{}, ctx context.Context, dec func(i
return interceptor(ctx, in, info, handler)
}
func _Bridge_FidoAssertionAbort_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoginAbortRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BridgeServer).FidoAssertionAbort(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Bridge_FidoAssertionAbort_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BridgeServer).FidoAssertionAbort(ctx, req.(*LoginAbortRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Bridge_CheckUpdate_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 {
@ -2085,21 +2223,11 @@ func _Bridge_RunEventStream_Handler(srv interface{}, stream grpc.ServerStream) e
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(BridgeServer).RunEventStream(m, &bridgeRunEventStreamServer{stream})
return srv.(BridgeServer).RunEventStream(m, &grpc.GenericServerStream[EventStreamRequest, StreamEvent]{ServerStream: stream})
}
type Bridge_RunEventStreamServer interface {
Send(*StreamEvent) error
grpc.ServerStream
}
type bridgeRunEventStreamServer struct {
grpc.ServerStream
}
func (x *bridgeRunEventStreamServer) Send(m *StreamEvent) error {
return x.ServerStream.SendMsg(m)
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Bridge_RunEventStreamServer = grpc.ServerStreamingServer[StreamEvent]
func _Bridge_StopEventStream_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
@ -2268,6 +2396,10 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
MethodName: "Login2FA",
Handler: _Bridge_Login2FA_Handler,
},
{
MethodName: "LoginFido",
Handler: _Bridge_LoginFido_Handler,
},
{
MethodName: "Login2Passwords",
Handler: _Bridge_Login2Passwords_Handler,
@ -2276,6 +2408,10 @@ var Bridge_ServiceDesc = grpc.ServiceDesc{
MethodName: "LoginAbort",
Handler: _Bridge_LoginAbort_Handler,
},
{
MethodName: "FidoAssertionAbort",
Handler: _Bridge_FidoAssertionAbort_Handler,
},
{
MethodName: "CheckUpdate",
Handler: _Bridge_CheckUpdate_Handler,

View File

@ -89,6 +89,26 @@ func NewLoginTfaRequestedEvent(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TfaRequested{TfaRequested: &LoginTfaRequestedEvent{Username: username}}})
}
func NewLoginFidoRequestedEvent(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_FidoRequested{FidoRequested: &LoginFidoRequestedEvent{Username: username}}})
}
func NewLoginTfaOrFidoRequestedEvent(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TfaOrFidoRequested{TfaOrFidoRequested: &LoginTfaOrFidoRequestedEvent{Username: username}}})
}
func NewLoginFidoTouchRequested(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_LoginFidoTouchRequested{LoginFidoTouchRequested: &LoginFidoTouchEvent{Username: username}}})
}
func NewLoginFidoTouchCompleted(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_LoginFidoTouchCompleted{LoginFidoTouchCompleted: &LoginFidoTouchEvent{Username: username}}})
}
func NewLoginFidoPinRequired(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_LoginFidoPinRequired{LoginFidoPinRequired: &LoginFidoPinRequired{Username: username}}})
}
func NewLoginTwoPasswordsRequestedEvent(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{TwoPasswordRequested: &LoginTwoPasswordsRequestedEvent{Username: username}}})
}

View File

@ -0,0 +1,111 @@
// Copyright (c) 2025 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/>.
//go:build linux || darwin
package grpc
import (
"context"
"errors"
"fmt"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/fido"
"github.com/keys-pub/go-libfido2"
"google.golang.org/protobuf/types/known/emptypb"
)
func (s *Service) LoginFido(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Debug("LoginFido")
go func() {
defer async.HandlePanic(s.panicHandler)
fidoCtx, cancelFido := context.WithCancel(context.Background())
s.fidoManager.SetCancel(cancelFido)
defer s.fidoManager.Clear()
if s.auth.UID == "" || s.authClient == nil {
s.log.Errorf("Login FIDO: authentication incomplete %s %p", s.auth.UID, s.authClient)
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_ERROR, "Missing authentication, try again."))
s.loginClean()
return
}
pinSupported, err := fido.IsPinSupported()
if err != nil {
s.log.WithError(err).Warn("could not determine security key PIN requirements")
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_ERROR, fmt.Sprintf("Could not obtain security key pin requirements: %s", err)))
s.loginClean()
return
}
if pinSupported && len(login.Password) == 0 {
_ = s.SendEvent(NewLoginFidoPinRequired(login.Username))
return
}
pin, err := base64Decode(login.Password)
if err != nil {
s.log.WithError(err).Error("cannot decode security key device pin")
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_PIN_INVALID, "Could not decode security key PIN"))
return
}
touchCh := make(chan struct{})
touchConfirmCh := make(chan struct{})
defer func() {
close(touchCh)
close(touchConfirmCh)
}()
go func() {
if _, ok := <-touchCh; ok {
_ = s.SendEvent(NewLoginFidoTouchRequested(login.Username))
if _, ok := <-touchConfirmCh; ok {
_ = s.SendEvent(NewLoginFidoTouchCompleted(login.Username))
}
}
}()
if err := fido.AuthWithHardwareKeyGUI(fidoCtx, s.authClient, s.auth, touchCh, touchConfirmCh, string(pin)); err != nil {
s.log.WithError(err).Warn("Login FIDO: failed")
switch {
case errors.Is(err, libfido2.ErrPinAuthBlocked):
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_PIN_BLOCKED, "Security key PIN code is blocked"))
case errors.Is(err, libfido2.ErrPinInvalid):
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_PIN_INVALID, "Security key PIN code is incorrect"))
case errors.Is(err, fido.ErrAssertionCancelled): // User cancellation, they can click re-auth again.
return
default:
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_ERROR, fmt.Sprintf("Security key authentication failed: %s", err)))
s.loginClean()
}
return
}
s.finishLogin()
}()
return &emptypb.Empty{}, nil
}

View File

@ -0,0 +1,55 @@
// Copyright (c) 2025 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 fido
import (
"context"
"sync"
)
type Manager struct {
mu sync.Mutex
cancel context.CancelFunc
}
func (fm *Manager) withLock(fn func()) {
fm.mu.Lock()
defer fm.mu.Unlock()
fn()
}
func (fm *Manager) SetCancel(cancel context.CancelFunc) {
fm.withLock(func() {
fm.cancel = cancel
})
}
func (fm *Manager) Cancel() {
fm.withLock(func() {
if fm.cancel != nil {
fm.cancel()
fm.cancel = nil
}
})
}
func (fm *Manager) Clear() {
fm.withLock(func() {
fm.cancel = nil
})
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2025 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/>.
//go:build windows
package grpc
import (
"context"
"fmt"
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/proton-bridge/v3/internal/fido"
"google.golang.org/protobuf/types/known/emptypb"
)
func (s *Service) LoginFido(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Debug("LoginFido")
go func() {
defer async.HandlePanic(s.panicHandler)
if s.auth.UID == "" || s.authClient == nil {
s.log.Errorf("Login FIDO: authentication incomplete %s %p", s.auth.UID, s.authClient)
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, "Missing authentication, try again."))
s.loginClean()
return
}
if err := fido.AuthWithHardwareKeyGUI(s.authClient, s.auth, false); err != nil {
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_ERROR, fmt.Sprintf("Security key authentication failed: %s", err)))
s.loginClean()
return
}
s.finishLogin()
}()
return &emptypb.Empty{}, nil
}

View File

@ -38,6 +38,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/frontend/grpc/fido"
"github.com/ProtonMail/proton-bridge/v3/internal/hv"
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
@ -102,6 +103,8 @@ type Service struct { // nolint:structcheck
hvDetails *proton.APIHVDetails
useHvDetails bool
fidoManager *fido.Manager
}
// NewService returns a new instance of the service.
@ -187,6 +190,8 @@ func NewService(
parentPID: parentPID,
parentPIDDoneCh: make(chan struct{}),
showOnStartup: showOnStartup,
fidoManager: &fido.Manager{},
}
// Initializing.Done is only called sync.Once. Please keep the increment set to 1

View File

@ -35,6 +35,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/platform"
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
"github.com/ProtonMail/proton-bridge/v3/internal/service"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/pkg/ports"
"github.com/sirupsen/logrus"
@ -500,10 +501,27 @@ func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty,
s.authClient = client
s.auth = auth
u2fLoginEnabled := s.bridge.GetFeatureFlagValue(unleash.InboxBridgeU2FLoginEnabled)
switch {
case auth.TwoFA.Enabled&proton.HasTOTP != 0:
case auth.TwoFA.Enabled == proton.HasTOTP:
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
case auth.TwoFA.Enabled == proton.HasFIDO2:
if !u2fLoginEnabled {
// Such a case may only occur to internal users.
_ = s.SendEvent(NewLoginError(LoginErrorType_FIDO_ERROR, "Security key authentication required but not enabled in server configuration."))
return
}
_ = s.SendEvent(NewLoginFidoRequestedEvent(login.Username))
case auth.TwoFA.Enabled == proton.HasFIDO2AndTOTP:
if u2fLoginEnabled {
_ = s.SendEvent(NewLoginTfaOrFidoRequestedEvent(login.Username))
} else {
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
}
case auth.PasswordMode == proton.TwoPasswordMode:
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent(login.Username))
@ -582,6 +600,17 @@ func (s *Service) Login2Passwords(_ context.Context, login *LoginRequest) (*empt
return &emptypb.Empty{}, nil
}
func (s *Service) FidoAssertionAbort(_ context.Context, loginAbort *LoginAbortRequest) (*emptypb.Empty, error) {
s.log.WithField("username", loginAbort.Username).Debug("FidoAssertionAbort")
go func() {
defer async.HandlePanic(s.panicHandler)
s.fidoManager.Cancel()
}()
return &emptypb.Empty{}, nil
}
func (s *Service) LoginAbort(_ context.Context, loginAbort *LoginAbortRequest) (*emptypb.Empty, error) {
s.log.WithField("username", loginAbort.Username).Debug("LoginAbort")

View File

@ -47,6 +47,7 @@ const (
InternalLabelConflictResolverDisabled = "InboxBridgeUnexpectedFoldersLabelsStartupFixupDisabled"
InternalLabelConflictNonEmptyMailboxDeletion = "InboxBridgeUnknownNonEmptyMailboxDeletion"
LinuxVaultPreferredKeychainNotAvailableRetryDisabled = "InboxBridgeLinuxVaultPreferredKeychainNotAvailableRetryDisabled"
InboxBridgeU2FLoginEnabled = "InboxBridgeU2FLogin"
)
type FeatureFlagValueProvider interface {

View File

@ -306,8 +306,7 @@ func (t *testCtx) initFrontendClient() error {
target = fmt.Sprintf("%v:%d", constants.Host, cfg.Port)
}
conn, err := grpc.DialContext(
context.Background(),
conn, err := grpc.NewClient(
target,
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{RootCAs: cp, ServerName: "127.0.0.1"})),
grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {