mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2026-02-04 00:08:33 +00:00
feat(BRIDGE-424): FIDO2 GUI support.
This commit is contained in:
267
internal/fido/fido.go
Normal file
267
internal/fido/fido.go
Normal 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)
|
||||
}
|
||||
94
internal/fido/fido_windows.go
Normal file
94
internal/fido/fido_windows.go
Normal 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
23
internal/fido/types.go
Normal 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
111
internal/fido/utils.go
Normal 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
|
||||
}
|
||||
@ -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()),
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 |
@ -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.
|
||||
//****************************************************************************************************************************************************
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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}}})
|
||||
}
|
||||
|
||||
111
internal/frontend/grpc/fido.go
Normal file
111
internal/frontend/grpc/fido.go
Normal 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
|
||||
}
|
||||
55
internal/frontend/grpc/fido/manager.go
Normal file
55
internal/frontend/grpc/fido/manager.go
Normal 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
|
||||
})
|
||||
}
|
||||
53
internal/frontend/grpc/fido_windows.go
Normal file
53
internal/frontend/grpc/fido_windows.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -47,6 +47,7 @@ const (
|
||||
InternalLabelConflictResolverDisabled = "InboxBridgeUnexpectedFoldersLabelsStartupFixupDisabled"
|
||||
InternalLabelConflictNonEmptyMailboxDeletion = "InboxBridgeUnknownNonEmptyMailboxDeletion"
|
||||
LinuxVaultPreferredKeychainNotAvailableRetryDisabled = "InboxBridgeLinuxVaultPreferredKeychainNotAvailableRetryDisabled"
|
||||
InboxBridgeU2FLoginEnabled = "InboxBridgeU2FLogin"
|
||||
)
|
||||
|
||||
type FeatureFlagValueProvider interface {
|
||||
|
||||
Reference in New Issue
Block a user