From ce7a6f1971d11a87aa02b922ee8ffeeeafd9f1b3 Mon Sep 17 00:00:00 2001 From: Stefan Jacobi Date: Tue, 23 Jan 2024 12:14:48 +0100 Subject: [PATCH 01/24] improve(passkeys): improve passkey naming * add cmd flag for loading aaguid-map file * add aaguid mapper for better passkey naming * bundle aaguid file in docker container * refactor file loading to reuse in multiple occasions Closes: #1027 --- backend/Dockerfile | 9 +- backend/cmd/serve/all.go | 9 +- backend/cmd/serve/public.go | 9 +- backend/config/config.go | 19 +++- backend/dto/intern/WebauthnCredential.go | 4 +- backend/handler/public_router.go | 5 +- backend/handler/webauthn.go | 7 +- backend/mapper/aaguid_mapper.go | 48 +++++++++++ backend/server/server.go | 5 +- deploy/docker-compose/aaguid.json | 105 +++++++++++++++++++++++ deploy/docker-compose/quickstart.yaml | 2 +- 11 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 backend/mapper/aaguid_mapper.go create mode 100644 deploy/docker-compose/aaguid.json diff --git a/backend/Dockerfile b/backend/Dockerfile index d738afe28..1cbbe8bf7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,5 @@ # Build the hanko binary -FROM --platform=$BUILDPLATFORM golang:1.20 as builder +FROM --platform=$BUILDPLATFORM golang:1.20 AS builder ARG TARGETARCH @@ -28,16 +28,21 @@ COPY build_info build_info/ COPY middleware middleware/ COPY template template/ COPY utils utils/ +COPY mapper mapper/ + +# Load AAGUID map +RUN wget --quiet https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/main/aaguid.json -O aaguid.json # Build RUN go generate ./... -RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -a -o hanko main.go +RUN CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" go build -a -o hanko main.go # Use distroless as minimal base image to package hanko binary # See https://github.com/GoogleContainerTools/distroless for details FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/hanko . +COPY --from=builder /workspace/aaguid.json /etc/config/ USER 65532:65532 ENTRYPOINT ["/hanko"] diff --git a/backend/cmd/serve/all.go b/backend/cmd/serve/all.go index da30b2275..0d058b699 100644 --- a/backend/cmd/serve/all.go +++ b/backend/cmd/serve/all.go @@ -7,6 +7,7 @@ import ( "github.com/labstack/echo-contrib/echoprometheus" "github.com/spf13/cobra" "github.com/teamhanko/hanko/backend/config" + "github.com/teamhanko/hanko/backend/mapper" "github.com/teamhanko/hanko/backend/persistence" "github.com/teamhanko/hanko/backend/server" "log" @@ -15,7 +16,8 @@ import ( func NewServeAllCommand() *cobra.Command { var ( - configFile string + configFile string + aaguidMapFile string ) cmd := &cobra.Command{ @@ -28,6 +30,8 @@ func NewServeAllCommand() *cobra.Command { log.Fatal(err) } + aaguidMap := mapper.LoadAaguidMap(&aaguidMapFile) + persister, err := persistence.New(cfg.Database) if err != nil { log.Fatal(err) @@ -37,7 +41,7 @@ func NewServeAllCommand() *cobra.Command { prometheus := echoprometheus.NewMiddleware("hanko") - go server.StartPublic(cfg, &wg, persister, prometheus) + go server.StartPublic(cfg, &wg, persister, prometheus, aaguidMap) go server.StartAdmin(cfg, &wg, persister, prometheus) wg.Wait() @@ -45,6 +49,7 @@ func NewServeAllCommand() *cobra.Command { } cmd.Flags().StringVar(&configFile, "config", config.DefaultConfigFilePath, "config file") + cmd.Flags().StringVar(&aaguidMapFile, "aaguid-map", "", "aaguid map file") return cmd } diff --git a/backend/cmd/serve/public.go b/backend/cmd/serve/public.go index 72de57bc9..5695901cc 100644 --- a/backend/cmd/serve/public.go +++ b/backend/cmd/serve/public.go @@ -6,6 +6,7 @@ package serve import ( "github.com/spf13/cobra" "github.com/teamhanko/hanko/backend/config" + "github.com/teamhanko/hanko/backend/mapper" "github.com/teamhanko/hanko/backend/persistence" "github.com/teamhanko/hanko/backend/server" "log" @@ -14,7 +15,8 @@ import ( func NewServePublicCommand() *cobra.Command { var ( - configFile string + configFile string + aaguidMapFile string ) cmd := &cobra.Command{ @@ -27,6 +29,8 @@ func NewServePublicCommand() *cobra.Command { log.Fatal(err) } + aaguidMap := mapper.LoadAaguidMap(&aaguidMapFile) + persister, err := persistence.New(cfg.Database) if err != nil { log.Fatal(err) @@ -34,13 +38,14 @@ func NewServePublicCommand() *cobra.Command { var wg sync.WaitGroup wg.Add(1) - go server.StartPublic(cfg, &wg, persister, nil) + go server.StartPublic(cfg, &wg, persister, nil, aaguidMap) wg.Wait() }, } cmd.Flags().StringVar(&configFile, "config", config.DefaultConfigFilePath, "config file") + cmd.Flags().StringVar(&aaguidMapFile, "aaguid-map", "", "config file") return cmd } diff --git a/backend/config/config.go b/backend/config/config.go index 453b37962..8017cfbac 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -39,14 +39,27 @@ var ( DefaultConfigFilePath = "./config/config.yaml" ) -func Load(cfgFile *string) (*Config, error) { +func LoadFile(filePath *string, pa koanf.Parser) (*koanf.Koanf, error) { k := koanf.New(".") - var err error + + if filePath == nil || *filePath == "" { + return nil, nil + } + + if err := k.Load(file.Provider(*filePath), pa); err != nil { + return nil, fmt.Errorf("failed to load file from '%s': %w", *filePath, err) + } + + return k, nil +} + +func Load(cfgFile *string) (*Config, error) { if cfgFile == nil || *cfgFile == "" { *cfgFile = DefaultConfigFilePath } - if err = k.Load(file.Provider(*cfgFile), yaml.Parser()); err != nil { + k, err := LoadFile(cfgFile, yaml.Parser()) + if err != nil { if *cfgFile != DefaultConfigFilePath { return nil, fmt.Errorf("failed to load config from: %s: %w", *cfgFile, err) } diff --git a/backend/dto/intern/WebauthnCredential.go b/backend/dto/intern/WebauthnCredential.go index 9a25b7c51..ea1f035bd 100644 --- a/backend/dto/intern/WebauthnCredential.go +++ b/backend/dto/intern/WebauthnCredential.go @@ -5,17 +5,19 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/webauthn" "github.com/gofrs/uuid" + "github.com/teamhanko/hanko/backend/mapper" "github.com/teamhanko/hanko/backend/persistence/models" "time" ) -func WebauthnCredentialToModel(credential *webauthn.Credential, userId uuid.UUID, backupEligible bool, backupState bool) *models.WebauthnCredential { +func WebauthnCredentialToModel(credential *webauthn.Credential, userId uuid.UUID, backupEligible bool, backupState bool, aaguidMap mapper.AaguidMap) *models.WebauthnCredential { now := time.Now().UTC() aaguid, _ := uuid.FromBytes(credential.Authenticator.AAGUID) credentialID := base64.RawURLEncoding.EncodeToString(credential.ID) c := &models.WebauthnCredential{ ID: credentialID, + Name: aaguidMap.GetNameForAaguid(aaguid), UserId: userId, PublicKey: base64.RawURLEncoding.EncodeToString(credential.PublicKey), AttestationType: credential.AttestationType, diff --git a/backend/handler/public_router.go b/backend/handler/public_router.go index 051a663f7..8c8ff90a0 100644 --- a/backend/handler/public_router.go +++ b/backend/handler/public_router.go @@ -11,13 +11,14 @@ import ( "github.com/teamhanko/hanko/backend/dto" "github.com/teamhanko/hanko/backend/ee/saml" "github.com/teamhanko/hanko/backend/mail" + "github.com/teamhanko/hanko/backend/mapper" hankoMiddleware "github.com/teamhanko/hanko/backend/middleware" "github.com/teamhanko/hanko/backend/persistence" "github.com/teamhanko/hanko/backend/session" "github.com/teamhanko/hanko/backend/template" ) -func NewPublicRouter(cfg *config.Config, persister persistence.Persister, prometheus echo.MiddlewareFunc) *echo.Echo { +func NewPublicRouter(cfg *config.Config, persister persistence.Persister, prometheus echo.MiddlewareFunc, aaguidMap mapper.AaguidMap) *echo.Echo { e := echo.New() e.Renderer = template.NewTemplateRenderer() e.HideBanner = true @@ -102,7 +103,7 @@ func NewPublicRouter(cfg *config.Config, persister persistence.Persister, promet } healthHandler := NewHealthHandler() - webauthnHandler, err := NewWebauthnHandler(cfg, persister, sessionManager, auditLogger) + webauthnHandler, err := NewWebauthnHandler(cfg, persister, sessionManager, auditLogger, aaguidMap) if err != nil { panic(fmt.Errorf("failed to create public webauthn handler: %w", err)) } diff --git a/backend/handler/webauthn.go b/backend/handler/webauthn.go index 2d331a68f..8542c6fb9 100644 --- a/backend/handler/webauthn.go +++ b/backend/handler/webauthn.go @@ -14,6 +14,7 @@ import ( "github.com/teamhanko/hanko/backend/config" "github.com/teamhanko/hanko/backend/dto" "github.com/teamhanko/hanko/backend/dto/intern" + "github.com/teamhanko/hanko/backend/mapper" "github.com/teamhanko/hanko/backend/persistence" "github.com/teamhanko/hanko/backend/persistence/models" "github.com/teamhanko/hanko/backend/session" @@ -28,10 +29,11 @@ type WebauthnHandler struct { sessionManager session.Manager cfg *config.Config auditLogger auditlog.Logger + aaguidMap mapper.AaguidMap } // NewWebauthnHandler creates a new handler which handles all webauthn related routes -func NewWebauthnHandler(cfg *config.Config, persister persistence.Persister, sessionManager session.Manager, auditLogger auditlog.Logger) (*WebauthnHandler, error) { +func NewWebauthnHandler(cfg *config.Config, persister persistence.Persister, sessionManager session.Manager, auditLogger auditlog.Logger, aaguidMap mapper.AaguidMap) (*WebauthnHandler, error) { f := false wa, err := webauthn.New(&webauthn.Config{ RPDisplayName: cfg.Webauthn.RelyingParty.DisplayName, @@ -66,6 +68,7 @@ func NewWebauthnHandler(cfg *config.Config, persister persistence.Persister, ses sessionManager: sessionManager, cfg: cfg, auditLogger: auditLogger, + aaguidMap: aaguidMap, }, nil } @@ -196,7 +199,7 @@ func (h *WebauthnHandler) FinishRegistration(c echo.Context) error { backupEligible := request.Response.AttestationObject.AuthData.Flags.HasBackupEligible() backupState := request.Response.AttestationObject.AuthData.Flags.HasBackupState() - model := intern.WebauthnCredentialToModel(credential, sessionData.UserId, backupEligible, backupState) + model := intern.WebauthnCredentialToModel(credential, sessionData.UserId, backupEligible, backupState, h.aaguidMap) err = h.persister.GetWebauthnCredentialPersisterWithConnection(tx).Create(*model) if err != nil { return fmt.Errorf("failed to store webauthn credential: %w", err) diff --git a/backend/mapper/aaguid_mapper.go b/backend/mapper/aaguid_mapper.go new file mode 100644 index 000000000..7f3557c84 --- /dev/null +++ b/backend/mapper/aaguid_mapper.go @@ -0,0 +1,48 @@ +package mapper + +import ( + "fmt" + "github.com/gofrs/uuid" + "github.com/knadh/koanf/parsers/json" + "github.com/teamhanko/hanko/backend/config" + "log" +) + +type Aaguid struct { + Name string `json:"name"` + IconLight string `json:"icon_light"` + IconDark string `json:"icon_dark"` +} + +type AaguidMap map[string]Aaguid + +func (w AaguidMap) GetNameForAaguid(aaguid uuid.UUID) *string { + if webauthnAaguid, ok := w[aaguid.String()]; ok { + return &webauthnAaguid.Name + } else { + return nil + } +} + +func LoadAaguidMap(aaguidFilePath *string) AaguidMap { + k, err := config.LoadFile(aaguidFilePath, json.Parser()) + + if err != nil { + log.Println(err) + return nil + } + + if k == nil { + log.Println("no aaguid map file provided. Skipping...") + return nil + } + + var aaguidMap AaguidMap + err = k.Unmarshal("", &aaguidMap) + if err != nil { + log.Println(fmt.Errorf("unable to unmarshal aaguid map: %w", err)) + return nil + } + + return aaguidMap +} diff --git a/backend/server/server.go b/backend/server/server.go index 84174112c..bb8dae96a 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -4,13 +4,14 @@ import ( "github.com/labstack/echo/v4" "github.com/teamhanko/hanko/backend/config" "github.com/teamhanko/hanko/backend/handler" + "github.com/teamhanko/hanko/backend/mapper" "github.com/teamhanko/hanko/backend/persistence" "sync" ) -func StartPublic(cfg *config.Config, wg *sync.WaitGroup, persister persistence.Persister, prometheus echo.MiddlewareFunc) { +func StartPublic(cfg *config.Config, wg *sync.WaitGroup, persister persistence.Persister, prometheus echo.MiddlewareFunc, aaguidMap mapper.AaguidMap) { defer wg.Done() - router := handler.NewPublicRouter(cfg, persister, prometheus) + router := handler.NewPublicRouter(cfg, persister, prometheus, aaguidMap) router.Logger.Fatal(router.Start(cfg.Server.Public.Address)) } diff --git a/deploy/docker-compose/aaguid.json b/deploy/docker-compose/aaguid.json new file mode 100644 index 000000000..8410d2e6c --- /dev/null +++ b/deploy/docker-compose/aaguid.json @@ -0,0 +1,105 @@ +{ + "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4": { + "name": "Google Password Manager", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE5MiAxOTIiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDE5MiAxOTIiIHdpZHRoPSIyNHB4Ij48cmVjdCBmaWxsPSJub25lIiBoZWlnaHQ9IjE5MiIgd2lkdGg9IjE5MiIgeT0iMCIvPjxnPjxwYXRoIGQ9Ik02OS4yOSwxMDZjLTMuNDYsNS45Ny05LjkxLDEwLTE3LjI5LDEwYy0xMS4wMywwLTIwLTguOTctMjAtMjBzOC45Ny0yMCwyMC0yMCBjNy4zOCwwLDEzLjgzLDQuMDMsMTcuMjksMTBoMjUuNTVDOTAuMyw2Ni41NCw3Mi44Miw1Miw1Miw1MkMyNy43NCw1Miw4LDcxLjc0LDgsOTZzMTkuNzQsNDQsNDQsNDRjMjAuODIsMCwzOC4zLTE0LjU0LDQyLjg0LTM0IEg2OS4yOXoiIGZpbGw9IiM0Mjg1RjQiLz48cmVjdCBmaWxsPSIjRkJCQzA0IiBoZWlnaHQ9IjI0IiB3aWR0aD0iNDQiIHg9Ijk0IiB5PSI4NCIvPjxwYXRoIGQ9Ik05NC4zMiw4NEg2OHYwLjA1YzIuNSwzLjM0LDQsNy40Nyw0LDExLjk1cy0xLjUsOC42MS00LDExLjk1VjEwOGgyNi4zMiBjMS4wOC0zLjgyLDEuNjgtNy44NCwxLjY4LTEyUzk1LjQxLDg3LjgyLDk0LjMyLDg0eiIgZmlsbD0iI0VBNDMzNSIvPjxwYXRoIGQ9Ik0xODQsMTA2djI2aC0xNnYtOGMwLTQuNDItMy41OC04LTgtOHMtOCwzLjU4LTgsOHY4aC0xNnYtMjZIMTg0eiIgZmlsbD0iIzM0QTg1MyIvPjxyZWN0IGZpbGw9IiMxODgwMzgiIGhlaWdodD0iMjQiIHdpZHRoPSI0OCIgeD0iMTM2IiB5PSI4NCIvPjwvZz48L3N2Zz4=", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE5MiAxOTIiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDE5MiAxOTIiIHdpZHRoPSIyNHB4Ij48cmVjdCBmaWxsPSJub25lIiBoZWlnaHQ9IjE5MiIgd2lkdGg9IjE5MiIgeT0iMCIvPjxnPjxwYXRoIGQ9Ik02OS4yOSwxMDZjLTMuNDYsNS45Ny05LjkxLDEwLTE3LjI5LDEwYy0xMS4wMywwLTIwLTguOTctMjAtMjBzOC45Ny0yMCwyMC0yMCBjNy4zOCwwLDEzLjgzLDQuMDMsMTcuMjksMTBoMjUuNTVDOTAuMyw2Ni41NCw3Mi44Miw1Miw1Miw1MkMyNy43NCw1Miw4LDcxLjc0LDgsOTZzMTkuNzQsNDQsNDQsNDRjMjAuODIsMCwzOC4zLTE0LjU0LDQyLjg0LTM0IEg2OS4yOXoiIGZpbGw9IiM0Mjg1RjQiLz48cmVjdCBmaWxsPSIjRkJCQzA0IiBoZWlnaHQ9IjI0IiB3aWR0aD0iNDQiIHg9Ijk0IiB5PSI4NCIvPjxwYXRoIGQ9Ik05NC4zMiw4NEg2OHYwLjA1YzIuNSwzLjM0LDQsNy40Nyw0LDExLjk1cy0xLjUsOC42MS00LDExLjk1VjEwOGgyNi4zMiBjMS4wOC0zLjgyLDEuNjgtNy44NCwxLjY4LTEyUzk1LjQxLDg3LjgyLDk0LjMyLDg0eiIgZmlsbD0iI0VBNDMzNSIvPjxwYXRoIGQ9Ik0xODQsMTA2djI2aC0xNnYtOGMwLTQuNDItMy41OC04LTgtOHMtOCwzLjU4LTgsOHY4aC0xNnYtMjZIMTg0eiIgZmlsbD0iIzM0QTg1MyIvPjxyZWN0IGZpbGw9IiMxODgwMzgiIGhlaWdodD0iMjQiIHdpZHRoPSI0OCIgeD0iMTM2IiB5PSI4NCIvPjwvZz48L3N2Zz4=" + }, + "adce0002-35bc-c60a-648b-0b25f1f05503": { + "name": "Chrome on Mac", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDggNDgiPgogIDxkZWZzPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMy4yMTczIiB5MT0iMTUiIHgyPSI0NC43ODEyIiB5Mj0iMTUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZDkzMDI1Ii8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2VhNDMzNSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjIwLjcyMTkiIHkxPSI0Ny42NzkxIiB4Mj0iNDEuNTAzOSIgeTI9IjExLjY4MzciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmNjOTM0Ii8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZiYmMwNCIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjI2LjU5ODEiIHkxPSI0Ni41MDE1IiB4Mj0iNS44MTYxIiB5Mj0iMTAuNTA2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzFlOGUzZSIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNGE4NTMiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgICAKICAgIDxwYXRoIGlkPSJwIiBkPSJNMTMuNjA4NiAzMC4wMDMxIDMuMjE4IDEyLjAwNkEyMy45OTQgMjMuOTk0IDAgMCAwIDI0LjAwMjUgNDhsMTAuMzkwNi0xNy45OTcxLS4wMDY3LS4wMDY4YTExLjk4NTIgMTEuOTg1MiAwIDAgMS0yMC43Nzc4LjAwN1oiLz4KICA8L2RlZnM+CiAgCiAgPHVzZSB4bGluazpocmVmPSIjcCIgZmlsbD0idXJsKCNhKSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDI0IDI0KSIvPgogIDx1c2UgeGxpbms6aHJlZj0iI3AiIGZpbGw9InVybCgjYikiIHRyYW5zZm9ybT0icm90YXRlKC0xMjAgMjQgMjQpIi8+CiAgPHVzZSB4bGluazpocmVmPSIjcCIgZmlsbD0idXJsKCNjKSIvPgogIAogIDxjaXJjbGUgY3g9IjI0IiBjeT0iMjQiIHI9IjEyIiBzdHlsZT0iZmlsbDojZmZmIi8+CiAgPGNpcmNsZSBjeD0iMjQiIGN5PSIyNCIgcj0iOS41IiBzdHlsZT0iZmlsbDojMWE3M2U4Ii8+Cjwvc3ZnPg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDggNDgiPgogIDxkZWZzPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMy4yMTczIiB5MT0iMTUiIHgyPSI0NC43ODEyIiB5Mj0iMTUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZDkzMDI1Ii8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2VhNDMzNSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjIwLjcyMTkiIHkxPSI0Ny42NzkxIiB4Mj0iNDEuNTAzOSIgeTI9IjExLjY4MzciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmNjOTM0Ii8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZiYmMwNCIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjI2LjU5ODEiIHkxPSI0Ni41MDE1IiB4Mj0iNS44MTYxIiB5Mj0iMTAuNTA2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzFlOGUzZSIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNGE4NTMiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgICAKICAgIDxwYXRoIGlkPSJwIiBkPSJNMTMuNjA4NiAzMC4wMDMxIDMuMjE4IDEyLjAwNkEyMy45OTQgMjMuOTk0IDAgMCAwIDI0LjAwMjUgNDhsMTAuMzkwNi0xNy45OTcxLS4wMDY3LS4wMDY4YTExLjk4NTIgMTEuOTg1MiAwIDAgMS0yMC43Nzc4LjAwN1oiLz4KICA8L2RlZnM+CiAgCiAgPHVzZSB4bGluazpocmVmPSIjcCIgZmlsbD0idXJsKCNhKSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDI0IDI0KSIvPgogIDx1c2UgeGxpbms6aHJlZj0iI3AiIGZpbGw9InVybCgjYikiIHRyYW5zZm9ybT0icm90YXRlKC0xMjAgMjQgMjQpIi8+CiAgPHVzZSB4bGluazpocmVmPSIjcCIgZmlsbD0idXJsKCNjKSIvPgogIAogIDxjaXJjbGUgY3g9IjI0IiBjeT0iMjQiIHI9IjEyIiBzdHlsZT0iZmlsbDojZmZmIi8+CiAgPGNpcmNsZSBjeD0iMjQiIGN5PSIyNCIgcj0iOS41IiBzdHlsZT0iZmlsbDojMWE3M2U4Ii8+Cjwvc3ZnPg==" + }, + "08987058-cadc-4b81-b6e1-30de50dcbe96": { + "name": "Windows Hello", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1IiBoZWlnaHQ9Ijk4LjM1Ii8+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIxMzMuNCIgeT0iMTMzLjQiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjwvc3ZnPg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1IiBoZWlnaHQ9Ijk4LjM1Ii8+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIxMzMuNCIgeT0iMTMzLjQiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjwvc3ZnPg==" + }, + "9ddd1817-af5a-4672-a2b9-3e3dd95000a9": { + "name": "Windows Hello", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1IiBoZWlnaHQ9Ijk4LjM1Ii8+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIxMzMuNCIgeT0iMTMzLjQiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjwvc3ZnPg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1IiBoZWlnaHQ9Ijk4LjM1Ii8+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIxMzMuNCIgeT0iMTMzLjQiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjwvc3ZnPg==" + }, + "6028b017-b1d4-4c02-b4b3-afcdafc96bb2": { + "name": "Windows Hello", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1IiBoZWlnaHQ9Ijk4LjM1Ii8+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIxMzMuNCIgeT0iMTMzLjQiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjwvc3ZnPg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMDc4ZDQ7c3Ryb2tlLXdpZHRoOjBweDt9PC9zdHlsZT48L2RlZnM+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIyNC4yNSIgeT0iMjQuMjUiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjxyZWN0IGNsYXNzPSJjbHMtMSIgeD0iMTMzLjQiIHk9IjI0LjI1IiB3aWR0aD0iOTguMzUiIGhlaWdodD0iOTguMzUiLz48cmVjdCBjbGFzcz0iY2xzLTEiIHg9IjI0LjI1IiB5PSIxMzMuNCIgd2lkdGg9Ijk4LjM1IiBoZWlnaHQ9Ijk4LjM1Ii8+PHJlY3QgY2xhc3M9ImNscy0xIiB4PSIxMzMuNCIgeT0iMTMzLjQiIHdpZHRoPSI5OC4zNSIgaGVpZ2h0PSI5OC4zNSIvPjwvc3ZnPg==" + }, + "dd4ec289-e01d-41c9-bb89-70fa845d4bf2": { + "name": "iCloud Keychain (Managed)", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ3LTc3LjY2LDAtNDUuNTksMjkuNjMtNjkuNzUsNTguODEtNjkuNzUsMTUuNSwwLDI4LjQyLDEwLjE4LDM4LjE1LDEwLjE4czIzLjcxLTEwLjc5LDQxLjM0LTEwLjc5YzE4LjQxLS40NywzNS44NCw4LjI0LDQ2LjUsMjMuMjVabS01NC44Ni00Mi41NWM3Ljc3LTkuMTQsMTIuMTctMjAuNjcsMTIuNDYtMzIuNjcuMDEtMS41OC0uMTQtMy4xNi0uNDYtNC43MS0xMy4zNSwxLjMtMjUuNjksNy42Ny0zNC41LDE3Ljc4LTcuODUsOC43OC0xMi40MSwyMC0xMi45MiwzMS43NiwwLDEuNDMuMTYsMi44Ni40Niw0LjI2LDEuMDUuMiwyLjEyLjMsMy4xOS4zLDEyLjQzLS45OSwyMy45MS03LjA0LDMxLjc2LTE2LjczWiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ3LTc3LjY2LDAtNDUuNTksMjkuNjMtNjkuNzUsNTguODEtNjkuNzUsMTUuNSwwLDI4LjQyLDEwLjE4LDM4LjE1LDEwLjE4czIzLjcxLTEwLjc5LDQxLjM0LTEwLjc5YzE4LjQxLS40NywzNS44NCw4LjI0LDQ2LjUsMjMuMjVabS01NC44Ni00Mi41NWM3Ljc3LTkuMTQsMTIuMTctMjAuNjcsMTIuNDYtMzIuNjcuMDEtMS41OC0uMTQtMy4xNi0uNDYtNC43MS0xMy4zNSwxLjMtMjUuNjksNy42Ny0zNC41LDE3Ljc4LTcuODUsOC43OC0xMi40MSwyMC0xMi45MiwzMS43NiwwLDEuNDMuMTYsMi44Ni40Niw0LjI2LDEuMDUuMiwyLjEyLjMsMy4xOS4zLDEyLjQzLS45OSwyMy45MS03LjA0LDMxLjc2LTE2LjczWiIgZmlsbD0iIzAwMCIvPjwvc3ZnPg==" + }, + "531126d6-e717-415c-9320-3d9aa6981239": { + "name": "Dashlane", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTcuNDc0IDM1OS4yMDlDMjU3LjQ3NCAzNTYuMTg5IDI1NC40NTQgMzUzLjE2OSAyNTAuMjE1IDM1MS45NTlMMTk5LjQxMSAzMzMuMjMxQzE5MC44OTUgMzI5LjYwMSAxODEuMjY0IDMzMy44MzEgMTgxLjI2NCAzMzkuODlWNDc1Ljc3OUMxODEuMjY0IDQ3OC44MDkgMTg0LjI4MyA0ODIuNDM4IDE4Ny4zMDMgNDgzLjY0OEwyMzkuMzI2IDUwMi4zNzZDMjQ3LjE5NSA1MDUuMzk2IDI1Ny40NzQgNTAxLjE2NiAyNTcuNDc0IDQ5NC41MDhWMzU5LjIwOVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0zNTIuNzM2IDMyMS4xMDRDMzUyLjczNiAzMTguMDg0IDM0OS43MTcgMzE1LjA2NCAzNDUuNDc3IDMxMy44NTRMMjk0LjY3NCAyOTUuMTI2QzI4Ni4xNTcgMjkxLjQ5NiAyNzYuNTI2IDI5NS43MjYgMjc2LjUyNiAzMDEuNzg1VjQzNy42NzRDMjc2LjUyNiA0NDAuNzA0IDI3OS41NDYgNDQ0LjMzMyAyODIuNTY2IDQ0NS41NDNMMzM0LjU4OSA0NjQuMjcxQzM0Mi40NTggNDY3LjI5MSAzNTIuNzM2IDQ2My4wNjEgMzUyLjczNiA0NTYuNDAzVjMyMS4xMDRaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjU3LjQ3NCAzNS4zMjExQzI1Ny40NzQgMzIuMzAxMyAyNTQuNDU0IDI5LjI4MTUgMjUwLjIxNSAyOC4wNzE3TDE5OS40MTEgOS4zNDM0M0MxOTAuODk1IDUuNzEzOTkgMTgxLjI2NCA5Ljk0MzU4IDE4MS4yNjQgMTYuMDAyMlYxNTEuODkyQzE4MS4yNjQgMTU0LjkyMSAxODQuMjgzIDE1OC41NTEgMTg3LjMwMyAxNTkuNzZMMjM5LjMyNiAxNzguNDg5QzI0Ny4xOTUgMTgxLjUwOSAyNTcuNDc0IDE3Ny4yNzkgMjU3LjQ3NCAxNzAuNjJWMzUuMzIxMVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0zNTIuNzM2IDkyLjQ3NzdDMzUyLjczNiA4OS40NTc5IDM0OS43MTcgODYuNDM4MiAzNDUuNDc3IDg1LjIyODNMMjk0LjY3NCA2Ni41QzI4Ni4xNTcgNjIuODcwNiAyNzYuNTI2IDY3LjEwMDIgMjc2LjUyNiA3My4xNTg4VjIwOS4wNDhDMjc2LjUyNiAyMTIuMDc4IDI3OS41NDYgMjE1LjcwNyAyODIuNTY2IDIxNi45MTdMMzM0LjU4OSAyMzUuNjQ1QzM0Mi40NTggMjM4LjY2NSAzNTIuNzM2IDIzNC40MzYgMzUyLjczNiAyMjcuNzc3VjkyLjQ3NzdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNNDQ4IDE2OC42ODdDNDQ4IDE2NS42NjcgNDQ0Ljk4IDE2Mi42NDcgNDQwLjc0MSAxNjEuNDM3TDM4OS45MzcgMTQyLjcwOUMzODEuNDIxIDEzOS4wNzkgMzcxLjc5IDE0My4zMDkgMzcxLjc5IDE0OS4zNjhWMzYxLjQ2NkMzNzEuNzkgMzY0LjQ5NSAzNzQuODEgMzY4LjEyNSAzNzcuODI5IDM2OS4zMzVMNDI5Ljg1MiAzODguMDYzQzQzNy43MjEgMzkxLjA4MyA0NDggMzg2Ljg1MyA0NDggMzgwLjE5NFYxNjguNjg3WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE2Mi4yMSAzNS4zMzA2QzE2Mi4yMSAzMi4zMTA4IDE1OS4xOSAyOS4yODE1IDE1NC45NTEgMjguMDcxN0wxMDQuMTQ4IDkuMzQzNDNDOTUuNjc4NyA1LjcxMzk5IDg2IDkuOTQzNTggODYgMTYuMDAyMlY0NzUuNzg5Qzg2IDQ3OC44MDggODkuMDE5OCA0ODIuNDM4IDkyLjA0OTIgNDgzLjY0OEwxNDQuMDYzIDUwMi4zNzZDMTUxLjkzMSA1MDUuMzk2IDE2Mi4yMSA1MDEuMTY2IDE2Mi4yMSA0OTQuNTA3VjM1LjMzMDZaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTcuNDc0IDM1OS4yMDlDMjU3LjQ3NCAzNTYuMTg5IDI1NC40NTQgMzUzLjE2OSAyNTAuMjE1IDM1MS45NTlMMTk5LjQxMSAzMzMuMjMxQzE5MC44OTUgMzI5LjYwMSAxODEuMjY0IDMzMy44MzEgMTgxLjI2NCAzMzkuODlWNDc1Ljc3OUMxODEuMjY0IDQ3OC44MDkgMTg0LjI4MyA0ODIuNDM4IDE4Ny4zMDMgNDgzLjY0OEwyMzkuMzI2IDUwMi4zNzZDMjQ3LjE5NSA1MDUuMzk2IDI1Ny40NzQgNTAxLjE2NiAyNTcuNDc0IDQ5NC41MDhWMzU5LjIwOVoiIGZpbGw9IiMwOTM2M0YiLz4KPHBhdGggZD0iTTM1Mi43MzYgMzIxLjEwM0MzNTIuNzM2IDMxOC4wODQgMzQ5LjcxNyAzMTUuMDY0IDM0NS40NzcgMzEzLjg1NEwyOTQuNjc0IDI5NS4xMjZDMjg2LjE1NyAyOTEuNDk2IDI3Ni41MjYgMjk1LjcyNiAyNzYuNTI2IDMwMS43ODVWNDM3LjY3NEMyNzYuNTI2IDQ0MC43MDQgMjc5LjU0NiA0NDQuMzMzIDI4Mi41NjYgNDQ1LjU0M0wzMzQuNTg5IDQ2NC4yNzFDMzQyLjQ1OCA0NjcuMjkxIDM1Mi43MzYgNDYzLjA2MSAzNTIuNzM2IDQ1Ni40MDNWMzIxLjEwM1oiIGZpbGw9IiMwOTM2M0YiLz4KPHBhdGggZD0iTTI1Ny40NzQgMzUuMzIxMUMyNTcuNDc0IDMyLjMwMTMgMjU0LjQ1NCAyOS4yODE1IDI1MC4yMTUgMjguMDcxN0wxOTkuNDExIDkuMzQzNDNDMTkwLjg5NSA1LjcxMzk5IDE4MS4yNjQgOS45NDM1OCAxODEuMjY0IDE2LjAwMjJWMTUxLjg5MkMxODEuMjY0IDE1NC45MjEgMTg0LjI4MyAxNTguNTUxIDE4Ny4zMDMgMTU5Ljc2TDIzOS4zMjYgMTc4LjQ4OUMyNDcuMTk1IDE4MS41MDggMjU3LjQ3NCAxNzcuMjc5IDI1Ny40NzQgMTcwLjYyVjM1LjMyMTFaIiBmaWxsPSIjMDkzNjNGIi8+CjxwYXRoIGQ9Ik0zNTIuNzM2IDkyLjQ3NzdDMzUyLjczNiA4OS40NTc5IDM0OS43MTcgODYuNDM4MiAzNDUuNDc3IDg1LjIyODNMMjk0LjY3NCA2Ni41QzI4Ni4xNTcgNjIuODcwNiAyNzYuNTI2IDY3LjEwMDIgMjc2LjUyNiA3My4xNTg4VjIwOS4wNDhDMjc2LjUyNiAyMTIuMDc4IDI3OS41NDYgMjE1LjcwNyAyODIuNTY2IDIxNi45MTdMMzM0LjU4OSAyMzUuNjQ1QzM0Mi40NTggMjM4LjY2NSAzNTIuNzM2IDIzNC40MzYgMzUyLjczNiAyMjcuNzc3VjkyLjQ3NzdaIiBmaWxsPSIjMDkzNjNGIi8+CjxwYXRoIGQ9Ik00NDggMTY4LjY4N0M0NDggMTY1LjY2NyA0NDQuOTggMTYyLjY0NyA0NDAuNzQxIDE2MS40MzdMMzg5LjkzNyAxNDIuNzA5QzM4MS40MjEgMTM5LjA3OSAzNzEuNzkgMTQzLjMwOSAzNzEuNzkgMTQ5LjM2OFYzNjEuNDY2QzM3MS43OSAzNjQuNDk1IDM3NC44MSAzNjguMTI1IDM3Ny44MjkgMzY5LjMzNUw0MjkuODUyIDM4OC4wNjNDNDM3LjcyMSAzOTEuMDgzIDQ0OCAzODYuODUzIDQ0OCAzODAuMTk0VjE2OC42ODdaIiBmaWxsPSIjMDkzNjNGIi8+CjxwYXRoIGQ9Ik0xNjIuMjEgMzUuMzMwNkMxNjIuMjEgMzIuMzEwOCAxNTkuMTkgMjkuMjgxNSAxNTQuOTUxIDI4LjA3MTdMMTA0LjE0OCA5LjM0MzQzQzk1LjY3ODcgNS43MTM5OSA4NiA5Ljk0MzU4IDg2IDE2LjAwMjJWNDc1Ljc4OUM4NiA0NzguODA4IDg5LjAxOTggNDgyLjQzOCA5Mi4wNDkyIDQ4My42NDhMMTQ0LjA2MyA1MDIuMzc2QzE1MS45MzEgNTA1LjM5NiAxNjIuMjEgNTAxLjE2NiAxNjIuMjEgNDk0LjUwN1YzNS4zMzA2WiIgZmlsbD0iIzA5MzYzRiIvPgo8L3N2Zz4K" + }, + "bada5566-a7aa-401f-bd96-45619a55120d": { + "name": "1Password", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQwIiBoZWlnaHQ9IjI0MCIgdmlld0JveD0iMCAwIDI0MCAyNDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMjM5LjI1NyAxMjAuNDE3QzIzOS4yNTcgNTQuNDE5MyAxODUuNzU1IDAuOTE2NTA0IDExOS43NTcgMC45MTY1MDRDNTMuNzYwMSAwLjkxNjUwNCAwLjI1NzMyNCA1NC40MTkzIDAuMjU3MzI0IDEyMC40MTdDMC4yNTczMjQgMTg2LjQxNyA1My43NjAxIDIzOS45MTcgMTE5Ljc1NyAyMzkuOTE3QzE4NS43NTUgMjM5LjkxNyAyMzkuMjU3IDE4Ni40MTcgMjM5LjI1NyAxMjAuNDE3Wk05OC4wMDY5IDU0LjAyNzZDOTcuMDY3NCA1NS44NzE0IDk3LjA2NzQgNTguMjg1MSA5Ny4wNjc0IDYzLjExMjZWOTAuNDcyOUM5Ny4wNjc0IDkxLjY3ODggOTcuMDY3NCA5Mi4yODE3IDk3LjIxOTYgOTIuODM5MkM5Ny4zNTQ1IDkzLjMzMzEgOTcuNTc2MyA5My43OTkgOTcuODc0NiA5NC4yMTVDOTguMjExMyA5NC42ODQ3IDk4LjY3OTIgOTUuMDY0OCA5OS42MTUyIDk1LjgyNTFMMTA2LjUzNiAxMDEuNDQ3QzEwNy42NjQgMTAyLjM2NCAxMDguMjI4IDEwMi44MjIgMTA4LjQzMyAxMDMuMzc0QzEwOC42MTMgMTAzLjg1NyAxMDguNjEzIDEwNC4zOSAxMDguNDMzIDEwNC44NzNDMTA4LjIyOCAxMDUuNDI1IDEwNy42NjQgMTA1Ljg4MyAxMDYuNTM2IDEwNi44TDk5LjYxNTIgMTEyLjQyMkM5OC42NzkzIDExMy4xODIgOTguMjExMyAxMTMuNTYyIDk3Ljg3NDYgMTE0LjAzMkM5Ny41NzYzIDExNC40NDggOTcuMzU0NSAxMTQuOTE0IDk3LjIxOTYgMTE1LjQwOEM5Ny4wNjc0IDExNS45NjUgOTcuMDY3NCAxMTYuNTY4IDk3LjA2NzQgMTE3Ljc3NFYxNzcuNzE5Qzk3LjA2NzQgMTgyLjU0NyA5Ny4wNjc0IDE4NC45NjEgOTguMDA2OSAxODYuODA1Qzk4LjgzMzMgMTg4LjQyNiAxMDAuMTUyIDE4OS43NDUgMTAxLjc3NCAxOTAuNTcxQzEwMy42MTggMTkxLjUxMSAxMDYuMDMxIDE5MS41MTEgMTEwLjg1OSAxOTEuNTExSDEyOC42NTZDMTMzLjQ4MyAxOTEuNTExIDEzNS44OTcgMTkxLjUxMSAxMzcuNzQxIDE5MC41NzFDMTM5LjM2MyAxODkuNzQ1IDE0MC42ODEgMTg4LjQyNiAxNDEuNTA4IDE4Ni44MDVDMTQyLjQ0NyAxODQuOTYxIDE0Mi40NDcgMTgyLjU0NyAxNDIuNDQ3IDE3Ny43MTlWMTUwLjM1OUMxNDIuNDQ3IDE0OS4xNTMgMTQyLjQ0NyAxNDguNTUgMTQyLjI5NSAxNDcuOTkzQzE0Mi4xNiAxNDcuNDk5IDE0MS45MzggMTQ3LjAzMyAxNDEuNjQgMTQ2LjYxN0MxNDEuMzAzIDE0Ni4xNDcgMTQwLjgzNSAxNDUuNzY3IDEzOS44OTkgMTQ1LjAwN0wxMzIuOTc4IDEzOS4zODVDMTMxLjg1IDEzOC40NjggMTMxLjI4NiAxMzguMDEgMTMxLjA4MiAxMzcuNDU5QzEzMC45MDIgMTM2Ljk3NSAxMzAuOTAyIDEzNi40NDMgMTMxLjA4MiAxMzUuOTU5QzEzMS4yODYgMTM1LjQwNyAxMzEuODUgMTM0Ljk0OSAxMzIuOTc4IDEzNC4wMzNMMTM5Ljg5OSAxMjguNDFDMTQwLjgzNSAxMjcuNjUgMTQxLjMwMyAxMjcuMjcgMTQxLjY0IDEyNi44QzE0MS45MzggMTI2LjM4NCAxNDIuMTYgMTI1LjkxOCAxNDIuMjk1IDEyNS40MjRDMTQyLjQ0NyAxMjQuODY3IDE0Mi40NDcgMTI0LjI2NCAxNDIuNDQ3IDEyMy4wNThWNjMuMTEyNkMxNDIuNDQ3IDU4LjI4NTEgMTQyLjQ0NyA1NS44NzE0IDE0MS41MDggNTQuMDI3NkMxNDAuNjgxIDUyLjQwNTcgMTM5LjM2MyA1MS4wODcgMTM3Ljc0MSA1MC4yNjA2QzEzNS44OTcgNDkuMzIxMSAxMzMuNDgzIDQ5LjMyMTEgMTI4LjY1NiA0OS4zMjExSDExMC44NTlDMTA2LjAzMSA0OS4zMjExIDEwMy42MTggNDkuMzIxMSAxMDEuNzc0IDUwLjI2MDZDMTAwLjE1MiA1MS4wODcgOTguODMzMyA1Mi40MDU3IDk4LjAwNjkgNTQuMDI3NloiIGZpbGw9IiNGRkZFRkIiLz4KPC9zdmc+Cg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQwIiBoZWlnaHQ9IjI0MCIgdmlld0JveD0iMCAwIDI0MCAyNDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMjM5LjExNiAxMjAuNDE3QzIzOS4xMTYgNTQuNDE5MyAxODUuNjEzIDAuOTE2NTA0IDExOS42MTYgMC45MTY1MDRDNTMuNjE5IDAuOTE2NTA0IDAuMTE2MjExIDU0LjQxOTMgMC4xMTYyMTEgMTIwLjQxN0MwLjExNjIxMSAxODYuNDE3IDUzLjYxOSAyMzkuOTE3IDExOS42MTYgMjM5LjkxN0MxODUuNjEzIDIzOS45MTcgMjM5LjExNiAxODYuNDE3IDIzOS4xMTYgMTIwLjQxN1pNOTcuODY1OCA1NC4wMjc2Qzk2LjkyNjMgNTUuODcxNCA5Ni45MjYzIDU4LjI4NTEgOTYuOTI2MyA2My4xMTI2VjkwLjQ3MjlDOTYuOTI2MyA5MS42Nzg4IDk2LjkyNjMgOTIuMjgxNyA5Ny4wNzg1IDkyLjgzOTJDOTcuMjEzNCA5My4zMzMxIDk3LjQzNTIgOTMuNzk5IDk3LjczMzUgOTQuMjE1Qzk4LjA3MDIgOTQuNjg0NyA5OC41MzgxIDk1LjA2NDggOTkuNDc0MSA5NS44MjUxTDEwNi4zOTUgMTAxLjQ0N0MxMDcuNTIzIDEwMi4zNjQgMTA4LjA4NyAxMDIuODIyIDEwOC4yOTIgMTAzLjM3NEMxMDguNDcxIDEwMy44NTcgMTA4LjQ3MSAxMDQuMzkgMTA4LjI5MiAxMDQuODczQzEwOC4wODcgMTA1LjQyNSAxMDcuNTIzIDEwNS44ODMgMTA2LjM5NSAxMDYuOEw5OS40NzQxIDExMi40MjJDOTguNTM4MiAxMTMuMTgyIDk4LjA3MDIgMTEzLjU2MiA5Ny43MzM1IDExNC4wMzJDOTcuNDM1MiAxMTQuNDQ4IDk3LjIxMzQgMTE0LjkxNCA5Ny4wNzg1IDExNS40MDhDOTYuOTI2MyAxMTUuOTY1IDk2LjkyNjMgMTE2LjU2OCA5Ni45MjYzIDExNy43NzRWMTc3LjcxOUM5Ni45MjYzIDE4Mi41NDcgOTYuOTI2MyAxODQuOTYxIDk3Ljg2NTggMTg2LjgwNUM5OC42OTIyIDE4OC40MjYgMTAwLjAxMSAxODkuNzQ1IDEwMS42MzMgMTkwLjU3MUMxMDMuNDc3IDE5MS41MTEgMTA1Ljg5IDE5MS41MTEgMTEwLjcxOCAxOTEuNTExSDEyOC41MTVDMTMzLjM0MiAxOTEuNTExIDEzNS43NTYgMTkxLjUxMSAxMzcuNiAxOTAuNTcxQzEzOS4yMjEgMTg5Ljc0NSAxNDAuNTQgMTg4LjQyNiAxNDEuMzY3IDE4Ni44MDVDMTQyLjMwNiAxODQuOTYxIDE0Mi4zMDYgMTgyLjU0NyAxNDIuMzA2IDE3Ny43MTlWMTUwLjM1OUMxNDIuMzA2IDE0OS4xNTMgMTQyLjMwNiAxNDguNTUgMTQyLjE1NCAxNDcuOTkzQzE0Mi4wMTkgMTQ3LjQ5OSAxNDEuNzk3IDE0Ny4wMzMgMTQxLjQ5OSAxNDYuNjE3QzE0MS4xNjIgMTQ2LjE0NyAxNDAuNjk0IDE0NS43NjcgMTM5Ljc1OCAxNDUuMDA3TDEzMi44MzcgMTM5LjM4NUMxMzEuNzA5IDEzOC40NjggMTMxLjE0NSAxMzguMDEgMTMwLjk0IDEzNy40NTlDMTMwLjc2MSAxMzYuOTc1IDEzMC43NjEgMTM2LjQ0MyAxMzAuOTQgMTM1Ljk1OUMxMzEuMTQ1IDEzNS40MDcgMTMxLjcwOSAxMzQuOTQ5IDEzMi44MzcgMTM0LjAzM0wxMzkuNzU4IDEyOC40MUMxNDAuNjk0IDEyNy42NSAxNDEuMTYyIDEyNy4yNyAxNDEuNDk5IDEyNi44QzE0MS43OTcgMTI2LjM4NCAxNDIuMDE5IDEyNS45MTggMTQyLjE1NCAxMjUuNDI0QzE0Mi4zMDYgMTI0Ljg2NyAxNDIuMzA2IDEyNC4yNjQgMTQyLjMwNiAxMjMuMDU4VjYzLjExMjZDMTQyLjMwNiA1OC4yODUxIDE0Mi4zMDYgNTUuODcxNCAxNDEuMzY3IDU0LjAyNzZDMTQwLjU0IDUyLjQwNTcgMTM5LjIyMSA1MS4wODcgMTM3LjYgNTAuMjYwNkMxMzUuNzU2IDQ5LjMyMTEgMTMzLjM0MiA0OS4zMjExIDEyOC41MTUgNDkuMzIxMUgxMTAuNzE4QzEwNS44OSA0OS4zMjExIDEwMy40NzcgNDkuMzIxMSAxMDEuNjMzIDUwLjI2MDZDMTAwLjAxMSA1MS4wODcgOTguNjkyMiA1Mi40MDU3IDk3Ljg2NTggNTQuMDI3NloiIGZpbGw9IiMxQTI4NUYiLz4KPC9zdmc+Cg==" + }, + "b84e4048-15dc-4dd0-8640-f4f60813c8af": { + "name": "NordPass", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgODAgODAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03LjYxMzQgNzBDMi44MjQzNSA2My4zNTIgMCA1NS4xNzIyIDAgNDYuMzI3M0MwIDI0LjA1NTIgMTcuOTA4NiA2IDQwIDZDNjIuMDkxNCA2IDgwIDI0LjA1NTIgODAgNDYuMzI3M0M4MCA1NS4xNzIxIDc3LjE3NTcgNjMuMzUxOCA3Mi4zODY3IDY5Ljk5OTlMNTMuMTc0NyAzOC41NDY2TDUxLjMxOTUgNDEuNzA0Nkw1My4yMDE4IDUwLjQ4NzdMNDAgMjcuNzE0N0wzMS44MzM0IDQxLjYxNjFMMzMuNzM0NiA1MC40ODc3TDI2LjgxNDcgMzguNTY0Nkw3LjYxMzQgNzBaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgODAgODAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03LjYxMzQgNzBDMi44MjQzNSA2My4zNTIgMCA1NS4xNzIyIDAgNDYuMzI3M0MwIDI0LjA1NTIgMTcuOTA4NiA2IDQwIDZDNjIuMDkxNCA2IDgwIDI0LjA1NTIgODAgNDYuMzI3M0M4MCA1NS4xNzIxIDc3LjE3NTcgNjMuMzUxOCA3Mi4zODY3IDY5Ljk5OTlMNTMuMTc0NyAzOC41NDY2TDUxLjMxOTUgNDEuNzA0Nkw1My4yMDE4IDUwLjQ4NzdMNDAgMjcuNzE0N0wzMS44MzM0IDQxLjYxNjFMMzMuNzM0NiA1MC40ODc3TDI2LjgxNDcgMzguNTY0Nkw3LjYxMzQgNzBaIiBmaWxsPSIjMENBQUFCIi8+Cjwvc3ZnPgo=" + }, + "0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6": { + "name": "Keeper", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzYwMzRfMzM2MjcpIj4KPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTIiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMiAxMkMyMiAxNy41MjI4IDE3LjUyMjggMjIgMTIgMjJDNi40NzcxNSAyMiAyIDE3LjUyMjggMiAxMkMyIDYuNDc3MTUgNi40NzcxNSAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNMTAuMTIxOCAzLjI3MzI1SDExLjY2NjZWOS41MTUyN0gxNC44NTc1TDE4LjY5NiA2LjQ2MzE3TDE5LjY2MDcgNy42NjgyMUwxNS4zOTg5IDExLjA1NjRIMTAuMTIxOFYzLjI3MzI1WiIgZmlsbD0iI0ZGQzcwMCIvPgo8cGF0aCBkPSJNMTMuMTQzOCAzLjQ4MzY2TDE0LjY4ODcgMy44NzY5NFY2LjAzNDkyTDE2LjQxNzMgNC42MTgxMUwxNy43MDA4IDUuNTYwOTdMMTQuNDA3IDguMjYwMTNMMTMuMTQzOCA4LjI1MzQxVjMuNDgzNjZaIiBmaWxsPSIjRkZDNzAwIi8+CjxwYXRoIGQ9Ik00LjAzODcgMTUuMDg0OUw1LjU4MzU0IDE2LjM5NThWNy44MTQyN0w0LjAzODcgOS4yMjc3MlYxNS4wODQ5WiIgZmlsbD0iI0ZGQzcwMCIvPgo8cGF0aCBkPSJNOC42MTI1NyAxOC4yNDExTDcuMDY2MDQgMTkuNTgwNlY0LjQ5NDg1TDguNjEyNTcgNS44MzQzNFYxOC4yNDExWiIgZmlsbD0iI0ZGQzcwMCIvPgo8cGF0aCBkPSJNMTQuNjg4NyAxOC4xMTc0TDE2LjQxNzMgMTkuNTM0MkwxNy43MDA4IDE4LjU4OTdMMTQuNDA3IDE1Ljg5MjJMMTMuMTQzOCAxNS44OTg5VjIwLjY2ODdMMTQuNjg4NyAyMC4yNzU0VjE4LjExNzRaIiBmaWxsPSIjRkZDNzAwIi8+CjxwYXRoIGQ9Ik0xOC42OTYgMTcuNDc4NkwxNC44NTc1IDE0LjQyNDhIMTEuNjY2NlYyMC42NjY4SDEwLjEyMThWMTIuODg1M0gxNS4zOTg5TDE5LjY2MDcgMTYuMjczNUwxOC42OTYgMTcuNDc4NloiIGZpbGw9IiNGRkM3MDAiLz4KPHBhdGggZD0iTTE2LjczNzYgMTEuOTcwNkwxOS44OTgxIDE0LjU3MDZMMjAuODgzIDEzLjM4MjNMMTkuMTY2MSAxMS45NzA2TDIwLjg4MyAxMC41NTg4TDE5Ljg5ODEgOS4zNzA1NkwxNi43Mzc2IDExLjk3MDZaIiBmaWxsPSIjRkZDNzAwIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjAzNF8zMzYyNyI+CjxyZWN0IHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzYwMzRfMzM2MjcpIj4KPGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTIiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yMiAxMkMyMiAxNy41MjI4IDE3LjUyMjggMjIgMTIgMjJDNi40NzcxNSAyMiAyIDE3LjUyMjggMiAxMkMyIDYuNDc3MTUgNi40NzcxNSAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNMTAuMTIxOCAzLjI3MzI1SDExLjY2NjZWOS41MTUyN0gxNC44NTc1TDE4LjY5NiA2LjQ2MzE3TDE5LjY2MDcgNy42NjgyMUwxNS4zOTg5IDExLjA1NjRIMTAuMTIxOFYzLjI3MzI1WiIgZmlsbD0iI0ZGQzcwMCIvPgo8cGF0aCBkPSJNMTMuMTQzOCAzLjQ4MzY2TDE0LjY4ODcgMy44NzY5NFY2LjAzNDkyTDE2LjQxNzMgNC42MTgxMUwxNy43MDA4IDUuNTYwOTdMMTQuNDA3IDguMjYwMTNMMTMuMTQzOCA4LjI1MzQxVjMuNDgzNjZaIiBmaWxsPSIjRkZDNzAwIi8+CjxwYXRoIGQ9Ik00LjAzODcgMTUuMDg0OUw1LjU4MzU0IDE2LjM5NThWNy44MTQyN0w0LjAzODcgOS4yMjc3MlYxNS4wODQ5WiIgZmlsbD0iI0ZGQzcwMCIvPgo8cGF0aCBkPSJNOC42MTI1NyAxOC4yNDExTDcuMDY2MDQgMTkuNTgwNlY0LjQ5NDg1TDguNjEyNTcgNS44MzQzNFYxOC4yNDExWiIgZmlsbD0iI0ZGQzcwMCIvPgo8cGF0aCBkPSJNMTQuNjg4NyAxOC4xMTc0TDE2LjQxNzMgMTkuNTM0MkwxNy43MDA4IDE4LjU4OTdMMTQuNDA3IDE1Ljg5MjJMMTMuMTQzOCAxNS44OTg5VjIwLjY2ODdMMTQuNjg4NyAyMC4yNzU0VjE4LjExNzRaIiBmaWxsPSIjRkZDNzAwIi8+CjxwYXRoIGQ9Ik0xOC42OTYgMTcuNDc4NkwxNC44NTc1IDE0LjQyNDhIMTEuNjY2NlYyMC42NjY4SDEwLjEyMThWMTIuODg1M0gxNS4zOTg5TDE5LjY2MDcgMTYuMjczNUwxOC42OTYgMTcuNDc4NloiIGZpbGw9IiNGRkM3MDAiLz4KPHBhdGggZD0iTTE2LjczNzYgMTEuOTcwNkwxOS44OTgxIDE0LjU3MDZMMjAuODgzIDEzLjM4MjNMMTkuMTY2MSAxMS45NzA2TDIwLjg4MyAxMC41NTg4TDE5Ljg5ODEgOS4zNzA1NkwxNi43Mzc2IDExLjk3MDZaIiBmaWxsPSIjRkZDNzAwIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjAzNF8zMzYyNyI+CjxyZWN0IHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K" + }, + "f3809540-7f14-49c1-a8b3-8f813b225541": { + "name": "Enpass", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTYuNDgzIDI4LjA1NTRDMzEzLjg5OSAyOC4wNTU0IDM3MS4zMTUgMjcuODg1NiA0MjguNzQ1IDI4LjE0MDVDNDQwLjY4IDI3LjkwNzYgNDUyLjUyMSAzMC4yODg5IDQ2My40NDEgMzUuMTE3OUM0NzQuMzYyIDM5Ljk0NjkgNDg0LjA5OSA0Ny4xMDczIDQ5MS45NzEgNTYuMDk4NEM1MDQuMDYyIDY5LjY5NjIgNTExLjEzMiA4Ny4wMzg3IDUxMiAxMDUuMjNDNTEyLjAyOCAxMjEuOTMzIDUxMC4wMzUgMTM4LjU3OCA1MDYuMDYzIDE1NC44MDFDNDk4LjQ0NCAxOTguNzA2IDQ5MC41MTUgMjQyLjUyNyA0ODIuNzI2IDI4Ni4zNzZDNDc2LjAxMiAzMjQuMTM0IDQ2OS41ODEgMzYxLjk1IDQ2Mi41MTMgMzk5LjY4QzQ1Ny42NzIgNDIwLjk2OSA0NDYuNTQ1IDQ0MC4zMDMgNDMwLjU4IDQ1NS4xNjVDNDE0LjYxNiA0NzAuMDI3IDM5NC41NTUgNDc5LjcyNyAzNzMuMDExIDQ4My4wMDJDMzY3Ljc1MiA0ODMuNjI5IDM2Mi40NjIgNDgzLjk0NiAzNTcuMTY2IDQ4My45NUMyOTAuMDUzIDQ4NC4wMTcgMjIyLjk0IDQ4NC4wMTcgMTU1LjgyOCA0ODMuOTVDMTMwLjQ2NiA0ODMuOSAxMDUuOTMgNDc0LjkxNSA4Ni41MTMyIDQ1OC41NjZDNjcuMDk2NSA0NDIuMjE4IDU0LjAzNjIgNDE5LjU0OCA0OS42MTggMzk0LjUyNUMzNi4xODA0IDMxOS4xNzcgMjIuNjI5NyAyNDMuODUzIDguOTY1OTcgMTY4LjU1M0M2LjI4MDM0IDE1My42MzkgMy4zMTIgMTM4LjgxMSAxLjIwNTkgMTIzLjc4NEMtMi40NjEwNSAxMDIuNzI5IDIuMzEwOTMgODEuMDc0NCAxNC40ODUyIDYzLjUyNDRDMjYuNjU5NiA0NS45NzQ1IDQ1LjI1MjkgMzMuOTQ2MiA2Ni4yMjY2IDMwLjA1MjVDNzMuMDU1NyAyOC43NDUxIDc5Ljk5NTkgMjguMTA5NCA4Ni45NDg0IDI4LjE1NDZDMTQzLjQ2IDI3Ljk5NDEgMTk5Ljk3MSAyNy45NjEgMjU2LjQ4MyAyOC4wNTU0Wk0yMTAuOTI2IDMzOS42NDNDMjEwLjkyNiAzNTQuNjcgMjEwLjkyNiAzNjkuNjk3IDIxMC45MjYgMzg0LjczOEMyMTAuNzczIDM4OC4yMDUgMjExLjM0MyAzOTEuNjY1IDIxMi41OTcgMzk0Ljg5OUMyMTMuODUyIDM5OC4xMzQgMjE1Ljc2NCA0MDEuMDcxIDIxOC4yMTMgNDAzLjUyNUMyMjAuNjYyIDQwNS45NzkgMjIzLjU5MyA0MDcuODk1IDIyNi44MjEgNDA5LjE1MkMyMzAuMDQ5IDQxMC40MDkgMjMzLjUwMyA0MTAuOTc5IDIzNi45NjIgNDEwLjgyNkMyNDkuMzg3IDQxMC44MjYgMjYxLjgxMiA0MTAuODI2IDI3NC4yMzYgNDEwLjgyNkMyNzcuOTIyIDQxMS4xODMgMjgxLjY0MiA0MTAuNzE3IDI4NS4xMjcgNDA5LjQ2MkMyODguNjEyIDQwOC4yMDggMjkxLjc3NyA0MDYuMTk2IDI5NC4zOTQgNDAzLjU3QzI5Ny4wMTIgNDAwLjk0NSAyOTkuMDE3IDM5Ny43NzIgMzAwLjI2NSAzOTQuMjc4QzMwMS41MTQgMzkwLjc4NSAzMDEuOTc1IDM4Ny4wNTggMzAxLjYxNSAzODMuMzY0QzMwMS42MTUgMzUzLjkxOSAzMDEuNjE2IDMyNC40NiAzMDEuNDc0IDI5NS4wMTVDMzAxLjMxMSAyOTMuMzMxIDMwMS42NyAyOTEuNjM3IDMwMi41MDIgMjkwLjE2NUMzMDMuMzM0IDI4OC42OTIgMzA0LjU5OSAyODcuNTEyIDMwNi4xMjUgMjg2Ljc4NkMzMjMuNzkgMjc2LjI5OCAzMzcuNTUxIDI2MC4zMTQgMzQ1LjMxMyAyNDEuMjY2QzM1My4wNzUgMjIyLjIxOSAzNTQuNDEzIDIwMS4xNTEgMzQ5LjEyMyAxODEuMjcyQzM0Mi4zNTYgMTU2Ljg1MyAzMjYuMjg2IDEzNi4wNzUgMzA0LjM3NiAxMjMuNDE0QzI4Mi40NjYgMTEwLjc1NCAyNTYuNDY5IDEwNy4yMjUgMjMxLjk4NyAxMTMuNTg2QzIxNy42NjkgMTE2LjU0NCAyMDQuMjg5IDEyMi45NTkgMTkzLjAwNyAxMzIuMjc0QzE4MS43MjYgMTQxLjU4OCAxNzIuODg0IDE1My41MjIgMTY3LjI0OSAxNjcuMDM4QzE1OS4wMjcgMTg4LjY4NiAxNTguNTQ4IDIxMi41MjEgMTY1Ljg5MyAyMzQuNDg0QzE3My4yMzggMjU2LjQ0NyAxODcuOTU0IDI3NS4xODEgMjA3LjUzMyAyODcuNDk1QzIwOC42NyAyODguMDM4IDIwOS42MTMgMjg4LjkxNyAyMTAuMjM3IDI5MC4wMTNDMjEwLjg2MSAyOTEuMTA5IDIxMS4xMzYgMjkyLjM3IDIxMS4wMjUgMjkzLjYyN0MyMTAuODQxIDMwOS4wMDggMjEwLjkyNiAzMjQuMzMzIDIxMC45MjYgMzM5LjY3MVYzMzkuNjQzWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTYuNDgzIDI4LjA1NTRDMzEzLjg5OSAyOC4wNTU0IDM3MS4zMTUgMjcuODg1NiA0MjguNzQ1IDI4LjE0MDVDNDQwLjY4IDI3LjkwNzYgNDUyLjUyMSAzMC4yODg5IDQ2My40NDEgMzUuMTE3OUM0NzQuMzYyIDM5Ljk0NjkgNDg0LjA5OSA0Ny4xMDczIDQ5MS45NzEgNTYuMDk4NEM1MDQuMDYyIDY5LjY5NjIgNTExLjEzMiA4Ny4wMzg3IDUxMiAxMDUuMjNDNTEyLjAyOCAxMjEuOTMzIDUxMC4wMzUgMTM4LjU3OCA1MDYuMDYzIDE1NC44MDFDNDk4LjQ0NCAxOTguNzA2IDQ5MC41MTUgMjQyLjUyNyA0ODIuNzI2IDI4Ni4zNzZDNDc2LjAxMiAzMjQuMTM0IDQ2OS41ODEgMzYxLjk1IDQ2Mi41MTMgMzk5LjY4QzQ1Ny42NzIgNDIwLjk2OSA0NDYuNTQ1IDQ0MC4zMDMgNDMwLjU4IDQ1NS4xNjVDNDE0LjYxNiA0NzAuMDI3IDM5NC41NTUgNDc5LjcyNyAzNzMuMDExIDQ4My4wMDJDMzY3Ljc1MiA0ODMuNjI5IDM2Mi40NjIgNDgzLjk0NiAzNTcuMTY2IDQ4My45NUMyOTAuMDUzIDQ4NC4wMTcgMjIyLjk0IDQ4NC4wMTcgMTU1LjgyOCA0ODMuOTVDMTMwLjQ2NiA0ODMuOSAxMDUuOTMgNDc0LjkxNSA4Ni41MTMyIDQ1OC41NjZDNjcuMDk2NSA0NDIuMjE4IDU0LjAzNjIgNDE5LjU0OCA0OS42MTggMzk0LjUyNUMzNi4xODA0IDMxOS4xNzcgMjIuNjI5NyAyNDMuODUzIDguOTY1OTcgMTY4LjU1M0M2LjI4MDM0IDE1My42MzkgMy4zMTIgMTM4LjgxMSAxLjIwNTkgMTIzLjc4NEMtMi40NjEwNSAxMDIuNzI5IDIuMzEwOTMgODEuMDc0NCAxNC40ODUyIDYzLjUyNDRDMjYuNjU5NiA0NS45NzQ1IDQ1LjI1MjkgMzMuOTQ2MiA2Ni4yMjY2IDMwLjA1MjVDNzMuMDU1NyAyOC43NDUxIDc5Ljk5NTkgMjguMTA5NCA4Ni45NDg0IDI4LjE1NDZDMTQzLjQ2IDI3Ljk5NDEgMTk5Ljk3MSAyNy45NjEgMjU2LjQ4MyAyOC4wNTU0Wk0yMTAuOTI2IDMzOS42NDNDMjEwLjkyNiAzNTQuNjcgMjEwLjkyNiAzNjkuNjk3IDIxMC45MjYgMzg0LjczOEMyMTAuNzczIDM4OC4yMDUgMjExLjM0MyAzOTEuNjY1IDIxMi41OTcgMzk0Ljg5OUMyMTMuODUyIDM5OC4xMzQgMjE1Ljc2NCA0MDEuMDcxIDIxOC4yMTMgNDAzLjUyNUMyMjAuNjYyIDQwNS45NzkgMjIzLjU5MyA0MDcuODk1IDIyNi44MjEgNDA5LjE1MkMyMzAuMDQ5IDQxMC40MDkgMjMzLjUwMyA0MTAuOTc5IDIzNi45NjIgNDEwLjgyNkMyNDkuMzg3IDQxMC44MjYgMjYxLjgxMiA0MTAuODI2IDI3NC4yMzYgNDEwLjgyNkMyNzcuOTIyIDQxMS4xODMgMjgxLjY0MiA0MTAuNzE3IDI4NS4xMjcgNDA5LjQ2MkMyODguNjEyIDQwOC4yMDggMjkxLjc3NyA0MDYuMTk2IDI5NC4zOTQgNDAzLjU3QzI5Ny4wMTIgNDAwLjk0NSAyOTkuMDE3IDM5Ny43NzIgMzAwLjI2NSAzOTQuMjc4QzMwMS41MTQgMzkwLjc4NSAzMDEuOTc1IDM4Ny4wNTggMzAxLjYxNSAzODMuMzY0QzMwMS42MTUgMzUzLjkxOSAzMDEuNjE2IDMyNC40NiAzMDEuNDc0IDI5NS4wMTVDMzAxLjMxMSAyOTMuMzMxIDMwMS42NyAyOTEuNjM3IDMwMi41MDIgMjkwLjE2NUMzMDMuMzM0IDI4OC42OTIgMzA0LjU5OSAyODcuNTEyIDMwNi4xMjUgMjg2Ljc4NkMzMjMuNzkgMjc2LjI5OCAzMzcuNTUxIDI2MC4zMTQgMzQ1LjMxMyAyNDEuMjY2QzM1My4wNzUgMjIyLjIxOSAzNTQuNDEzIDIwMS4xNTEgMzQ5LjEyMyAxODEuMjcyQzM0Mi4zNTYgMTU2Ljg1MyAzMjYuMjg2IDEzNi4wNzUgMzA0LjM3NiAxMjMuNDE0QzI4Mi40NjYgMTEwLjc1NCAyNTYuNDY5IDEwNy4yMjUgMjMxLjk4NyAxMTMuNTg2QzIxNy42NjkgMTE2LjU0NCAyMDQuMjg5IDEyMi45NTkgMTkzLjAwNyAxMzIuMjc0QzE4MS43MjYgMTQxLjU4OCAxNzIuODg0IDE1My41MjIgMTY3LjI0OSAxNjcuMDM4QzE1OS4wMjcgMTg4LjY4NiAxNTguNTQ4IDIxMi41MjEgMTY1Ljg5MyAyMzQuNDg0QzE3My4yMzggMjU2LjQ0NyAxODcuOTU0IDI3NS4xODEgMjA3LjUzMyAyODcuNDk1QzIwOC42NyAyODguMDM4IDIwOS42MTMgMjg4LjkxNyAyMTAuMjM3IDI5MC4wMTNDMjEwLjg2MSAyOTEuMTA5IDIxMS4xMzYgMjkyLjM3IDIxMS4wMjUgMjkzLjYyN0MyMTAuODQxIDMwOS4wMDggMjEwLjkyNiAzMjQuMzMzIDIxMC45MjYgMzM5LjY3MVYzMzkuNjQzWiIgZmlsbD0iIzBEMzM4RiIvPgo8L3N2Zz4K" + }, + "b5397666-4885-aa6b-cebf-e52262a439a2": { + "name": "Chromium Browser" + }, + "771b48fd-d3d4-4f74-9232-fc157ab0507a": { + "name": "Edge on Mac", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye29wYWNpdHk6MC4zNTtmaWxsOnVybCgjcmFkaWFsLWdyYWRpZW50KTt9LmNscy0yLC5jbHMtNHtpc29sYXRpb246aXNvbGF0ZTt9LmNscy0ze2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtMik7fS5jbHMtNHtvcGFjaXR5OjAuNDE7ZmlsbDp1cmwoI3JhZGlhbC1ncmFkaWVudC0yKTt9LmNscy01e2ZpbGw6dXJsKCNyYWRpYWwtZ3JhZGllbnQtMyk7fS5jbHMtNntmaWxsOnVybCgjcmFkaWFsLWdyYWRpZW50LTQpO308L3N0eWxlPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iNjMuMzMiIHkxPSI4NC4wMyIgeDI9IjI0MS42NyIgeTI9Ijg0LjAzIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsIDAsIDAsIC0xLCAwLCAyNjYpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMGM1OWE0Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTE0YThiIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9InJhZGlhbC1ncmFkaWVudCIgY3g9IjE2MS44MyIgY3k9IjY4LjkxIiByPSI5NS4zOCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLCAwLCAwLCAtMC45NSwgMCwgMjQ4Ljg0KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMC43MiIgc3RvcC1vcGFjaXR5PSIwIi8+PHN0b3Agb2Zmc2V0PSIwLjk1IiBzdG9wLW9wYWNpdHk9IjAuNTMiLz48c3RvcCBvZmZzZXQ9IjEiLz48L3JhZGlhbEdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIxNTcuMzUiIHkxPSIxNjEuMzkiIHgyPSI0NS45NiIgeTI9IjQwLjA2IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsIDAsIDAsIC0xLCAwLCAyNjYpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMWI5ZGUyIi8+PHN0b3Agb2Zmc2V0PSIwLjE2IiBzdG9wLWNvbG9yPSIjMTU5NWRmIi8+PHN0b3Agb2Zmc2V0PSIwLjY3IiBzdG9wLWNvbG9yPSIjMDY4MGQ3Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDA3OGQ0Ii8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9InJhZGlhbC1ncmFkaWVudC0yIiBjeD0iLTM0MC4yOSIgY3k9IjYyLjk5IiByPSIxNDMuMjQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4xNSwgLTAuOTksIC0wLjgsIC0wLjEyLCAxNzYuNjQsIC0xMjUuNCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAuNzYiIHN0b3Atb3BhY2l0eT0iMCIvPjxzdG9wIG9mZnNldD0iMC45NSIgc3RvcC1vcGFjaXR5PSIwLjUiLz48c3RvcCBvZmZzZXQ9IjEiLz48L3JhZGlhbEdyYWRpZW50PjxyYWRpYWxHcmFkaWVudCBpZD0icmFkaWFsLWdyYWRpZW50LTMiIGN4PSIxMTMuMzciIGN5PSI1NzAuMjEiIHI9IjIwMi40MyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtMC4wNCwgMSwgMi4xMywgMC4wOCwgLTExNzkuNTQsIC0xMDYuNjkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzVjMWYxIi8+PHN0b3Agb2Zmc2V0PSIwLjExIiBzdG9wLWNvbG9yPSIjMzRjMWVkIi8+PHN0b3Agb2Zmc2V0PSIwLjIzIiBzdG9wLWNvbG9yPSIjMmZjMmRmIi8+PHN0b3Agb2Zmc2V0PSIwLjMxIiBzdG9wLWNvbG9yPSIjMmJjM2QyIi8+PHN0b3Agb2Zmc2V0PSIwLjY3IiBzdG9wLWNvbG9yPSIjMzZjNzUyIi8+PC9yYWRpYWxHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9InJhZGlhbC1ncmFkaWVudC00IiBjeD0iMzc2LjUyIiBjeT0iNTY3Ljk3IiByPSI5Ny4zNCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwLjI4LCAwLjk2LCAwLjc4LCAtMC4yMywgLTMwMy43NiwgLTE0OC41KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzY2ZWI2ZSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzY2ZWI2ZSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9yYWRpYWxHcmFkaWVudD48L2RlZnM+PHRpdGxlPkVkZ2VfTG9nb18yNjV4MjY1PC90aXRsZT48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0yMzUuNjgsMTk1LjQ2YTkzLjczLDkzLjczLDAsMCwxLTEwLjU0LDQuNzEsMTAxLjg3LDEwMS44NywwLDAsMS0zNS45LDYuNDZjLTQ3LjMyLDAtODguNTQtMzIuNTUtODguNTQtNzQuMzJBMzEuNDgsMzEuNDgsMCwwLDEsMTE3LjEzLDEwNWMtNDIuOCwxLjgtNTMuOCw0Ni40LTUzLjgsNzIuNTMsMCw3My44OCw2OC4wOSw4MS4zNyw4Mi43Niw4MS4zNyw3LjkxLDAsMTkuODQtMi4zLDI3LTQuNTZsMS4zMS0uNDRBMTI4LjM0LDEyOC4zNCwwLDAsMCwyNDEsMjAxLjEsNCw0LDAsMCwwLDIzNS42OCwxOTUuNDZaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42MyAtNC45MikiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yMzUuNjgsMTk1LjQ2YTkzLjczLDkzLjczLDAsMCwxLTEwLjU0LDQuNzEsMTAxLjg3LDEwMS44NywwLDAsMS0zNS45LDYuNDZjLTQ3LjMyLDAtODguNTQtMzIuNTUtODguNTQtNzQuMzJBMzEuNDgsMzEuNDgsMCwwLDEsMTE3LjEzLDEwNWMtNDIuOCwxLjgtNTMuOCw0Ni40LTUzLjgsNzIuNTMsMCw3My44OCw2OC4wOSw4MS4zNyw4Mi43Niw4MS4zNyw3LjkxLDAsMTkuODQtMi4zLDI3LTQuNTZsMS4zMS0uNDRBMTI4LjM0LDEyOC4zNCwwLDAsMCwyNDEsMjAxLjEsNCw0LDAsMCwwLDIzNS42OCwxOTUuNDZaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42MyAtNC45MikiLz48cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0xMTAuMzQsMjQ2LjM0QTc5LjIsNzkuMiwwLDAsMSw4Ny42LDIyNSw4MC43Miw4MC43MiwwLDAsMSwxMTcuMTMsMTA1YzMuMTItMS40Nyw4LjQ1LTQuMTMsMTUuNTQtNGEzMi4zNSwzMi4zNSwwLDAsMSwyNS42OSwxMywzMS44OCwzMS44OCwwLDAsMSw2LjM2LDE4LjY2YzAtLjIxLDI0LjQ2LTc5LjYtODAtNzkuNi00My45LDAtODAsNDEuNjYtODAsNzguMjFhMTMwLjE1LDEzMC4xNSwwLDAsMCwxMi4xMSw1NiwxMjgsMTI4LDAsMCwwLDE1Ni4zOCw2Ny4xMSw3NS41NSw3NS41NSwwLDAsMS02Mi43OC04WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQuNjMgLTQuOTIpIi8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNMTEwLjM0LDI0Ni4zNEE3OS4yLDc5LjIsMCwwLDEsODcuNiwyMjUsODAuNzIsODAuNzIsMCwwLDEsMTE3LjEzLDEwNWMzLjEyLTEuNDcsOC40NS00LjEzLDE1LjU0LTRhMzIuMzUsMzIuMzUsMCwwLDEsMjUuNjksMTMsMzEuODgsMzEuODgsMCwwLDEsNi4zNiwxOC42NmMwLS4yMSwyNC40Ni03OS42LTgwLTc5LjYtNDMuOSwwLTgwLDQxLjY2LTgwLDc4LjIxYTEzMC4xNSwxMzAuMTUsMCwwLDAsMTIuMTEsNTYsMTI4LDEyOCwwLDAsMCwxNTYuMzgsNjcuMTEsNzUuNTUsNzUuNTUsMCwwLDEtNjIuNzgtOFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00LjYzIC00LjkyKSIvPjxwYXRoIGNsYXNzPSJjbHMtNSIgZD0iTTE1Ni45NCwxNTMuNzhjLS44MSwxLjA1LTMuMywyLjUtMy4zLDUuNjYsMCwyLjYxLDEuNyw1LjEyLDQuNzIsNy4yMywxNC4zOCwxMCw0MS40OSw4LjY4LDQxLjU2LDguNjhBNTkuNTYsNTkuNTYsMCwwLDAsMjMwLjE5LDE2N2E2MS4zOCw2MS4zOCwwLDAsMCwzMC40My01Mi44OGMuMjYtMjIuNDEtOC0zNy4zMS0xMS4zNC00My45MUMyMjguMDksMjguNzYsMTgyLjM1LDQuOTIsMTMyLjYxLDQuOTJhMTI4LDEyOCwwLDAsMC0xMjgsMTI2LjJjLjQ4LTM2LjU0LDM2LjgtNjYuMDUsODAtNjYuMDUsMy41LDAsMjMuNDYuMzQsNDIsMTAuMDcsMTYuMzQsOC41OCwyNC45LDE4Ljk0LDMwLjg1LDI5LjIxLDYuMTgsMTAuNjcsNy4yOCwyNC4xNSw3LjI4LDI5LjUyUzE2MiwxNDcuMiwxNTYuOTQsMTUzLjc4WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQuNjMgLTQuOTIpIi8+PHBhdGggY2xhc3M9ImNscy02IiBkPSJNMTU2Ljk0LDE1My43OGMtLjgxLDEuMDUtMy4zLDIuNS0zLjMsNS42NiwwLDIuNjEsMS43LDUuMTIsNC43Miw3LjIzLDE0LjM4LDEwLDQxLjQ5LDguNjgsNDEuNTYsOC42OEE1OS41Niw1OS41NiwwLDAsMCwyMzAuMTksMTY3YTYxLjM4LDYxLjM4LDAsMCwwLDMwLjQzLTUyLjg4Yy4yNi0yMi40MS04LTM3LjMxLTExLjM0LTQzLjkxQzIyOC4wOSwyOC43NiwxODIuMzUsNC45MiwxMzIuNjEsNC45MmExMjgsMTI4LDAsMCwwLTEyOCwxMjYuMmMuNDgtMzYuNTQsMzYuOC02Ni4wNSw4MC02Ni4wNSwzLjUsMCwyMy40Ni4zNCw0MiwxMC4wNywxNi4zNCw4LjU4LDI0LjksMTguOTQsMzAuODUsMjkuMjEsNi4xOCwxMC42Nyw3LjI4LDI0LjE1LDcuMjgsMjkuNTJTMTYyLDE0Ny4yLDE1Ni45NCwxNTMuNzhaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42MyAtNC45MikiLz48L3N2Zz4=", + "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye29wYWNpdHk6MC4zNTtmaWxsOnVybCgjcmFkaWFsLWdyYWRpZW50KTt9LmNscy0yLC5jbHMtNHtpc29sYXRpb246aXNvbGF0ZTt9LmNscy0ze2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtMik7fS5jbHMtNHtvcGFjaXR5OjAuNDE7ZmlsbDp1cmwoI3JhZGlhbC1ncmFkaWVudC0yKTt9LmNscy01e2ZpbGw6dXJsKCNyYWRpYWwtZ3JhZGllbnQtMyk7fS5jbHMtNntmaWxsOnVybCgjcmFkaWFsLWdyYWRpZW50LTQpO308L3N0eWxlPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iNjMuMzMiIHkxPSI4NC4wMyIgeDI9IjI0MS42NyIgeTI9Ijg0LjAzIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsIDAsIDAsIC0xLCAwLCAyNjYpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMGM1OWE0Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTE0YThiIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9InJhZGlhbC1ncmFkaWVudCIgY3g9IjE2MS44MyIgY3k9IjY4LjkxIiByPSI5NS4zOCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLCAwLCAwLCAtMC45NSwgMCwgMjQ4Ljg0KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMC43MiIgc3RvcC1vcGFjaXR5PSIwIi8+PHN0b3Agb2Zmc2V0PSIwLjk1IiBzdG9wLW9wYWNpdHk9IjAuNTMiLz48c3RvcCBvZmZzZXQ9IjEiLz48L3JhZGlhbEdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIxNTcuMzUiIHkxPSIxNjEuMzkiIHgyPSI0NS45NiIgeTI9IjQwLjA2IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsIDAsIDAsIC0xLCAwLCAyNjYpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMWI5ZGUyIi8+PHN0b3Agb2Zmc2V0PSIwLjE2IiBzdG9wLWNvbG9yPSIjMTU5NWRmIi8+PHN0b3Agb2Zmc2V0PSIwLjY3IiBzdG9wLWNvbG9yPSIjMDY4MGQ3Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDA3OGQ0Ii8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9InJhZGlhbC1ncmFkaWVudC0yIiBjeD0iLTM0MC4yOSIgY3k9IjYyLjk5IiByPSIxNDMuMjQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4xNSwgLTAuOTksIC0wLjgsIC0wLjEyLCAxNzYuNjQsIC0xMjUuNCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAuNzYiIHN0b3Atb3BhY2l0eT0iMCIvPjxzdG9wIG9mZnNldD0iMC45NSIgc3RvcC1vcGFjaXR5PSIwLjUiLz48c3RvcCBvZmZzZXQ9IjEiLz48L3JhZGlhbEdyYWRpZW50PjxyYWRpYWxHcmFkaWVudCBpZD0icmFkaWFsLWdyYWRpZW50LTMiIGN4PSIxMTMuMzciIGN5PSI1NzAuMjEiIHI9IjIwMi40MyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtMC4wNCwgMSwgMi4xMywgMC4wOCwgLTExNzkuNTQsIC0xMDYuNjkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzVjMWYxIi8+PHN0b3Agb2Zmc2V0PSIwLjExIiBzdG9wLWNvbG9yPSIjMzRjMWVkIi8+PHN0b3Agb2Zmc2V0PSIwLjIzIiBzdG9wLWNvbG9yPSIjMmZjMmRmIi8+PHN0b3Agb2Zmc2V0PSIwLjMxIiBzdG9wLWNvbG9yPSIjMmJjM2QyIi8+PHN0b3Agb2Zmc2V0PSIwLjY3IiBzdG9wLWNvbG9yPSIjMzZjNzUyIi8+PC9yYWRpYWxHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9InJhZGlhbC1ncmFkaWVudC00IiBjeD0iMzc2LjUyIiBjeT0iNTY3Ljk3IiByPSI5Ny4zNCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwLjI4LCAwLjk2LCAwLjc4LCAtMC4yMywgLTMwMy43NiwgLTE0OC41KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzY2ZWI2ZSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzY2ZWI2ZSIgc3RvcC1vcGFjaXR5PSIwIi8+PC9yYWRpYWxHcmFkaWVudD48L2RlZnM+PHRpdGxlPkVkZ2VfTG9nb18yNjV4MjY1PC90aXRsZT48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0yMzUuNjgsMTk1LjQ2YTkzLjczLDkzLjczLDAsMCwxLTEwLjU0LDQuNzEsMTAxLjg3LDEwMS44NywwLDAsMS0zNS45LDYuNDZjLTQ3LjMyLDAtODguNTQtMzIuNTUtODguNTQtNzQuMzJBMzEuNDgsMzEuNDgsMCwwLDEsMTE3LjEzLDEwNWMtNDIuOCwxLjgtNTMuOCw0Ni40LTUzLjgsNzIuNTMsMCw3My44OCw2OC4wOSw4MS4zNyw4Mi43Niw4MS4zNyw3LjkxLDAsMTkuODQtMi4zLDI3LTQuNTZsMS4zMS0uNDRBMTI4LjM0LDEyOC4zNCwwLDAsMCwyNDEsMjAxLjEsNCw0LDAsMCwwLDIzNS42OCwxOTUuNDZaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42MyAtNC45MikiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0yMzUuNjgsMTk1LjQ2YTkzLjczLDkzLjczLDAsMCwxLTEwLjU0LDQuNzEsMTAxLjg3LDEwMS44NywwLDAsMS0zNS45LDYuNDZjLTQ3LjMyLDAtODguNTQtMzIuNTUtODguNTQtNzQuMzJBMzEuNDgsMzEuNDgsMCwwLDEsMTE3LjEzLDEwNWMtNDIuOCwxLjgtNTMuOCw0Ni40LTUzLjgsNzIuNTMsMCw3My44OCw2OC4wOSw4MS4zNyw4Mi43Niw4MS4zNyw3LjkxLDAsMTkuODQtMi4zLDI3LTQuNTZsMS4zMS0uNDRBMTI4LjM0LDEyOC4zNCwwLDAsMCwyNDEsMjAxLjEsNCw0LDAsMCwwLDIzNS42OCwxOTUuNDZaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42MyAtNC45MikiLz48cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0xMTAuMzQsMjQ2LjM0QTc5LjIsNzkuMiwwLDAsMSw4Ny42LDIyNSw4MC43Miw4MC43MiwwLDAsMSwxMTcuMTMsMTA1YzMuMTItMS40Nyw4LjQ1LTQuMTMsMTUuNTQtNGEzMi4zNSwzMi4zNSwwLDAsMSwyNS42OSwxMywzMS44OCwzMS44OCwwLDAsMSw2LjM2LDE4LjY2YzAtLjIxLDI0LjQ2LTc5LjYtODAtNzkuNi00My45LDAtODAsNDEuNjYtODAsNzguMjFhMTMwLjE1LDEzMC4xNSwwLDAsMCwxMi4xMSw1NiwxMjgsMTI4LDAsMCwwLDE1Ni4zOCw2Ny4xMSw3NS41NSw3NS41NSwwLDAsMS02Mi43OC04WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQuNjMgLTQuOTIpIi8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNMTEwLjM0LDI0Ni4zNEE3OS4yLDc5LjIsMCwwLDEsODcuNiwyMjUsODAuNzIsODAuNzIsMCwwLDEsMTE3LjEzLDEwNWMzLjEyLTEuNDcsOC40NS00LjEzLDE1LjU0LTRhMzIuMzUsMzIuMzUsMCwwLDEsMjUuNjksMTMsMzEuODgsMzEuODgsMCwwLDEsNi4zNiwxOC42NmMwLS4yMSwyNC40Ni03OS42LTgwLTc5LjYtNDMuOSwwLTgwLDQxLjY2LTgwLDc4LjIxYTEzMC4xNSwxMzAuMTUsMCwwLDAsMTIuMTEsNTYsMTI4LDEyOCwwLDAsMCwxNTYuMzgsNjcuMTEsNzUuNTUsNzUuNTUsMCwwLDEtNjIuNzgtOFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00LjYzIC00LjkyKSIvPjxwYXRoIGNsYXNzPSJjbHMtNSIgZD0iTTE1Ni45NCwxNTMuNzhjLS44MSwxLjA1LTMuMywyLjUtMy4zLDUuNjYsMCwyLjYxLDEuNyw1LjEyLDQuNzIsNy4yMywxNC4zOCwxMCw0MS40OSw4LjY4LDQxLjU2LDguNjhBNTkuNTYsNTkuNTYsMCwwLDAsMjMwLjE5LDE2N2E2MS4zOCw2MS4zOCwwLDAsMCwzMC40My01Mi44OGMuMjYtMjIuNDEtOC0zNy4zMS0xMS4zNC00My45MUMyMjguMDksMjguNzYsMTgyLjM1LDQuOTIsMTMyLjYxLDQuOTJhMTI4LDEyOCwwLDAsMC0xMjgsMTI2LjJjLjQ4LTM2LjU0LDM2LjgtNjYuMDUsODAtNjYuMDUsMy41LDAsMjMuNDYuMzQsNDIsMTAuMDcsMTYuMzQsOC41OCwyNC45LDE4Ljk0LDMwLjg1LDI5LjIxLDYuMTgsMTAuNjcsNy4yOCwyNC4xNSw3LjI4LDI5LjUyUzE2MiwxNDcuMiwxNTYuOTQsMTUzLjc4WiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQuNjMgLTQuOTIpIi8+PHBhdGggY2xhc3M9ImNscy02IiBkPSJNMTU2Ljk0LDE1My43OGMtLjgxLDEuMDUtMy4zLDIuNS0zLjMsNS42NiwwLDIuNjEsMS43LDUuMTIsNC43Miw3LjIzLDE0LjM4LDEwLDQxLjQ5LDguNjgsNDEuNTYsOC42OEE1OS41Niw1OS41NiwwLDAsMCwyMzAuMTksMTY3YTYxLjM4LDYxLjM4LDAsMCwwLDMwLjQzLTUyLjg4Yy4yNi0yMi40MS04LTM3LjMxLTExLjM0LTQzLjkxQzIyOC4wOSwyOC43NiwxODIuMzUsNC45MiwxMzIuNjEsNC45MmExMjgsMTI4LDAsMCwwLTEyOCwxMjYuMmMuNDgtMzYuNTQsMzYuOC02Ni4wNSw4MC02Ni4wNSwzLjUsMCwyMy40Ni4zNCw0MiwxMC4wNywxNi4zNCw4LjU4LDI0LjksMTguOTQsMzAuODUsMjkuMjEsNi4xOCwxMC42Nyw3LjI4LDI0LjE1LDcuMjgsMjkuNTJTMTYyLDE0Ny4yLDE1Ni45NCwxNTMuNzhaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42MyAtNC45MikiLz48L3N2Zz4=" + }, + "39a5647e-1853-446c-a1f6-a79bae9f5bc7": { + "name": "IDmelon", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2YxNWI1Yzt9LmNscy0ye2ZpbGw6IzkyMWIxZDt9LmNscy0ze2ZpbGw6I2VlMzAyNTt9LmNscy00e2ZpbGw6I2JiMjAyNjt9PC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMzQxLjg3LDEwMC4ybC00LjI5LTEuNjRjLTMyLjMxLTExLjgxLTY1LjM2LTEzLjI3LTc2LjkyLTEzLjRsLS44OSwwSDEyMC4xMkEyMy40MywyMy40MywwLDAsMCwxMTEuNiw4N2MtLjQxLjIxLS44MS40Mi0xLjE3LjY0bC0xLjg1LDEuNzYsMTMzLjM1LDY1LjgsMTAzLjM4LTUyLjg5WiIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjI5NC41OCIgeTE9IjEzNy4wNyIgeDI9IjI5Ni45OSIgeTI9IjEzOC4yNyIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjIzOS41MyIgeTE9IjE1Mi4xMSIgeDI9IjI0MS45MyIgeTI9IjE1My4zMSIvPjxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTEwNi43NCw5MXEtMi42Miw0LjItMi4zNSwxMS4yNnQuMjYsMTMuMzdWNDIzLjIxcTAsNS43Ni0uMjYsMTQuNDF0MS4zMSwxMi44NGExNC41NSwxNC41NSwwLDAsMCwxLjE0LDIuMTlsMTM2LTI5OS41NkwxMTAuNDMsODcuNjVBMTEuMjQsMTEuMjQsMCwwLDAsMTA2Ljc0LDkxWiIvPjxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTM2MS44NiwxMTEuNTNjLTIuMzItMS41NS00LjctMy4xLTcuMTMtNC42OGE5My45Miw5My45MiwwLDAsMC0xMi02LjMyYy0uMjctLjExLS41NS0uMjMtLjgzLS4zM2wtOTksNTIuODlMMzg3LjYzLDQwMi4zMUExNjQuMDcsMTY0LjA3LDAsMCwwLDM5NywzODguMTJxMjkuODItNTEuMjEsMjkuODItMTI1YTI4NC44MywyODQuODMsMCwwLDAtNy4wOC02MS4yNSwxNjQuMTYsMTY0LjE2LDAsMCwwLTI2LjUzLTU5Ljc1LDEzNC45LDEzNC45LDAsMCwwLTkuMDUtMTEuMzhBMTUzLjIsMTUzLjIsMCwwLDAsMzYxLjg2LDExMS41M1oiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xMDYuNjUsNDUyLjM0YTEwLjA3LDEwLjA3LDAsMCwwLDcuNjksNS4xOWwxLjc0LjJoMTU2YzUwLjI3LDAsODguNjQtMTguNjksMTE1LjUyLTU1LjQyTDI0Mi44OSwxNTMuMDlaIi8+PC9zdmc+", + "icon_light": "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2YxNWI1Yzt9LmNscy0ye2ZpbGw6IzkyMWIxZDt9LmNscy0ze2ZpbGw6I2VlMzAyNTt9LmNscy00e2ZpbGw6I2JiMjAyNjt9PC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMzQxLjg3LDEwMC4ybC00LjI5LTEuNjRjLTMyLjMxLTExLjgxLTY1LjM2LTEzLjI3LTc2LjkyLTEzLjRsLS44OSwwSDEyMC4xMkEyMy40MywyMy40MywwLDAsMCwxMTEuNiw4N2MtLjQxLjIxLS44MS40Mi0xLjE3LjY0bC0xLjg1LDEuNzYsMTMzLjM1LDY1LjgsMTAzLjM4LTUyLjg5WiIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjI5NC41OCIgeTE9IjEzNy4wNyIgeDI9IjI5Ni45OSIgeTI9IjEzOC4yNyIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjIzOS41MyIgeTE9IjE1Mi4xMSIgeDI9IjI0MS45MyIgeTI9IjE1My4zMSIvPjxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTEwNi43NCw5MXEtMi42Miw0LjItMi4zNSwxMS4yNnQuMjYsMTMuMzdWNDIzLjIxcTAsNS43Ni0uMjYsMTQuNDF0MS4zMSwxMi44NGExNC41NSwxNC41NSwwLDAsMCwxLjE0LDIuMTlsMTM2LTI5OS41NkwxMTAuNDMsODcuNjVBMTEuMjQsMTEuMjQsMCwwLDAsMTA2Ljc0LDkxWiIvPjxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTM2MS44NiwxMTEuNTNjLTIuMzItMS41NS00LjctMy4xLTcuMTMtNC42OGE5My45Miw5My45MiwwLDAsMC0xMi02LjMyYy0uMjctLjExLS41NS0uMjMtLjgzLS4zM2wtOTksNTIuODlMMzg3LjYzLDQwMi4zMUExNjQuMDcsMTY0LjA3LDAsMCwwLDM5NywzODguMTJxMjkuODItNTEuMjEsMjkuODItMTI1YTI4NC44MywyODQuODMsMCwwLDAtNy4wOC02MS4yNSwxNjQuMTYsMTY0LjE2LDAsMCwwLTI2LjUzLTU5Ljc1LDEzNC45LDEzNC45LDAsMCwwLTkuMDUtMTEuMzhBMTUzLjIsMTUzLjIsMCwwLDAsMzYxLjg2LDExMS41M1oiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xMDYuNjUsNDUyLjM0YTEwLjA3LDEwLjA3LDAsMCwwLDcuNjksNS4xOWwxLjc0LjJoMTU2YzUwLjI3LDAsODguNjQtMTguNjksMTE1LjUyLTU1LjQyTDI0Mi44OSwxNTMuMDlaIi8+PC9zdmc+" + }, + "d548826e-79b4-db40-a3d8-11116f7e8349": { + "name": "Bitwarden", + "icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9Ikljb24iIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEwMjQgMTAyNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiMxNzVEREM7fQoJLnN0MXtmaWxsOiNGRkZGRkY7fQo8L3N0eWxlPgo8cmVjdCBpZD0iQmFja2dyb3VuZCIgY2xhc3M9InN0MCIgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIvPgo8cGF0aCBpZD0iSWRlbnRpdHkiIGNsYXNzPSJzdDEiIGQ9Ik04MjkuOCwxMjguNmMtNi41LTYuNS0xNC4yLTkuNy0yMy05LjdIMjE3LjJjLTguOSwwLTE2LjUsMy4yLTIzLDkuN3MtOS43LDE0LjItOS43LDIzdjM5My4xCgljMCwyOS4zLDUuNyw1OC40LDE3LjEsODcuM2MxMS40LDI4LjgsMjUuNiw1NC40LDQyLjUsNzYuOGMxNi45LDIyLjMsMzcsNDQuMSw2MC40LDY1LjNzNDUsMzguNyw2NC43LDUyLjcKCWMxOS44LDE0LDQwLjQsMjcuMiw2MS45LDM5LjdzMzYuOCwyMC45LDQ1LjgsMjUuM2M5LDQuNCwxNi4zLDcuOSwyMS43LDEwLjJjNC4xLDIsOC41LDMuMSwxMy4zLDMuMWM0LjgsMCw5LjItMSwxMy4zLTMuMQoJYzUuNS0yLjQsMTIuNy01LjgsMjEuOC0xMC4yYzktNC40LDI0LjMtMTIuOSw0NS44LTI1LjNjMjEuNS0xMi41LDQyLjEtMjUuNyw2MS45LTM5LjdjMTkuOC0xNCw0MS40LTMxLjYsNjQuOC01Mi43CgljMjMuNC0yMS4yLDQzLjUtNDIuOSw2MC40LTY1LjNjMTYuOS0yMi40LDMxLTQ3LjksNDIuNS03Ni44YzExLjQtMjguOCwxNy4xLTU3LjksMTcuMS04Ny4zdi0zOTMKCUM4MzkuNiwxNDIuOCw4MzYuMywxMzUuMSw4MjkuOCwxMjguNnogTTc1My44LDU0OC40YzAsMTQyLjMtMjQxLjgsMjY0LjktMjQxLjgsMjY0LjlWMjAzaDI0MS44Qzc1My44LDIwMyw3NTMuOCw0MDYuMSw3NTMuOCw1NDguNHoKCSIvPgo8L3N2Zz4K", + "icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMywgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9Ikljb24iIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEwMjQgMTAyNDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiMxNzVEREM7fQoJLnN0MXtmaWxsOiNGRkZGRkY7fQo8L3N0eWxlPgo8cmVjdCBpZD0iQmFja2dyb3VuZCIgY2xhc3M9InN0MCIgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIvPgo8cGF0aCBpZD0iSWRlbnRpdHkiIGNsYXNzPSJzdDEiIGQ9Ik04MjkuOCwxMjguNmMtNi41LTYuNS0xNC4yLTkuNy0yMy05LjdIMjE3LjJjLTguOSwwLTE2LjUsMy4yLTIzLDkuN3MtOS43LDE0LjItOS43LDIzdjM5My4xCgljMCwyOS4zLDUuNyw1OC40LDE3LjEsODcuM2MxMS40LDI4LjgsMjUuNiw1NC40LDQyLjUsNzYuOGMxNi45LDIyLjMsMzcsNDQuMSw2MC40LDY1LjNzNDUsMzguNyw2NC43LDUyLjcKCWMxOS44LDE0LDQwLjQsMjcuMiw2MS45LDM5LjdzMzYuOCwyMC45LDQ1LjgsMjUuM2M5LDQuNCwxNi4zLDcuOSwyMS43LDEwLjJjNC4xLDIsOC41LDMuMSwxMy4zLDMuMWM0LjgsMCw5LjItMSwxMy4zLTMuMQoJYzUuNS0yLjQsMTIuNy01LjgsMjEuOC0xMC4yYzktNC40LDI0LjMtMTIuOSw0NS44LTI1LjNjMjEuNS0xMi41LDQyLjEtMjUuNyw2MS45LTM5LjdjMTkuOC0xNCw0MS40LTMxLjYsNjQuOC01Mi43CgljMjMuNC0yMS4yLDQzLjUtNDIuOSw2MC40LTY1LjNjMTYuOS0yMi40LDMxLTQ3LjksNDIuNS03Ni44YzExLjQtMjguOCwxNy4xLTU3LjksMTcuMS04Ny4zdi0zOTMKCUM4MzkuNiwxNDIuOCw4MzYuMywxMzUuMSw4MjkuOCwxMjguNnogTTc1My44LDU0OC40YzAsMTQyLjMtMjQxLjgsMjY0LjktMjQxLjgsMjY0LjlWMjAzaDI0MS44Qzc1My44LDIwMyw3NTMuOCw0MDYuMSw3NTMuOCw1NDguNHoKCSIvPgo8L3N2Zz4K" + }, + "fbfc3007-154e-4ecc-8c0b-6e020557d7bd": { + "name": "iCloud Keychain", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ3LTc3LjY2LDAtNDUuNTksMjkuNjMtNjkuNzUsNTguODEtNjkuNzUsMTUuNSwwLDI4LjQyLDEwLjE4LDM4LjE1LDEwLjE4czIzLjcxLTEwLjc5LDQxLjM0LTEwLjc5YzE4LjQxLS40NywzNS44NCw4LjI0LDQ2LjUsMjMuMjVabS01NC44Ni00Mi41NWM3Ljc3LTkuMTQsMTIuMTctMjAuNjcsMTIuNDYtMzIuNjcuMDEtMS41OC0uMTQtMy4xNi0uNDYtNC43MS0xMy4zNSwxLjMtMjUuNjksNy42Ny0zNC41LDE3Ljc4LTcuODUsOC43OC0xMi40MSwyMC0xMi45MiwzMS43NiwwLDEuNDMuMTYsMi44Ni40Niw0LjI2LDEuMDUuMiwyLjEyLjMsMy4xOS4zLDEyLjQzLS45OSwyMy45MS03LjA0LDMxLjc2LTE2LjczWiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBmaWxsPSJub25lIj48cGF0aCBkPSJtMjE3LjM2LDkwLjY5Yy0xNS41OCw5LjU0LTI1LjE3LDI2LjQxLTI1LjM4LDQ0LjY4LjA2LDIwLjY3LDEyLjQzLDM5LjMyLDMxLjQ2LDQ3LjQxLTMuNjcsMTEuODQtOS4xLDIzLjA2LTE2LjExLDMzLjI4LTEwLjAzLDE0LjQ0LTIwLjUyLDI4Ljg3LTM2LjQ3LDI4Ljg3cy0yMC4wNi05LjI3LTM4LjQ1LTkuMjctMjQuMzIsOS41Ny0zOC45LDkuNTctMjQuNzctMTMuMzctMzYuNDctMjkuNzljLTE1LjQ2LTIyLjk5LTIzLjk1LTQ5Ljk2LTI0LjQ3LTc3LjY2LDAtNDUuNTksMjkuNjMtNjkuNzUsNTguODEtNjkuNzUsMTUuNSwwLDI4LjQyLDEwLjE4LDM4LjE1LDEwLjE4czIzLjcxLTEwLjc5LDQxLjM0LTEwLjc5YzE4LjQxLS40NywzNS44NCw4LjI0LDQ2LjUsMjMuMjVabS01NC44Ni00Mi41NWM3Ljc3LTkuMTQsMTIuMTctMjAuNjcsMTIuNDYtMzIuNjcuMDEtMS41OC0uMTQtMy4xNi0uNDYtNC43MS0xMy4zNSwxLjMtMjUuNjksNy42Ny0zNC41LDE3Ljc4LTcuODUsOC43OC0xMi40MSwyMC0xMi45MiwzMS43NiwwLDEuNDMuMTYsMi44Ni40Niw0LjI2LDEuMDUuMiwyLjEyLjMsMy4xOS4zLDEyLjQzLS45OSwyMy45MS03LjA0LDMxLjc2LTE2LjczWiIgZmlsbD0iIzAwMCIvPjwvc3ZnPg==" + }, + "53414d53-554e-4700-0000-000000000000": { + "name": "Samsung Pass", + "icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgd2lkdGg9IjUycHgiIGhlaWdodD0iNTJweCIgdmlld0JveD0iMCAwIDUyLjAgNTIuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGRlZnM+PGNsaXBQYXRoIGlkPSJpMCI+PHBhdGggZD0iTTM2MCwwIEwzNjAsODAwIEwwLDgwMCBMMCwwIEwzNjAsMCBaIj48L3BhdGg+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImkxIj48cGF0aCBkPSJNMjYsMCBDMzMuOTkxMDI3OCwwIDQxLjEzOTU4MzMsMC45NzUgNDUuOTA4Nzc3OCw1Ljc3Nzc3Nzc4IEM0OS43MTAxOTQ0LDkuNjA1OTE2NjcgNTIsMTUuODY1MDU1NiA1MiwyNiBDNTIsMzYuMTM0OTQ0NCA0OS43MDk4MzMzLDQyLjM5NDQ0NDQgNDUuOTA4MDU1Niw0Ni4yMjI1ODMzIEM0MS4xMzg4NjExLDUxLjAyNDYzODkgMzMuOTkwMzA1Niw1MiAyNiw1MiBDMTguMDA4OTcyMiw1MiAxMC44NjA3Nzc4LDUxLjAyNDYzODkgNi4wOTE1ODMzMyw0Ni4yMjI1ODMzIEMyLjI5MDE2NjY3LDQyLjM5NDQ0NDQgMCwzNi4xMzQ5NDQ0IDAsMjYgQzAsMTUuODY1MDU1NiAyLjI4OTgwNTU2LDkuNjA1NTU1NTYgNi4wOTA4NjExMSw1Ljc3Nzc3Nzc4IEMxMC44NjAwNTU2LDAuOTc1IDE4LjAwODYxMTEsMCAyNiwwIFoiPjwvcGF0aD48L2NsaXBQYXRoPjxsaW5lYXJHcmFkaWVudCBpZD0iaTIiIHgxPSIyNnB4IiB5MT0iNTJweCIgeDI9IjI2cHgiIHkyPSIwLjE5NTkyMTE0OHB4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzI5MjlCMiIgb2Zmc2V0PSIwJSI+PC9zdG9wPjxzdG9wIHN0b3AtY29sb3I9IiMxQTQwQ0MiIG9mZnNldD0iMTAwJSI+PC9zdG9wPjwvbGluZWFyR3JhZGllbnQ+PGNsaXBQYXRoIGlkPSJpMyI+PHBhdGggZD0iTTM3LjE5NDQ0NDQsMCBMMzcuMTk0NDQ0NCw1LjcyMjIyMjIyIEwwLDUuNzIyMjIyMjIgTDAsMCBMMzcuMTk0NDQ0NCwwIFoiPjwvcGF0aD48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iaTQiPjxwYXRoIGQ9Ik0xLjg4MzQyMjEzLDAgQzIuNjIwNzU5ODcsMCAzLjY0MzU2NDAyLDAuMTgxNjEwODcxIDMuNjQzNTY0MDIsMS4zMjExMTgxOSBMMy42NDM1NjQwMiwxLjY4OTExNTYyIEwyLjM0Mjc5MzM5LDEuNjg5MTE1NjIgTDIuMzQyNzkzMzksMS4zNjQ5MDY1OSBDMi4zNDI3OTMzOSwxLjA3OTU3NTczIDIuMTYzMTk2NjQsMC44ODkxNTMzNzEgMS44NTgxNzY4LDAuODg5MTUzMzcxIEMxLjUyOTMxNzg4LDAuODg5MTUzMzcxIDEuNDE2NDgzOTgsMS4wNzUyNzA4OCAxLjM4MDg1OTI3LDEuMjQyMTUxMDkgQzEuMzY2Nzk2ODgsMS4zMDAyNjY1NyAxLjM2MDkwNDA4LDEuNDExNzIxODQgMS4zODYxNDk0MSwxLjUxNzI1NzkzIEMxLjUzNDYwODAyLDIuMTMwNDk3MyAzLjUxMjQ0OTAyLDIuNDU2MTE4ODcgMy43MzI1NTg4MywzLjU1NTUzNzI3IEMzLjc1MzcxOTM3LDMuNjY3MDU5OCAzLjgwMDc5NDg4LDMuOTYxMTM0ODggMy43MzgwNDk4Niw0LjQxMzAwOTYzIEMzLjYxMTgyMzIxLDUuMjg5NjUyMDMgMi44MzgwNTcyLDUuNjEyMTc5NDkgMS44OTU4MTA0Myw1LjYxMjE3OTQ5IEMwLjkxNTcyOTEzNiw1LjYxMjE3OTQ5IDAsNS4yNTk5ODg5MiAwLDQuMDg4ODAwNiBMMCwzLjY4ODI0NzczIEwxLjM5OTQwODIzLDMuNjg4MjQ3NzMgTDEuNDAxMjE2MjUsNC4xOTIzMTg3OSBDMS40MDEyMTYyNSw0LjQ3ODY1ODYgMS41OTYwODA3Myw0LjY2OTI4Mjc1IDEuOTIxODU5MzIsNC42NjkyODI3NSBDMi4yNzA3NDA0LDQuNjY5MjgyNzUgMi4zODgzOTU2OSw0LjQ5MTUwNTg5IDIuNDMxMTg1NTIsNC4zMTU0MTA2MSBDMi40NTYyMjk5Niw0LjIxNjM5OTA1IDIuNDcxNDk3NjksNC4wNTQ2MzA4NSAyLjQyMTAwNzAzLDMuOTI4NjQ2NzEgQzIuMTUxMzQ0MDYsMy4yNTA5NjkxMSAwLjI5NzkyMTY3NywyLjk0MTI4ODk1IDAuMDcyMTE5OTQ3MywxLjg3MDMyMjkyIEMwLjAxNzM0MzYwODUsMS42MDU2NDE4OSAwLjAyMzgzOTA5MTIsMS4zOTkwNzYzNCAwLjA2MTc0MDU2NzcsMS4xNjQyNjAyMSBDMC4yMDAyMjE1ODEsMC4zMDg4NzMwMDcgMC45NTcxMTI3MjcsMCAxLjg4MzQyMjEzLDAgWiBNMTguODQxMTE4NiwwLjAyOTY2MzEwODkgQzE5LjU3MDQ4NzcsMC4wMjk2NjMxMDg5IDIwLjU3Nzg5MDEsMC4yMDY5NjkxMjkgMjAuNTc3ODkwMSwxLjMzNTY0NzA2IEwyMC41Nzc4OTAxLDEuNzAwNzUyMTcgTDE5LjI5MjE4NjQsMS43MDA3NTIxNyBMMTkuMjkyMTg2NCwxLjM4MDQ0NDQxIEMxOS4yOTIxODY0LDEuMDk3NDAwNSAxOS4xMTU2MDMsMC45MDg3OTQyNSAxOC44MTQ0MDAxLDAuOTA4Nzk0MjUgQzE4LjQ5MTIzMzEsMC45MDg3OTQyNSAxOC4zNzkwNjg4LDEuMDkxNjE1ODYgMTguMzQxNzcsMS4yNTkzNzA0OSBDMTguMzI5NzgzNSwxLjMxNjgxMzM0IDE4LjMyMzA4NzEsMS40MjY2NTQyOCAxOC4zNDYyNTY2LDEuNTMwMTcyNDggQzE4LjQ5NDMxMzQsMi4xMzY0MTY0NyAyMC40NTAzOTEyLDIuNDYzMTE0MjUgMjAuNjY2MDE0NCwzLjU0OTIxNDUyIEMyMC42ODk2NTI2LDMuNjU5MDU1NDcgMjAuNzM0MDQ5NiwzLjk1MDcwOTA3IDIwLjY3NTE4ODUsNC4zOTg0MTM1IEMyMC41NTE3NzQzLDUuMjY1MTAwOTMgMTkuNzgyNDk0OSw1LjU4NDgwMzMzIDE4Ljg1MTA5NjIsNS41ODQ4MDMzMyBDMTcuODc4NTgxOCw1LjU4NDgwMzMzIDE2Ljk3NTY0MjgsNS4yMzU0Mzc4MyAxNi45NzU2NDI4LDQuMDc3NTAwMzcgTDE2Ljk3NTY0MjgsMy42Nzc4ODkxOSBMMTguMzU5NTE1NCwzLjY3Nzg4OTE5IEwxOC4zNTk5MTcyLDQuMTgwNjE0OTggQzE4LjM1OTkxNzIsNC40NjMwNTM1MiAxOC41NTUxODM0LDQuNjUwNDQ5MDMgMTguODc5NzU2Nyw0LjY1MDQ0OTAzIEMxOS4yMjU3NTgzLDQuNjUwNDQ5MDMgMTkuMzQyNDc2MSw0LjQ3NTE2MDkxIDE5LjM4MjM4NjUsNC4zMDA4ODE3NCBDMTkuNDA2MzU5NSw0LjIwNTU2OTY2IDE5LjQxOTgxOTIsNC4wNDM4MDE0NiAxOS4zNzI4MTA3LDMuOTE2OTQyOSBDMTkuMTA2NDI4OSwzLjI0NzI2OTYzIDE3LjI3MTE1MzcsMi45NDAwNzgyMSAxNy4wNDc3NjI3LDEuODgxMTUyMyBDMTYuOTkwMTA2OSwxLjYxOTM2MzYgMTYuOTk5MDgwMSwxLjQxNDIxMDU4IDE3LjAzNTMwNzQsMS4xODMwOTM5MyBDMTcuMTcyMzE1MywwLjMzMzgyNzY4NiAxNy45MjI1MSwwLjAyOTY2MzEwODkgMTguODQxMTE4NiwwLjAyOTY2MzEwODkgWiBNMjMuMjM2NDg0NSwwLjE2Njg4MDIxMSBMMjMuMjM2MjI5MSw0LjExMTM3MzY2IEMyMy4yMzcyMDYzLDQuMTU3OTg0NjIgMjMuMjQwODgxOCw0LjIwNDA2NzQ1IDIzLjI0OTY3NjQsNC4yNDExNTE5NCBDMjMuMjc1MTIyNiw0LjM3MTIzOTEzIDIzLjM4Nzc1NTYsNC42MjE1OTMwOCAyMy43NDY5NDkxLDQuNjIxNTkzMDggQzI0LjExMDgzMDEsNC42MjE1OTMwOCAyNC4yMjA1ODM2LDQuMzcxMjM5MTMgMjQuMjQ4MTA1Nyw0LjI0MTE1MTk0IEMyNC4yNTk2OTA1LDQuMTg1NTI1MiAyNC4yNjE3NjYzLDQuMTA5NjUyMjIgMjQuMjU5NjkwNSw0LjA0MjExOTg4IEwyNC4yNTk2OTA1LDAuMTY2ODgwMjExIEwyNS41Nzg2MDgzLDAuMTY2ODgwMjExIEwyNS41Nzg2MDgzLDMuOTIxODUzMTIgQzI1LjU4NDMwMDIsNC4wMTg2NDQ5OSAyNS41NzQ3MjQ0LDQuMjE2Mzk5MDUgMjUuNTY3NDI1Myw0LjI2ODE5MTc4IEMyNS40NzQ3NDc1LDUuMjQ2NjcwNzkgMjQuNzA3NDc3LDUuNTY0MzU1MjkgMjMuNzQ2OTQ5MSw1LjU2NDM1NTI5IEMyMi43ODgyOTYyLDUuNTY0MzU1MjkgMjIuMDIwNTU3LDUuMjQ2NjcwNzkgMjEuOTI5NTUzMiw0LjI2ODE5MTc4IEMyMS45MjM4NjEzLDQuMjE2Mzk5MDUgMjEuOTE2Mjk0NCw0LjAxODY0NDk5IDIxLjkxNzk2ODUsMy45MjE4NTMxMiBMMjEuOTE3OTY4NSwwLjE2Njg4MDIxMSBMMjMuMjM2NDg0NSwwLjE2Njg4MDIxMSBaIE0zNC42Mjk3NjIxLDAuMDI1OTYzNjI4MiBDMzUuNTUzOTk1NiwwLjAyNTk2MzYyODIgMzYuMzYwMDM4MiwwLjMzNjg1NDUzNCAzNi40NTg3NDI3LDEuMzE5NTAzODcgQzM2LjQ2NTU3MywxLjM5MTE5NjkyIDM2LjQ2ODM4MTQsMS40NjUxMTM3OCAzNi40NjkzNjA3LDEuNTI2MTgwMjIgTDM2LjQ2OTAwNCwxLjY1NTc1NDAyIEwzNi40Njg3MjAzLDEuNjY2MTc4ODQgTDM2LjQ2ODcyMDMsMS44MzgwMzY1NCBMMzUuMTU1MzYwNSwxLjgzODAzNjU0IEwzNS4xNTUxNSwxLjUzMzU1NTU2IEMzNS4xNTQ0NDcxLDEuNDk4NzMzNjIgMzUuMTUxNDksMS40MDU2NDEyMyAzNS4xMzk0OTAxLDEuMzQ1MjY1NzEgQzM1LjExNjY1NTUsMS4yMzEzMjE3IDM1LjAxNzA4MDQsMC45NjYwMzUzMDYgMzQuNjE4MTc3NCwwLjk2NjAzNTMwNiBDMzQuMjM4MTU4MiwwLjk2NjAzNTMwNiAzNC4xMjU1OTIxLDEuMjE3NTk5OTkgMzQuMDk5Njc3MiwxLjM0NTI2NTcxIEMzNC4wODE3OTc4LDEuNDE1NDIxMzIgMzQuMDc2MTA1OSwxLjUwODExMDEyIDM0LjA3NjEwNTksMS41OTI3OTQ2IEwzNC4wNzYxMDU5LDMuOTg2ODk2NzIgQzM0LjA3NjEwNTksNC4wNTQ2MzA4NSAzNC4wODAxOTA3LDQuMTI3NjExNTEgMzQuMDg5NzY2NSw0LjE4NjEzMDU3IEMzNC4xMTI1MzQyLDQuMzI3NTE4IDM0LjI0Mzg1MDEsNC41NjgyNTMzIDM0LjYyMDk4OTksNC41NjgyNTMzIEMzNS4wMDA0MDY0LDQuNTY4MjUzMyAzNS4xMzQxMzMsNC4zMjc1MTggMzUuMTU1MzYwNSw0LjE4NjEzMDU3IEMzNS4xNjY5NDUyLDQuMTI3NjExNTEgMzUuMTcwNjI4Miw0LjA1NDYzMDg1IDM1LjE2ODk1NDEsMy45ODY4OTY3MiBMMzUuMTY4OTU0MSwzLjIyODkwNjc2IEwzNC42MzQ0NDk2LDMuMjI4OTA2NzYgTDM0LjYzNDQ0OTYsMi40NjQ5MzAzNiBMMzYuNDc5MTY2NywyLjQ2NDkzMDM2IEwzNi40NzkxNjY3LDMuODY4NTEzMzQgQzM2LjQ3NzA5MDgsMy45NjQ0MzA3OCAzNi40NzU0ODM3LDQuMDM4Njg5NDUgMzYuNDYwMDE1LDQuMjEzOTc3NTcgQzM2LjM3MjgyODIsNS4xNjk1ODcwNyAzNS41NTM5OTU2LDUuNTA4MzI0OTcgMzQuNjI2MDc5MSw1LjUwODMyNDk3IEMzMy43MDM4NTQ1LDUuNTA4MzI0OTcgMzIuODc5MzMsNS4xNjk1ODcwNyAzMi43OTM2MTY0LDQuMjEzOTc3NTcgQzMyLjc3NTkzOCw0LjAzODY4OTQ1IDMyLjc3Mjg1NzYsMy45NjQ0MzA3OCAzMi43NzI4NTc2LDMuODY4NTEzMzQgTDMyLjc3Mjg1NzYsMS42NjYxNzg4NCBDMzIuNzcyODU3NiwxLjU3MDI2MTQgMzIuNzg4NzI4LDEuNDA5MDk4NTcgMzIuNzk5MTA3NCwxLjMxOTUwMzg3IEMzMi45MTQxNTExLDAuMzM5NzQ2ODU1IDMzLjcwMzg1NDUsMC4wMjU5NjM2MjgyIDM0LjYyOTc2MjEsMC4wMjU5NjM2MjgyIFogTTEyLjE0NDc0NDcsMC4xNjY4ODAyMTEgTDEyLjgwMjI2MTYsNC4yNjE1OTk5OCBMMTMuNDYwMTgwNCwwLjE2Njg4MDIxMSBMMTUuNTg1Mjc0NiwwLjE2Njg4MDIxMSBMMTUuNzAxOTI1NSw1LjQwNTIxMDM2IEwxNC4zOTUyNjIsNS40MDUyMTAzNiBMMTQuMzU5ODM4MiwwLjU1NTkzMTA1NCBMMTMuNDYyMjU2Miw1LjQwNTIxMDM2IEwxMi4xMzk4NTYzLDUuNDA1MjEwMzYgTDExLjI0MzA3NzksMC41NTU5MzEwNTQgTDExLjIwNzc4OCw1LjQwNTIxMDM2IEw5LjkwNDQwNTgsNS40MDUyMTAzNiBMMTAuMDE3MjM5NywwLjE2Njg4MDIxMSBMMTIuMTQ0NzQ0NywwLjE2Njg4MDIxMSBaIE03LjkwMTI1MjUsMC4xNjY4ODAyMTEgTDguODYzMjUzNTgsNS40MDUyMTAzNiBMNy40NjQzMTQxLDUuNDA1MjEwMzYgTDYuNzUzODI4ODMsMC41NTU5MzEwNTQgTDYuMDI1ODY2MDIsNS40MDUyMTAzNiBMNC42MTcxNDk4Myw1LjQwNTIxMDM2IEw1LjU4MzE2ODczLDAuMTY2ODgwMjExIEw3LjkwMTI1MjUsMC4xNjY4ODAyMTEgWiBNMjguMzA0ODM2LDUuMzUwNTkyNTcgTDI3LjAyNDYyMzMsNS4zNTA1OTI1NyBMMjcuMDI0NjIzMywwLjE2Njg4MDIxMSBMMjguOTU5ODc1MywwLjE2Njg4MDIxMSBMMzAuMTg3OTkwMyw0LjM4NDM1NTQ3IEwzMC4xMTY4NzQ4LDAuMTY2ODgwMjExIEwzMS40MDU0NTgxLDAuMTY2ODgwMjExIEwzMS40MDU0NTgxLDUuMzUwNTkyNTcgTDI5LjU0OTQyNDEsNS4zNTA1OTI1NyBMMjguMjMsMC45OTkgTDI4LjMwNDgzNiw1LjM1MDU5MjU3IFoiPjwvcGF0aD48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iaTUiPjxwYXRoIGQ9Ik0yNC4zMzUyOTY2LDIuNDcwMDY0MzIgQzI1LjMwOTY1MDIsMi40NzAwNjQzMiAyNi4xMjEzMjMsMi42NTY2MDU2NCAyNi43NjkyNzY4LDMuMDI4OTczNTYgQzI3LjQxNzIzMDUsMy40MDE2OTg4MyAyNy45Mjk1MDE3LDMuOTA4NDMzNjcgMjguMzA2MDkwMiw0LjU1MDI1MDE1IEwyNi4zOTU0NTczLDUuNDgyNTk5MzcgQzI2LjE5NjA4NjksNS4xNDYzMjQ3IDI1LjkxOTE4MzYsNC44ODAwOTIzNiAyNS41NjQ3NDczLDQuNjg0NjE3MDggQzI1LjIxMDMxMTEsNC40ODkxNDE3OSAyNC44MDA0OTQyLDQuMzkxMjI1NDcgMjQuMzM1Mjk2Niw0LjM5MTIyNTQ3IEMyMy44MDM2NDIyLDQuMzkxMjI1NDcgMjMuNDEwNDM5NSw0LjQ5NDg1OTUzIDIzLjE1NTY4ODUsNC43MDIxMjc2NiBDMjIuOTAwNTkxMyw0LjkwOTM5NTc5IDIyLjc3MzU2MTksNS4xNDk1NDA5MyAyMi43NzM1NjE5LDUuNDIyMjA1NzMgQzIyLjc3MzU2MTksNS43Mzg0NjgzMSAyMi45NjE4NTYyLDUuOTY1MDMzODEgMjMuMzM4NDQ0Nyw2LjEwMDgzMDE3IEMyMy43MTQ2ODcsNi4yMzczNDEyNSAyNC4yNjg4Mzk4LDYuMzgyMDcxNTggMjQuOTk5ODY0Niw2LjUzNDY2MzgxIEMyNS4zOTg2MDU0LDYuNjExMTM4NiAyNS43OTk3NjksNi43MTE1NTY0NCAyNi4yMDQzOTQsNi44MzczNDY3NSBDMjYuNjA4NjcyOSw2Ljk2Mjc3OTcgMjYuOTc2OTU0Myw3LjEzMTgxMDQzIDI3LjMwOTIzODMsNy4zNDQ3OTYzMSBDMjcuNjQxNTIyMiw3LjU1NzQyNDgyIDI3LjkxMDExODUsNy44Mjk3MzIyNiAyOC4xMTUwMjY5LDguMTYyNzkwNyBDMjguMzE5OTM1NCw4LjQ5NTQ5MTc4IDI4LjQyMjM4OTYsOC45MTI1Mjk1NSAyOC40MjIzODk2LDkuNDE0MjYxMzcgQzI4LjQyMjM4OTYsOS43NTIzMjI4MyAyOC4zNDQ4NTY3LDEwLjEwNDMyMTMgMjguMTg5NzkwOCwxMC40Njk1NDIgQzI4LjAzNDM3ODgsMTAuODM0NzYyOCAyNy43OTM4MTkxLDExLjE3MzE4MTYgMjcuNDY3MDczMSwxMS40ODM3MjY0IEMyNy4xNDAzMjcyLDExLjc5NDk4NiAyNi43Mjc3NDEzLDEyLjA0ODM1MzQgMjYuMjI5MzE1MywxMi4yNDQ1NDM0IEMyNS43MzA4ODkzLDEyLjQ0MTA5MDggMjUuMTMyNzc4MiwxMi41MzkwMDcxIDI0LjQzNDk4MTgsMTIuNTM5MDA3MSBDMjMuMzYwNTk2OSwxMi41MzkwMDcxIDIyLjQ2ODk2ODIsMTIuMzMyODExIDIxLjc2MDA5NTcsMTEuOTIwMDYxNiBDMjEuMDUxMjIzMywxMS41MDY5NTQ4IDIwLjUwMjk1NDcsMTAuOTEwNTIyOCAyMC4xMTUyOSwxMC4xMzAwNTExIEwyMi4xOTIwNjQ5LDkuMTY1MTgyMjUgQzIyLjQyNDY2MzcsOS42MDQ3MzM2MyAyMi43NDAzMzM1LDkuOTQyNDM3NzQgMjMuMTM5MDc0MywxMC4xNzg2NTE5IEMyMy41Mzc4MTUxLDEwLjQxNDUwODggMjQuMDAzMDEyNiwxMC41MzIwNzk4IDI0LjUzNDY2NywxMC41MzIwNzk4IEMyNS4wODg0NzM2LDEwLjUzMjA3OTggMjUuNTAzODI4NiwxMC40MTc3MjUgMjUuNzgwNzMxOSwxMC4xODkwMTUzIEMyNi4wNTcyODkxLDkuOTU5OTQ4MzIgMjYuMTk2MDg2OSw5LjY4NzY0MDg4IDI2LjE5NjA4NjksOS4zNzEwMjA5NSBDMjYuMTk2MDg2OSw5LjE5NjYyOTgzIDI2LjEzMjM5OTIsOS4wNTUxMTU3MyAyNi4wMDUwMjM2LDguOTQ1NzYzOTIgQzI1Ljg3NzY0ODEsOC44MzcxMjY4MyAyNS43MTE1MDYxLDguNzQxMzU0NjYgMjUuNTA2NTk3Niw4LjY1OTg3Njg1IEMyNS4zMDEzNDMxLDguNTc4MDQxNjcgMjUuMDYwNzgzMyw4LjUxMDE0MzQ5IDI0Ljc4Mzg4LDguNDU1MTEwMjMgQzI0LjUwNjk3NjcsOC40MDA3OTE2OSAyNC4yMTg5OTcyLDguMzQwNzU1NCAyMy45MTk5NDE2LDguMjc1MzU4NzMgQzIzLjQ5ODcwMjUsOC4xODgxNjMxOCAyMy4wODY0NjI2LDguMDg0ODg2NDcgMjIuNjgyMTgzOCw3Ljk2NDgxMzkgQzIyLjI3NzkwNSw3Ljg0NTA5ODY5IDIxLjkxNTE2MTYsNy42Nzg1Njk0NyAyMS41OTM5NTM4LDcuNDY2Mjk4MzEgQzIxLjI3Mjc0NTksNy4yNTMzMTI0NCAyMS4wMTI0NTY4LDYuOTgwNjQ3NjQgMjAuODEzMDg2NCw2LjY0ODMwMzkyIEMyMC42MTM3MTYsNi4zMTU5NjAyIDIwLjUxNDAzMDgsNS44OTg5MjI0MyAyMC41MTQwMzA4LDUuMzk3NTQ3OTcgQzIwLjUxNDAzMDgsNS4wMTU1MzEzNyAyMC42MDU0MDg5LDQuNjQ3ODA5MTIgMjAuNzg4MTY1MSw0LjI5MzY2NjUgQzIwLjk3MDkyMTMsMy45MzkxNjY1MyAyMS4yMjg0NDE0LDMuNjI2MTIwMTggMjEuNTYwNzI1NCwzLjM1MzA5ODAzIEMyMS44OTMwMDkzLDMuMDgwNzkwNTkgMjIuMjk0MTczLDIuODY1NjYwNTYgMjIuNzY1MjU0OCwyLjcwNzM1MDYgQzIzLjIzNTk5MDQsMi41NDkwNDA2MyAyMy43NTkzMzc3LDIuNDcwMDY0MzIgMjQuMzM1Mjk2NiwyLjQ3MDA2NDMyIFogTTMzLjEwNzM1MTUsMi40NzAwNjQzMiBDMzQuMDgxNzA1LDIuNDcwMDY0MzIgMzQuODkzMzc3OSwyLjY1NjYwNTY0IDM1LjU0MTMzMTYsMy4wMjg5NzM1NiBDMzYuMTg5Mjg1NCwzLjQwMTY5ODgzIDM2LjcwMTU1NjUsMy45MDg0MzM2NyAzNy4wNzgxNDUxLDQuNTUwMjUwMTUgTDM1LjE2NzUxMjIsNS40ODI1OTkzNyBDMzQuOTY4MTQxOCw1LjE0NjMyNDcgMzQuNjkxMjM4NCw0Ljg4MDA5MjM2IDM0LjMzNjgwMjIsNC42ODQ2MTcwOCBDMzMuOTgyMzY1OSw0LjQ4OTE0MTc5IDMzLjU3MjU0OSw0LjM5MTIyNTQ3IDMzLjEwNzM1MTUsNC4zOTEyMjU0NyBDMzIuNTc1Njk3MSw0LjM5MTIyNTQ3IDMyLjE4MjQ5NDQsNC40OTQ4NTk1MyAzMS45Mjc3NDMzLDQuNzAyMTI3NjYgQzMxLjY3MjY0NjEsNC45MDkzOTU3OSAzMS41NDU2MTY3LDUuMTQ5NTQwOTMgMzEuNTQ1NjE2Nyw1LjQyMjIwNTczIEMzMS41NDU2MTY3LDUuNzM4NDY4MzEgMzEuNzMzOTExLDUuOTY1MDMzODEgMzIuMTEwNDk5NSw2LjEwMDgzMDE3IEMzMi40ODY3NDE5LDYuMjM3MzQxMjUgMzMuMDQwODk0Nyw2LjM4MjA3MTU4IDMzLjc3MTkxOTQsNi41MzQ2NjM4MSBDMzQuMTcwNjYwMiw2LjYxMTEzODYgMzQuNTcxODIzOSw2LjcxMTU1NjQ0IDM0Ljk3NjQ0ODksNi44MzczNDY3NSBDMzUuMzgwNzI3Nyw2Ljk2Mjc3OTcgMzUuNzQ5MDA5MSw3LjEzMTgxMDQzIDM2LjA4MTI5MzEsNy4zNDQ3OTYzMSBDMzYuNDEzNTc3MSw3LjU1NzQyNDgyIDM2LjY4MjE3MzMsNy44Mjk3MzIyNiAzNi44ODcwODE4LDguMTYyNzkwNyBDMzcuMDkxOTkwMiw4LjQ5NTQ5MTc4IDM3LjE5NDQ0NDQsOC45MTI1Mjk1NSAzNy4xOTQ0NDQ0LDkuNDE0MjYxMzcgQzM3LjE5NDQ0NDQsOS43NTIzMjI4MyAzNy4xMTY5MTE1LDEwLjEwNDMyMTMgMzYuOTYxODQ1NywxMC40Njk1NDIgQzM2LjgwNjQzMzcsMTAuODM0NzYyOCAzNi41NjU4NzM5LDExLjE3MzE4MTYgMzYuMjM5MTI4LDExLjQ4MzcyNjQgQzM1LjkxMjM4MjEsMTEuNzk0OTg2IDM1LjQ5OTc5NjEsMTIuMDQ4MzUzNCAzNS4wMDEzNzAyLDEyLjI0NDU0MzQgQzM0LjUwMjk0NDIsMTIuNDQxMDkwOCAzMy45MDQ4MzMsMTIuNTM5MDA3MSAzMy4yMDcwMzY3LDEyLjUzOTAwNzEgQzMyLjEzMjY1MTgsMTIuNTM5MDA3MSAzMS4yNDEwMjMxLDEyLjMzMjgxMSAzMC41MzIxNTA2LDExLjkyMDA2MTYgQzI5LjgyMzI3ODEsMTEuNTA2OTU0OCAyOS4yNzUwMDk1LDEwLjkxMDUyMjggMjguODg3MzQ0OSwxMC4xMzAwNTExIEwzMC45NjQxMTk4LDkuMTY1MTgyMjUgQzMxLjE5NjcxODYsOS42MDQ3MzM2MyAzMS41MTIzODgzLDkuOTQyNDM3NzQgMzEuOTExMTI5MSwxMC4xNzg2NTE5IEMzMi4zMDk4Njk5LDEwLjQxNDUwODggMzIuNzc1MDY3NSwxMC41MzIwNzk4IDMzLjMwNjcyMTgsMTAuNTMyMDc5OCBDMzMuODYwNTI4NSwxMC41MzIwNzk4IDM0LjI3NTg4MzUsMTAuNDE3NzI1IDM0LjU1Mjc4NjgsMTAuMTg5MDE1MyBDMzQuODI5MzQ0LDkuOTU5OTQ4MzIgMzQuOTY4MTQxOCw5LjY4NzY0MDg4IDM0Ljk2ODE0MTgsOS4zNzEwMjA5NSBDMzQuOTY4MTQxOCw5LjE5NjYyOTgzIDM0LjkwNDQ1NCw5LjA1NTExNTczIDM0Ljc3NzA3ODUsOC45NDU3NjM5MiBDMzQuNjQ5NzAyOSw4LjgzNzEyNjgzIDM0LjQ4MzU2MSw4Ljc0MTM1NDY2IDM0LjI3ODY1MjUsOC42NTk4NzY4NSBDMzQuMDczMzk3OSw4LjU3ODA0MTY3IDMzLjgzMjgzODIsOC41MTAxNDM0OSAzMy41NTU5MzQ4LDguNDU1MTEwMjMgQzMzLjI3OTAzMTUsOC40MDA3OTE2OSAzMi45OTEwNTIxLDguMzQwNzU1NCAzMi42OTE5OTY1LDguMjc1MzU4NzMgQzMyLjI3MDc1NzMsOC4xODgxNjMxOCAzMS44NTg1MTc1LDguMDg0ODg2NDcgMzEuNDU0MjM4Niw3Ljk2NDgxMzkgQzMxLjA0OTk1OTgsNy44NDUwOTg2OSAzMC42ODcyMTY1LDcuNjc4NTY5NDcgMzAuMzY2MDA4Niw3LjQ2NjI5ODMxIEMzMC4wNDQ4MDA4LDcuMjUzMzEyNDQgMjkuNzg0NTExNiw2Ljk4MDY0NzY0IDI5LjU4NTE0MTIsNi42NDgzMDM5MiBDMjkuMzg1NzcwOSw2LjMxNTk2MDIgMjkuMjg2MDg1Nyw1Ljg5ODkyMjQzIDI5LjI4NjA4NTcsNS4zOTc1NDc5NyBDMjkuMjg2MDg1Nyw1LjAxNTUzMTM3IDI5LjM3NzQ2MzgsNC42NDc4MDkxMiAyOS41NjAyMTk5LDQuMjkzNjY2NSBDMjkuNzQyOTc2MSwzLjkzOTE2NjUzIDMwLjAwMDQ5NjIsMy42MjYxMjAxOCAzMC4zMzI3ODAyLDMuMzUzMDk4MDMgQzMwLjY2NTA2NDIsMy4wODA3OTA1OSAzMS4wNjYyMjc5LDIuODY1NjYwNTYgMzEuNTM3MzA5NiwyLjcwNzM1MDYgQzMyLjAwODA0NTMsMi41NDkwNDA2MyAzMi41MzEzOTI2LDIuNDcwMDY0MzIgMzMuMTA3MzUxNSwyLjQ3MDA2NDMyIFogTTEzLjc3MzIwNTcsMi40NzAwMjg1OSBDMTQuNDE1NjIxNCwyLjQ3MDAyODU5IDE1LjAwODE5NDUsMi41OTAxMDExNiAxNS41NTA5MjUsMi44Mjk1MzE1OSBDMTYuMDkzMzA5NCwzLjA2OTY3NjczIDE2LjU0MjIzODksMy4zOTY2NjAwNyAxNi44OTY2NzUxLDMuODEwODM4OTcgTDE2Ljg5NjY3NTEsMi40ODcxODE4MSBMMTkuMTM5NTkyLDIuNDg3MTgxODEgTDE5LjEzOTU5MiwxMi41MjE4MTgxIEwxNi44OTY2NzUxLDEyLjUyMTgxODEgTDE2Ljg5NjY3NTEsMTEuMDk1OTU2MyBDMTYuNTQyMjM4OSwxMS41NDQwODQzIDE2LjA4ODExNzQsMTEuODk2Nzk3NSAxNS41MzQzMTA4LDEyLjE1MzczODUgQzE0Ljk4MDUwNDIsMTIuNDEwNjc5NSAxNC4zODIzOTMsMTIuNTM4OTcxNCAxMy43Mzk5NzczLDEyLjUzODk3MTQgQzEzLjE1Mjk0MjMsMTIuNTM4OTcxNCAxMi41NzQyMTQzLDEyLjQyNjQwMzMgMTIuMDAzNzkzNSwxMi4yMDA1NTI1IEMxMS40MzMzNzI2LDExLjk3NDM0NDQgMTAuOTIxMTAxNSwxMS42NDYyODkgMTAuNDY2OTgwMSwxMS4yMTYzODYzIEMxMC4wMTI4NTg2LDEwLjc4NjQ4MzYgOS42NDcwMDAxMSwxMC4yNjAwOTQgOS4zNzA0NDI5Miw5LjYzNzU3NDkxIEM5LjA5MzUzOTYsOS4wMTQ2OTg0NCA4Ljk1NTA4Nzk0LDguMzA2NDEzMjIgOC45NTUwODc5NCw3LjUxMzA3NjU4IEM4Ljk1NTA4Nzk0LDYuNzA4MzA0NDcgOS4wOTA0MjQ0NCw1Ljk5NTAxNjIyIDkuMzYyMTM1ODIsNS4zNzE3ODI0IEM5LjYzMzUwMTA3LDQuNzQ4OTA1OTMgOS45OTM0NzUzOSw0LjIyMjg3MzcxIDEwLjQ0MjA1ODgsMy43OTI5NzEwMyBDMTAuODkwNjQyMSwzLjM2MjcxMDk4IDExLjQwNTY4MjMsMy4wMzUwMTI5MiAxMS45ODcxNzkzLDIuODA5NTE5NDkgQzEyLjU2ODY3NjMsMi41ODMzMTEzNCAxMy4xNjQwMTg0LDIuNDcwMDI4NTkgMTMuNzczMjA1NywyLjQ3MDAyODU5IFogTTQuMTUzNTQ5NzgsMCBDNC43ODQ4ODkzNSwwIDUuMzY2Mzg2MzIsMC4xMTcyMTM3MDEgNS44OTgwNDA2OSwwLjM1MTk5ODQ2MSBDNi40Mjk2OTUwNiwwLjU4NjQyNTg2MiA2Ljg4OTAwODQ0LDAuOTAzNDAzMTU2IDcuMjc3MDE5MjIsMS4zMDQwMDI0MiBDNy42NjQ2ODM4NiwxLjcwNDI0NDMyIDcuOTY4OTMxMzgsMi4xNzU5NTggOC4xOTA4MDAxNywyLjcxOTE0MzQ0IEM4LjQxMjMyMjgyLDMuMjYxOTcxNTIgOC41MjMwODQxNSwzLjg0MjMyMjI4IDguNTIzMDg0MTUsNC40NjAxOTU3MiBDOC41MjMwODQxNSw1LjA3NzM1NDQ0IDguNDEyMzIyODIsNS42NjA1NjQwOCA4LjE5MDgwMDE3LDYuMjA5NDY3MjYgQzcuOTY4OTMxMzgsNi43NTgzNzA0NCA3LjY2NDY4Mzg2LDcuMjMyOTQyOTkgNy4yNzcwMTkyMiw3LjYzMzU0MjI1IEM2Ljg4OTAwODQ0LDguMDMzNzg0MTYgNi40MjY5MjYwMyw4LjM1MTExODgxIDUuODg5NzMzNTksOC41ODUxODg4NSBDNS4zNTIxOTUwMiw4LjgxOTYxNjI1IDQuNzY4Mjc1MTUsOC45MzY4Mjk5NSA0LjEzNjkzNTU4LDguOTM2ODI5OTUgTDIuMjU5NTMxMDgsOC45MzY4Mjk5NSBMMi4yNTk1MzEwOCwxMi41MjE4NTM5IEwwLDEyLjUyMTg1MzkgTDAsMCBMNC4xNTM1NDk3OCwwIFogTTE0LjEwNTQ4OTcsNC41ODAyMzI1NiBDMTMuNjk1NjcyOCw0LjU4MDIzMjU2IDEzLjMxMDc3NzEsNC42NTU2MzUyNyAxMi45NTA4MDI4LDQuODA1NzI1OTkgQzEyLjU5MDgyODUsNC45NTU4MTY3IDEyLjI3NzkyNzgsNS4xNjAyMjU5NiAxMi4wMTIxMDA2LDUuNDE3NTI0MzMgQzExLjc0NjI3MzQsNS42NzU1Mzc0MSAxMS41Mzg1OTU5LDUuOTgxNzkzOTQgMTEuMzg5MDY4MSw2LjMzNjI5MzkxIEMxMS4yMzk1NDAzLDYuNjkwNzkzODkgMTEuMTY0Nzc2NCw3LjA3MTczODQxIDExLjE2NDc3NjQsNy40ODAxOTk1NyBDMTEuMTY0Nzc2NCw3Ljg4ODMwMzM3IDExLjIzOTU0MDMsOC4yNzI0NjQxMyAxMS4zODkwNjgxLDguNjMxOTY3MTIgQzExLjUzODU5NTksOC45OTE0NzAxMiAxMS43NDYyNzM0LDkuMzAyNzI5NjcgMTIuMDEyMTAwNiw5LjU2NjQ2MDUgQzEyLjI3NzkyNzgsOS44Mjk0NzY2MSAxMi41OTA4Mjg1LDEwLjAzNjAzIDEyLjk1MDgwMjgsMTAuMTg2ODM1NCBDMTMuMzEwNzc3MSwxMC4zMzY5MjYyIDEzLjY5NTY3MjgsMTAuNDExOTcxNSAxNC4xMDU0ODk3LDEwLjQxMTk3MTUgQzE0LjUyNjM4MjcsMTAuNDExOTcxNSAxNC45MTM3MDEyLDEwLjMzNjkyNjIgMTUuMjY4NDgzNiwxMC4xODY4MzU0IEMxNS42MjI5MTk5LDEwLjAzNjAzIDE1LjkyNzE2NzQsOS44MjY2MTc3NCAxNi4xODIyNjQ2LDkuNTU3ODgzODkgQzE2LjQzNzAxNTYsOS4yODk1MDczOSAxNi42MzkxNTUsOC45Nzc4OTA0OCAxNi43ODg2ODI4LDguNjIzNzQ3ODcgQzE2LjkzODIxMDYsOC4yNjk2MDUyNiAxNy4wMTI5NzQ1LDcuODg4MzAzMzcgMTcuMDEyOTc0NSw3LjQ4MDE5OTU3IEMxNy4wMTI5NzQ1LDcuMDgyODE2NTQgMTYuOTM4MjEwNiw2LjcwNjg3NTAzIDE2Ljc4ODY4MjgsNi4zNTIzNzUwNiBDMTYuNjM5MTU1LDUuOTk3ODc1MDkgMTYuNDM3MDE1Niw1LjY4OTExNzA1IDE2LjE4MjI2NDYsNS40MjYxMDA5NCBDMTUuOTI3MTY3NCw1LjE2MjcyNzQ3IDE1LjYyMjkxOTksNC45NTU4MTY3IDE1LjI2ODQ4MzYsNC44MDU3MjU5OSBDMTQuOTEzNzAxMiw0LjY1NTYzNTI3IDE0LjUyNjM4MjcsNC41ODAyMzI1NiAxNC4xMDU0ODk3LDQuNTgwMjMyNTYgWiBNMy45ODc0MDc3OSwyLjE2MTMwNjI4IEwyLjI1OTUzMTA4LDIuMTYxMzA2MjggTDIuMjU5NTMxMDgsNi43NzU1MjM2NyBMMy45ODc0MDc3OSw2Ljc3NTUyMzY3IEM0LjMxOTY5MTc3LDYuNzc1NTIzNjcgNC42MjQyODU0Miw2LjcxNTQ4NzM4IDQuOTAxMTg4NzQsNi41OTU0MTQ4MSBDNS4xNzc3NDU5Myw2LjQ3NTM0MjI0IDUuNDE2MjI4OTIsNi4zMDk1Mjc3NCA1LjYxNTU5OTMsNi4wOTgzMjg2NiBDNS44MTQ5Njk2OSw1Ljg4Njc3MjIyIDUuOTcwMDM1NTUsNS42NDA1NTE5OCA2LjA4MDc5Njg4LDUuMzYwMzgyNjUgQzYuMTkxMjEyMDgsNS4wODA1NzA2NyA2LjI0NjkzODg3LDQuNzgwMDMxODkgNi4yNDY5Mzg4Nyw0LjQ2MDE5NTcyIEM2LjI0NjkzODg3LDQuMTM5NjQ0ODQgNi4xOTEyMTIwOCwzLjgzOTQ2MzQxIDYuMDgwNzk2ODgsMy41NTkyOTQwOCBDNS45NzAwMzU1NSwzLjI3OTEyNDc1IDUuODE0OTY5NjksMy4wMzYxMjA3MyA1LjYxNTU5OTMsMi44MzAyODIwNCBDNS40MTYyMjg5MiwyLjYyNDQ0MzM0IDUuMTc3NzQ1OTMsMi40NjE0ODc3MSA0LjkwMTE4ODc0LDIuMzQxNDE1MTQgQzQuNjI0Mjg1NDIsMi4yMjEzNDI1NyA0LjMxOTY5MTc3LDIuMTYxMzA2MjggMy45ODc0MDc3OSwyLjE2MTMwNjI4IFoiPjwvcGF0aD48L2NsaXBQYXRoPjwvZGVmcz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMzLjAgLTE2MC4wKSI+PGcgY2xpcC1wYXRoPSJ1cmwoI2kwKSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjMzLjAgMTYwLjApIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC4wMDAxODA1NTU1NTU1NTIwNzU0OCAwLjApIj48ZyBjbGlwLXBhdGg9InVybCgjaTEpIj48cG9seWdvbiBwb2ludHM9IjAsMCA1MiwwIDUyLDUyIDAsNTIgMCwwIiBzdHJva2U9Im5vbmUiIGZpbGw9InVybCgjaTIpIj48L3BvbHlnb24+PC9nPjwvZz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3LjU4MzMzMzMzMzMzMzMzMiAxNC44MDU1NTU1NTU1NTU1NSkiPjxnIGNsaXAtcGF0aD0idXJsKCNpMykiPjxnIGNsaXAtcGF0aD0idXJsKCNpNCkiPjxwb2x5Z29uIHBvaW50cz0iMCwwIDM2LjQ3OTE2NjcsMCAzNi40NzkxNjY3LDUuNjEyMTc5NDkgMCw1LjYxMjE3OTQ5IDAsMCIgc3Ryb2tlPSJub25lIiBmaWxsPSIjRkZGRkZGIj48L3BvbHlnb24+PC9nPjwvZz48L2c+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNy41ODMzMzMzMzMzMzMzNzEgMjQuNDMyMDgwMjU1NDc3MzMpIj48ZyBjbGlwLXBhdGg9InVybCgjaTUpIj48cG9seWdvbiBwb2ludHM9IjAsMCAzNy4xOTQ0NDQ0LDAgMzcuMTk0NDQ0NCwxMi41MzkwMDcxIDAsMTIuNTM5MDA3MSAwLDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0iI0ZGRkZGRiI+PC9wb2x5Z29uPjwvZz48L2c+PC9nPjwvZz48L2c+PC9zdmc+", + "icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgd2lkdGg9IjUycHgiIGhlaWdodD0iNTJweCIgdmlld0JveD0iMCAwIDUyLjAgNTIuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGRlZnM+PGNsaXBQYXRoIGlkPSJpMCI+PHBhdGggZD0iTTM2MCwwIEwzNjAsODAwIEwwLDgwMCBMMCwwIEwzNjAsMCBaIj48L3BhdGg+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImkxIj48cGF0aCBkPSJNMjYsMCBDMzMuOTkxMDI3OCwwIDQxLjEzOTU4MzMsMC45NzUgNDUuOTA4Nzc3OCw1Ljc3Nzc3Nzc4IEM0OS43MTAxOTQ0LDkuNjA1OTE2NjcgNTIsMTUuODY1MDU1NiA1MiwyNiBDNTIsMzYuMTM0OTQ0NCA0OS43MDk4MzMzLDQyLjM5NDQ0NDQgNDUuOTA4MDU1Niw0Ni4yMjI1ODMzIEM0MS4xMzg4NjExLDUxLjAyNDYzODkgMzMuOTkwMzA1Niw1MiAyNiw1MiBDMTguMDA4OTcyMiw1MiAxMC44NjA3Nzc4LDUxLjAyNDYzODkgNi4wOTE1ODMzMyw0Ni4yMjI1ODMzIEMyLjI5MDE2NjY3LDQyLjM5NDQ0NDQgMCwzNi4xMzQ5NDQ0IDAsMjYgQzAsMTUuODY1MDU1NiAyLjI4OTgwNTU2LDkuNjA1NTU1NTYgNi4wOTA4NjExMSw1Ljc3Nzc3Nzc4IEMxMC44NjAwNTU2LDAuOTc1IDE4LjAwODYxMTEsMCAyNiwwIFoiPjwvcGF0aD48L2NsaXBQYXRoPjxsaW5lYXJHcmFkaWVudCBpZD0iaTIiIHgxPSIyNnB4IiB5MT0iNTJweCIgeDI9IjI2cHgiIHkyPSIwLjE5NTkyMTE0OHB4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzI5MjlCMiIgb2Zmc2V0PSIwJSI+PC9zdG9wPjxzdG9wIHN0b3AtY29sb3I9IiMxQTQwQ0MiIG9mZnNldD0iMTAwJSI+PC9zdG9wPjwvbGluZWFyR3JhZGllbnQ+PGNsaXBQYXRoIGlkPSJpMyI+PHBhdGggZD0iTTM3LjE5NDQ0NDQsMCBMMzcuMTk0NDQ0NCw1LjcyMjIyMjIyIEwwLDUuNzIyMjIyMjIgTDAsMCBMMzcuMTk0NDQ0NCwwIFoiPjwvcGF0aD48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iaTQiPjxwYXRoIGQ9Ik0xLjg4MzQyMjEzLDAgQzIuNjIwNzU5ODcsMCAzLjY0MzU2NDAyLDAuMTgxNjEwODcxIDMuNjQzNTY0MDIsMS4zMjExMTgxOSBMMy42NDM1NjQwMiwxLjY4OTExNTYyIEwyLjM0Mjc5MzM5LDEuNjg5MTE1NjIgTDIuMzQyNzkzMzksMS4zNjQ5MDY1OSBDMi4zNDI3OTMzOSwxLjA3OTU3NTczIDIuMTYzMTk2NjQsMC44ODkxNTMzNzEgMS44NTgxNzY4LDAuODg5MTUzMzcxIEMxLjUyOTMxNzg4LDAuODg5MTUzMzcxIDEuNDE2NDgzOTgsMS4wNzUyNzA4OCAxLjM4MDg1OTI3LDEuMjQyMTUxMDkgQzEuMzY2Nzk2ODgsMS4zMDAyNjY1NyAxLjM2MDkwNDA4LDEuNDExNzIxODQgMS4zODYxNDk0MSwxLjUxNzI1NzkzIEMxLjUzNDYwODAyLDIuMTMwNDk3MyAzLjUxMjQ0OTAyLDIuNDU2MTE4ODcgMy43MzI1NTg4MywzLjU1NTUzNzI3IEMzLjc1MzcxOTM3LDMuNjY3MDU5OCAzLjgwMDc5NDg4LDMuOTYxMTM0ODggMy43MzgwNDk4Niw0LjQxMzAwOTYzIEMzLjYxMTgyMzIxLDUuMjg5NjUyMDMgMi44MzgwNTcyLDUuNjEyMTc5NDkgMS44OTU4MTA0Myw1LjYxMjE3OTQ5IEMwLjkxNTcyOTEzNiw1LjYxMjE3OTQ5IDAsNS4yNTk5ODg5MiAwLDQuMDg4ODAwNiBMMCwzLjY4ODI0NzczIEwxLjM5OTQwODIzLDMuNjg4MjQ3NzMgTDEuNDAxMjE2MjUsNC4xOTIzMTg3OSBDMS40MDEyMTYyNSw0LjQ3ODY1ODYgMS41OTYwODA3Myw0LjY2OTI4Mjc1IDEuOTIxODU5MzIsNC42NjkyODI3NSBDMi4yNzA3NDA0LDQuNjY5MjgyNzUgMi4zODgzOTU2OSw0LjQ5MTUwNTg5IDIuNDMxMTg1NTIsNC4zMTU0MTA2MSBDMi40NTYyMjk5Niw0LjIxNjM5OTA1IDIuNDcxNDk3NjksNC4wNTQ2MzA4NSAyLjQyMTAwNzAzLDMuOTI4NjQ2NzEgQzIuMTUxMzQ0MDYsMy4yNTA5NjkxMSAwLjI5NzkyMTY3NywyLjk0MTI4ODk1IDAuMDcyMTE5OTQ3MywxLjg3MDMyMjkyIEMwLjAxNzM0MzYwODUsMS42MDU2NDE4OSAwLjAyMzgzOTA5MTIsMS4zOTkwNzYzNCAwLjA2MTc0MDU2NzcsMS4xNjQyNjAyMSBDMC4yMDAyMjE1ODEsMC4zMDg4NzMwMDcgMC45NTcxMTI3MjcsMCAxLjg4MzQyMjEzLDAgWiBNMTguODQxMTE4NiwwLjAyOTY2MzEwODkgQzE5LjU3MDQ4NzcsMC4wMjk2NjMxMDg5IDIwLjU3Nzg5MDEsMC4yMDY5NjkxMjkgMjAuNTc3ODkwMSwxLjMzNTY0NzA2IEwyMC41Nzc4OTAxLDEuNzAwNzUyMTcgTDE5LjI5MjE4NjQsMS43MDA3NTIxNyBMMTkuMjkyMTg2NCwxLjM4MDQ0NDQxIEMxOS4yOTIxODY0LDEuMDk3NDAwNSAxOS4xMTU2MDMsMC45MDg3OTQyNSAxOC44MTQ0MDAxLDAuOTA4Nzk0MjUgQzE4LjQ5MTIzMzEsMC45MDg3OTQyNSAxOC4zNzkwNjg4LDEuMDkxNjE1ODYgMTguMzQxNzcsMS4yNTkzNzA0OSBDMTguMzI5NzgzNSwxLjMxNjgxMzM0IDE4LjMyMzA4NzEsMS40MjY2NTQyOCAxOC4zNDYyNTY2LDEuNTMwMTcyNDggQzE4LjQ5NDMxMzQsMi4xMzY0MTY0NyAyMC40NTAzOTEyLDIuNDYzMTE0MjUgMjAuNjY2MDE0NCwzLjU0OTIxNDUyIEMyMC42ODk2NTI2LDMuNjU5MDU1NDcgMjAuNzM0MDQ5NiwzLjk1MDcwOTA3IDIwLjY3NTE4ODUsNC4zOTg0MTM1IEMyMC41NTE3NzQzLDUuMjY1MTAwOTMgMTkuNzgyNDk0OSw1LjU4NDgwMzMzIDE4Ljg1MTA5NjIsNS41ODQ4MDMzMyBDMTcuODc4NTgxOCw1LjU4NDgwMzMzIDE2Ljk3NTY0MjgsNS4yMzU0Mzc4MyAxNi45NzU2NDI4LDQuMDc3NTAwMzcgTDE2Ljk3NTY0MjgsMy42Nzc4ODkxOSBMMTguMzU5NTE1NCwzLjY3Nzg4OTE5IEwxOC4zNTk5MTcyLDQuMTgwNjE0OTggQzE4LjM1OTkxNzIsNC40NjMwNTM1MiAxOC41NTUxODM0LDQuNjUwNDQ5MDMgMTguODc5NzU2Nyw0LjY1MDQ0OTAzIEMxOS4yMjU3NTgzLDQuNjUwNDQ5MDMgMTkuMzQyNDc2MSw0LjQ3NTE2MDkxIDE5LjM4MjM4NjUsNC4zMDA4ODE3NCBDMTkuNDA2MzU5NSw0LjIwNTU2OTY2IDE5LjQxOTgxOTIsNC4wNDM4MDE0NiAxOS4zNzI4MTA3LDMuOTE2OTQyOSBDMTkuMTA2NDI4OSwzLjI0NzI2OTYzIDE3LjI3MTE1MzcsMi45NDAwNzgyMSAxNy4wNDc3NjI3LDEuODgxMTUyMyBDMTYuOTkwMTA2OSwxLjYxOTM2MzYgMTYuOTk5MDgwMSwxLjQxNDIxMDU4IDE3LjAzNTMwNzQsMS4xODMwOTM5MyBDMTcuMTcyMzE1MywwLjMzMzgyNzY4NiAxNy45MjI1MSwwLjAyOTY2MzEwODkgMTguODQxMTE4NiwwLjAyOTY2MzEwODkgWiBNMjMuMjM2NDg0NSwwLjE2Njg4MDIxMSBMMjMuMjM2MjI5MSw0LjExMTM3MzY2IEMyMy4yMzcyMDYzLDQuMTU3OTg0NjIgMjMuMjQwODgxOCw0LjIwNDA2NzQ1IDIzLjI0OTY3NjQsNC4yNDExNTE5NCBDMjMuMjc1MTIyNiw0LjM3MTIzOTEzIDIzLjM4Nzc1NTYsNC42MjE1OTMwOCAyMy43NDY5NDkxLDQuNjIxNTkzMDggQzI0LjExMDgzMDEsNC42MjE1OTMwOCAyNC4yMjA1ODM2LDQuMzcxMjM5MTMgMjQuMjQ4MTA1Nyw0LjI0MTE1MTk0IEMyNC4yNTk2OTA1LDQuMTg1NTI1MiAyNC4yNjE3NjYzLDQuMTA5NjUyMjIgMjQuMjU5NjkwNSw0LjA0MjExOTg4IEwyNC4yNTk2OTA1LDAuMTY2ODgwMjExIEwyNS41Nzg2MDgzLDAuMTY2ODgwMjExIEwyNS41Nzg2MDgzLDMuOTIxODUzMTIgQzI1LjU4NDMwMDIsNC4wMTg2NDQ5OSAyNS41NzQ3MjQ0LDQuMjE2Mzk5MDUgMjUuNTY3NDI1Myw0LjI2ODE5MTc4IEMyNS40NzQ3NDc1LDUuMjQ2NjcwNzkgMjQuNzA3NDc3LDUuNTY0MzU1MjkgMjMuNzQ2OTQ5MSw1LjU2NDM1NTI5IEMyMi43ODgyOTYyLDUuNTY0MzU1MjkgMjIuMDIwNTU3LDUuMjQ2NjcwNzkgMjEuOTI5NTUzMiw0LjI2ODE5MTc4IEMyMS45MjM4NjEzLDQuMjE2Mzk5MDUgMjEuOTE2Mjk0NCw0LjAxODY0NDk5IDIxLjkxNzk2ODUsMy45MjE4NTMxMiBMMjEuOTE3OTY4NSwwLjE2Njg4MDIxMSBMMjMuMjM2NDg0NSwwLjE2Njg4MDIxMSBaIE0zNC42Mjk3NjIxLDAuMDI1OTYzNjI4MiBDMzUuNTUzOTk1NiwwLjAyNTk2MzYyODIgMzYuMzYwMDM4MiwwLjMzNjg1NDUzNCAzNi40NTg3NDI3LDEuMzE5NTAzODcgQzM2LjQ2NTU3MywxLjM5MTE5NjkyIDM2LjQ2ODM4MTQsMS40NjUxMTM3OCAzNi40NjkzNjA3LDEuNTI2MTgwMjIgTDM2LjQ2OTAwNCwxLjY1NTc1NDAyIEwzNi40Njg3MjAzLDEuNjY2MTc4ODQgTDM2LjQ2ODcyMDMsMS44MzgwMzY1NCBMMzUuMTU1MzYwNSwxLjgzODAzNjU0IEwzNS4xNTUxNSwxLjUzMzU1NTU2IEMzNS4xNTQ0NDcxLDEuNDk4NzMzNjIgMzUuMTUxNDksMS40MDU2NDEyMyAzNS4xMzk0OTAxLDEuMzQ1MjY1NzEgQzM1LjExNjY1NTUsMS4yMzEzMjE3IDM1LjAxNzA4MDQsMC45NjYwMzUzMDYgMzQuNjE4MTc3NCwwLjk2NjAzNTMwNiBDMzQuMjM4MTU4MiwwLjk2NjAzNTMwNiAzNC4xMjU1OTIxLDEuMjE3NTk5OTkgMzQuMDk5Njc3MiwxLjM0NTI2NTcxIEMzNC4wODE3OTc4LDEuNDE1NDIxMzIgMzQuMDc2MTA1OSwxLjUwODExMDEyIDM0LjA3NjEwNTksMS41OTI3OTQ2IEwzNC4wNzYxMDU5LDMuOTg2ODk2NzIgQzM0LjA3NjEwNTksNC4wNTQ2MzA4NSAzNC4wODAxOTA3LDQuMTI3NjExNTEgMzQuMDg5NzY2NSw0LjE4NjEzMDU3IEMzNC4xMTI1MzQyLDQuMzI3NTE4IDM0LjI0Mzg1MDEsNC41NjgyNTMzIDM0LjYyMDk4OTksNC41NjgyNTMzIEMzNS4wMDA0MDY0LDQuNTY4MjUzMyAzNS4xMzQxMzMsNC4zMjc1MTggMzUuMTU1MzYwNSw0LjE4NjEzMDU3IEMzNS4xNjY5NDUyLDQuMTI3NjExNTEgMzUuMTcwNjI4Miw0LjA1NDYzMDg1IDM1LjE2ODk1NDEsMy45ODY4OTY3MiBMMzUuMTY4OTU0MSwzLjIyODkwNjc2IEwzNC42MzQ0NDk2LDMuMjI4OTA2NzYgTDM0LjYzNDQ0OTYsMi40NjQ5MzAzNiBMMzYuNDc5MTY2NywyLjQ2NDkzMDM2IEwzNi40NzkxNjY3LDMuODY4NTEzMzQgQzM2LjQ3NzA5MDgsMy45NjQ0MzA3OCAzNi40NzU0ODM3LDQuMDM4Njg5NDUgMzYuNDYwMDE1LDQuMjEzOTc3NTcgQzM2LjM3MjgyODIsNS4xNjk1ODcwNyAzNS41NTM5OTU2LDUuNTA4MzI0OTcgMzQuNjI2MDc5MSw1LjUwODMyNDk3IEMzMy43MDM4NTQ1LDUuNTA4MzI0OTcgMzIuODc5MzMsNS4xNjk1ODcwNyAzMi43OTM2MTY0LDQuMjEzOTc3NTcgQzMyLjc3NTkzOCw0LjAzODY4OTQ1IDMyLjc3Mjg1NzYsMy45NjQ0MzA3OCAzMi43NzI4NTc2LDMuODY4NTEzMzQgTDMyLjc3Mjg1NzYsMS42NjYxNzg4NCBDMzIuNzcyODU3NiwxLjU3MDI2MTQgMzIuNzg4NzI4LDEuNDA5MDk4NTcgMzIuNzk5MTA3NCwxLjMxOTUwMzg3IEMzMi45MTQxNTExLDAuMzM5NzQ2ODU1IDMzLjcwMzg1NDUsMC4wMjU5NjM2MjgyIDM0LjYyOTc2MjEsMC4wMjU5NjM2MjgyIFogTTEyLjE0NDc0NDcsMC4xNjY4ODAyMTEgTDEyLjgwMjI2MTYsNC4yNjE1OTk5OCBMMTMuNDYwMTgwNCwwLjE2Njg4MDIxMSBMMTUuNTg1Mjc0NiwwLjE2Njg4MDIxMSBMMTUuNzAxOTI1NSw1LjQwNTIxMDM2IEwxNC4zOTUyNjIsNS40MDUyMTAzNiBMMTQuMzU5ODM4MiwwLjU1NTkzMTA1NCBMMTMuNDYyMjU2Miw1LjQwNTIxMDM2IEwxMi4xMzk4NTYzLDUuNDA1MjEwMzYgTDExLjI0MzA3NzksMC41NTU5MzEwNTQgTDExLjIwNzc4OCw1LjQwNTIxMDM2IEw5LjkwNDQwNTgsNS40MDUyMTAzNiBMMTAuMDE3MjM5NywwLjE2Njg4MDIxMSBMMTIuMTQ0NzQ0NywwLjE2Njg4MDIxMSBaIE03LjkwMTI1MjUsMC4xNjY4ODAyMTEgTDguODYzMjUzNTgsNS40MDUyMTAzNiBMNy40NjQzMTQxLDUuNDA1MjEwMzYgTDYuNzUzODI4ODMsMC41NTU5MzEwNTQgTDYuMDI1ODY2MDIsNS40MDUyMTAzNiBMNC42MTcxNDk4Myw1LjQwNTIxMDM2IEw1LjU4MzE2ODczLDAuMTY2ODgwMjExIEw3LjkwMTI1MjUsMC4xNjY4ODAyMTEgWiBNMjguMzA0ODM2LDUuMzUwNTkyNTcgTDI3LjAyNDYyMzMsNS4zNTA1OTI1NyBMMjcuMDI0NjIzMywwLjE2Njg4MDIxMSBMMjguOTU5ODc1MywwLjE2Njg4MDIxMSBMMzAuMTg3OTkwMyw0LjM4NDM1NTQ3IEwzMC4xMTY4NzQ4LDAuMTY2ODgwMjExIEwzMS40MDU0NTgxLDAuMTY2ODgwMjExIEwzMS40MDU0NTgxLDUuMzUwNTkyNTcgTDI5LjU0OTQyNDEsNS4zNTA1OTI1NyBMMjguMjMsMC45OTkgTDI4LjMwNDgzNiw1LjM1MDU5MjU3IFoiPjwvcGF0aD48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iaTUiPjxwYXRoIGQ9Ik0yNC4zMzUyOTY2LDIuNDcwMDY0MzIgQzI1LjMwOTY1MDIsMi40NzAwNjQzMiAyNi4xMjEzMjMsMi42NTY2MDU2NCAyNi43NjkyNzY4LDMuMDI4OTczNTYgQzI3LjQxNzIzMDUsMy40MDE2OTg4MyAyNy45Mjk1MDE3LDMuOTA4NDMzNjcgMjguMzA2MDkwMiw0LjU1MDI1MDE1IEwyNi4zOTU0NTczLDUuNDgyNTk5MzcgQzI2LjE5NjA4NjksNS4xNDYzMjQ3IDI1LjkxOTE4MzYsNC44ODAwOTIzNiAyNS41NjQ3NDczLDQuNjg0NjE3MDggQzI1LjIxMDMxMTEsNC40ODkxNDE3OSAyNC44MDA0OTQyLDQuMzkxMjI1NDcgMjQuMzM1Mjk2Niw0LjM5MTIyNTQ3IEMyMy44MDM2NDIyLDQuMzkxMjI1NDcgMjMuNDEwNDM5NSw0LjQ5NDg1OTUzIDIzLjE1NTY4ODUsNC43MDIxMjc2NiBDMjIuOTAwNTkxMyw0LjkwOTM5NTc5IDIyLjc3MzU2MTksNS4xNDk1NDA5MyAyMi43NzM1NjE5LDUuNDIyMjA1NzMgQzIyLjc3MzU2MTksNS43Mzg0NjgzMSAyMi45NjE4NTYyLDUuOTY1MDMzODEgMjMuMzM4NDQ0Nyw2LjEwMDgzMDE3IEMyMy43MTQ2ODcsNi4yMzczNDEyNSAyNC4yNjg4Mzk4LDYuMzgyMDcxNTggMjQuOTk5ODY0Niw2LjUzNDY2MzgxIEMyNS4zOTg2MDU0LDYuNjExMTM4NiAyNS43OTk3NjksNi43MTE1NTY0NCAyNi4yMDQzOTQsNi44MzczNDY3NSBDMjYuNjA4NjcyOSw2Ljk2Mjc3OTcgMjYuOTc2OTU0Myw3LjEzMTgxMDQzIDI3LjMwOTIzODMsNy4zNDQ3OTYzMSBDMjcuNjQxNTIyMiw3LjU1NzQyNDgyIDI3LjkxMDExODUsNy44Mjk3MzIyNiAyOC4xMTUwMjY5LDguMTYyNzkwNyBDMjguMzE5OTM1NCw4LjQ5NTQ5MTc4IDI4LjQyMjM4OTYsOC45MTI1Mjk1NSAyOC40MjIzODk2LDkuNDE0MjYxMzcgQzI4LjQyMjM4OTYsOS43NTIzMjI4MyAyOC4zNDQ4NTY3LDEwLjEwNDMyMTMgMjguMTg5NzkwOCwxMC40Njk1NDIgQzI4LjAzNDM3ODgsMTAuODM0NzYyOCAyNy43OTM4MTkxLDExLjE3MzE4MTYgMjcuNDY3MDczMSwxMS40ODM3MjY0IEMyNy4xNDAzMjcyLDExLjc5NDk4NiAyNi43Mjc3NDEzLDEyLjA0ODM1MzQgMjYuMjI5MzE1MywxMi4yNDQ1NDM0IEMyNS43MzA4ODkzLDEyLjQ0MTA5MDggMjUuMTMyNzc4MiwxMi41MzkwMDcxIDI0LjQzNDk4MTgsMTIuNTM5MDA3MSBDMjMuMzYwNTk2OSwxMi41MzkwMDcxIDIyLjQ2ODk2ODIsMTIuMzMyODExIDIxLjc2MDA5NTcsMTEuOTIwMDYxNiBDMjEuMDUxMjIzMywxMS41MDY5NTQ4IDIwLjUwMjk1NDcsMTAuOTEwNTIyOCAyMC4xMTUyOSwxMC4xMzAwNTExIEwyMi4xOTIwNjQ5LDkuMTY1MTgyMjUgQzIyLjQyNDY2MzcsOS42MDQ3MzM2MyAyMi43NDAzMzM1LDkuOTQyNDM3NzQgMjMuMTM5MDc0MywxMC4xNzg2NTE5IEMyMy41Mzc4MTUxLDEwLjQxNDUwODggMjQuMDAzMDEyNiwxMC41MzIwNzk4IDI0LjUzNDY2NywxMC41MzIwNzk4IEMyNS4wODg0NzM2LDEwLjUzMjA3OTggMjUuNTAzODI4NiwxMC40MTc3MjUgMjUuNzgwNzMxOSwxMC4xODkwMTUzIEMyNi4wNTcyODkxLDkuOTU5OTQ4MzIgMjYuMTk2MDg2OSw5LjY4NzY0MDg4IDI2LjE5NjA4NjksOS4zNzEwMjA5NSBDMjYuMTk2MDg2OSw5LjE5NjYyOTgzIDI2LjEzMjM5OTIsOS4wNTUxMTU3MyAyNi4wMDUwMjM2LDguOTQ1NzYzOTIgQzI1Ljg3NzY0ODEsOC44MzcxMjY4MyAyNS43MTE1MDYxLDguNzQxMzU0NjYgMjUuNTA2NTk3Niw4LjY1OTg3Njg1IEMyNS4zMDEzNDMxLDguNTc4MDQxNjcgMjUuMDYwNzgzMyw4LjUxMDE0MzQ5IDI0Ljc4Mzg4LDguNDU1MTEwMjMgQzI0LjUwNjk3NjcsOC40MDA3OTE2OSAyNC4yMTg5OTcyLDguMzQwNzU1NCAyMy45MTk5NDE2LDguMjc1MzU4NzMgQzIzLjQ5ODcwMjUsOC4xODgxNjMxOCAyMy4wODY0NjI2LDguMDg0ODg2NDcgMjIuNjgyMTgzOCw3Ljk2NDgxMzkgQzIyLjI3NzkwNSw3Ljg0NTA5ODY5IDIxLjkxNTE2MTYsNy42Nzg1Njk0NyAyMS41OTM5NTM4LDcuNDY2Mjk4MzEgQzIxLjI3Mjc0NTksNy4yNTMzMTI0NCAyMS4wMTI0NTY4LDYuOTgwNjQ3NjQgMjAuODEzMDg2NCw2LjY0ODMwMzkyIEMyMC42MTM3MTYsNi4zMTU5NjAyIDIwLjUxNDAzMDgsNS44OTg5MjI0MyAyMC41MTQwMzA4LDUuMzk3NTQ3OTcgQzIwLjUxNDAzMDgsNS4wMTU1MzEzNyAyMC42MDU0MDg5LDQuNjQ3ODA5MTIgMjAuNzg4MTY1MSw0LjI5MzY2NjUgQzIwLjk3MDkyMTMsMy45MzkxNjY1MyAyMS4yMjg0NDE0LDMuNjI2MTIwMTggMjEuNTYwNzI1NCwzLjM1MzA5ODAzIEMyMS44OTMwMDkzLDMuMDgwNzkwNTkgMjIuMjk0MTczLDIuODY1NjYwNTYgMjIuNzY1MjU0OCwyLjcwNzM1MDYgQzIzLjIzNTk5MDQsMi41NDkwNDA2MyAyMy43NTkzMzc3LDIuNDcwMDY0MzIgMjQuMzM1Mjk2NiwyLjQ3MDA2NDMyIFogTTMzLjEwNzM1MTUsMi40NzAwNjQzMiBDMzQuMDgxNzA1LDIuNDcwMDY0MzIgMzQuODkzMzc3OSwyLjY1NjYwNTY0IDM1LjU0MTMzMTYsMy4wMjg5NzM1NiBDMzYuMTg5Mjg1NCwzLjQwMTY5ODgzIDM2LjcwMTU1NjUsMy45MDg0MzM2NyAzNy4wNzgxNDUxLDQuNTUwMjUwMTUgTDM1LjE2NzUxMjIsNS40ODI1OTkzNyBDMzQuOTY4MTQxOCw1LjE0NjMyNDcgMzQuNjkxMjM4NCw0Ljg4MDA5MjM2IDM0LjMzNjgwMjIsNC42ODQ2MTcwOCBDMzMuOTgyMzY1OSw0LjQ4OTE0MTc5IDMzLjU3MjU0OSw0LjM5MTIyNTQ3IDMzLjEwNzM1MTUsNC4zOTEyMjU0NyBDMzIuNTc1Njk3MSw0LjM5MTIyNTQ3IDMyLjE4MjQ5NDQsNC40OTQ4NTk1MyAzMS45Mjc3NDMzLDQuNzAyMTI3NjYgQzMxLjY3MjY0NjEsNC45MDkzOTU3OSAzMS41NDU2MTY3LDUuMTQ5NTQwOTMgMzEuNTQ1NjE2Nyw1LjQyMjIwNTczIEMzMS41NDU2MTY3LDUuNzM4NDY4MzEgMzEuNzMzOTExLDUuOTY1MDMzODEgMzIuMTEwNDk5NSw2LjEwMDgzMDE3IEMzMi40ODY3NDE5LDYuMjM3MzQxMjUgMzMuMDQwODk0Nyw2LjM4MjA3MTU4IDMzLjc3MTkxOTQsNi41MzQ2NjM4MSBDMzQuMTcwNjYwMiw2LjYxMTEzODYgMzQuNTcxODIzOSw2LjcxMTU1NjQ0IDM0Ljk3NjQ0ODksNi44MzczNDY3NSBDMzUuMzgwNzI3Nyw2Ljk2Mjc3OTcgMzUuNzQ5MDA5MSw3LjEzMTgxMDQzIDM2LjA4MTI5MzEsNy4zNDQ3OTYzMSBDMzYuNDEzNTc3MSw3LjU1NzQyNDgyIDM2LjY4MjE3MzMsNy44Mjk3MzIyNiAzNi44ODcwODE4LDguMTYyNzkwNyBDMzcuMDkxOTkwMiw4LjQ5NTQ5MTc4IDM3LjE5NDQ0NDQsOC45MTI1Mjk1NSAzNy4xOTQ0NDQ0LDkuNDE0MjYxMzcgQzM3LjE5NDQ0NDQsOS43NTIzMjI4MyAzNy4xMTY5MTE1LDEwLjEwNDMyMTMgMzYuOTYxODQ1NywxMC40Njk1NDIgQzM2LjgwNjQzMzcsMTAuODM0NzYyOCAzNi41NjU4NzM5LDExLjE3MzE4MTYgMzYuMjM5MTI4LDExLjQ4MzcyNjQgQzM1LjkxMjM4MjEsMTEuNzk0OTg2IDM1LjQ5OTc5NjEsMTIuMDQ4MzUzNCAzNS4wMDEzNzAyLDEyLjI0NDU0MzQgQzM0LjUwMjk0NDIsMTIuNDQxMDkwOCAzMy45MDQ4MzMsMTIuNTM5MDA3MSAzMy4yMDcwMzY3LDEyLjUzOTAwNzEgQzMyLjEzMjY1MTgsMTIuNTM5MDA3MSAzMS4yNDEwMjMxLDEyLjMzMjgxMSAzMC41MzIxNTA2LDExLjkyMDA2MTYgQzI5LjgyMzI3ODEsMTEuNTA2OTU0OCAyOS4yNzUwMDk1LDEwLjkxMDUyMjggMjguODg3MzQ0OSwxMC4xMzAwNTExIEwzMC45NjQxMTk4LDkuMTY1MTgyMjUgQzMxLjE5NjcxODYsOS42MDQ3MzM2MyAzMS41MTIzODgzLDkuOTQyNDM3NzQgMzEuOTExMTI5MSwxMC4xNzg2NTE5IEMzMi4zMDk4Njk5LDEwLjQxNDUwODggMzIuNzc1MDY3NSwxMC41MzIwNzk4IDMzLjMwNjcyMTgsMTAuNTMyMDc5OCBDMzMuODYwNTI4NSwxMC41MzIwNzk4IDM0LjI3NTg4MzUsMTAuNDE3NzI1IDM0LjU1Mjc4NjgsMTAuMTg5MDE1MyBDMzQuODI5MzQ0LDkuOTU5OTQ4MzIgMzQuOTY4MTQxOCw5LjY4NzY0MDg4IDM0Ljk2ODE0MTgsOS4zNzEwMjA5NSBDMzQuOTY4MTQxOCw5LjE5NjYyOTgzIDM0LjkwNDQ1NCw5LjA1NTExNTczIDM0Ljc3NzA3ODUsOC45NDU3NjM5MiBDMzQuNjQ5NzAyOSw4LjgzNzEyNjgzIDM0LjQ4MzU2MSw4Ljc0MTM1NDY2IDM0LjI3ODY1MjUsOC42NTk4NzY4NSBDMzQuMDczMzk3OSw4LjU3ODA0MTY3IDMzLjgzMjgzODIsOC41MTAxNDM0OSAzMy41NTU5MzQ4LDguNDU1MTEwMjMgQzMzLjI3OTAzMTUsOC40MDA3OTE2OSAzMi45OTEwNTIxLDguMzQwNzU1NCAzMi42OTE5OTY1LDguMjc1MzU4NzMgQzMyLjI3MDc1NzMsOC4xODgxNjMxOCAzMS44NTg1MTc1LDguMDg0ODg2NDcgMzEuNDU0MjM4Niw3Ljk2NDgxMzkgQzMxLjA0OTk1OTgsNy44NDUwOTg2OSAzMC42ODcyMTY1LDcuNjc4NTY5NDcgMzAuMzY2MDA4Niw3LjQ2NjI5ODMxIEMzMC4wNDQ4MDA4LDcuMjUzMzEyNDQgMjkuNzg0NTExNiw2Ljk4MDY0NzY0IDI5LjU4NTE0MTIsNi42NDgzMDM5MiBDMjkuMzg1NzcwOSw2LjMxNTk2MDIgMjkuMjg2MDg1Nyw1Ljg5ODkyMjQzIDI5LjI4NjA4NTcsNS4zOTc1NDc5NyBDMjkuMjg2MDg1Nyw1LjAxNTUzMTM3IDI5LjM3NzQ2MzgsNC42NDc4MDkxMiAyOS41NjAyMTk5LDQuMjkzNjY2NSBDMjkuNzQyOTc2MSwzLjkzOTE2NjUzIDMwLjAwMDQ5NjIsMy42MjYxMjAxOCAzMC4zMzI3ODAyLDMuMzUzMDk4MDMgQzMwLjY2NTA2NDIsMy4wODA3OTA1OSAzMS4wNjYyMjc5LDIuODY1NjYwNTYgMzEuNTM3MzA5NiwyLjcwNzM1MDYgQzMyLjAwODA0NTMsMi41NDkwNDA2MyAzMi41MzEzOTI2LDIuNDcwMDY0MzIgMzMuMTA3MzUxNSwyLjQ3MDA2NDMyIFogTTEzLjc3MzIwNTcsMi40NzAwMjg1OSBDMTQuNDE1NjIxNCwyLjQ3MDAyODU5IDE1LjAwODE5NDUsMi41OTAxMDExNiAxNS41NTA5MjUsMi44Mjk1MzE1OSBDMTYuMDkzMzA5NCwzLjA2OTY3NjczIDE2LjU0MjIzODksMy4zOTY2NjAwNyAxNi44OTY2NzUxLDMuODEwODM4OTcgTDE2Ljg5NjY3NTEsMi40ODcxODE4MSBMMTkuMTM5NTkyLDIuNDg3MTgxODEgTDE5LjEzOTU5MiwxMi41MjE4MTgxIEwxNi44OTY2NzUxLDEyLjUyMTgxODEgTDE2Ljg5NjY3NTEsMTEuMDk1OTU2MyBDMTYuNTQyMjM4OSwxMS41NDQwODQzIDE2LjA4ODExNzQsMTEuODk2Nzk3NSAxNS41MzQzMTA4LDEyLjE1MzczODUgQzE0Ljk4MDUwNDIsMTIuNDEwNjc5NSAxNC4zODIzOTMsMTIuNTM4OTcxNCAxMy43Mzk5NzczLDEyLjUzODk3MTQgQzEzLjE1Mjk0MjMsMTIuNTM4OTcxNCAxMi41NzQyMTQzLDEyLjQyNjQwMzMgMTIuMDAzNzkzNSwxMi4yMDA1NTI1IEMxMS40MzMzNzI2LDExLjk3NDM0NDQgMTAuOTIxMTAxNSwxMS42NDYyODkgMTAuNDY2OTgwMSwxMS4yMTYzODYzIEMxMC4wMTI4NTg2LDEwLjc4NjQ4MzYgOS42NDcwMDAxMSwxMC4yNjAwOTQgOS4zNzA0NDI5Miw5LjYzNzU3NDkxIEM5LjA5MzUzOTYsOS4wMTQ2OTg0NCA4Ljk1NTA4Nzk0LDguMzA2NDEzMjIgOC45NTUwODc5NCw3LjUxMzA3NjU4IEM4Ljk1NTA4Nzk0LDYuNzA4MzA0NDcgOS4wOTA0MjQ0NCw1Ljk5NTAxNjIyIDkuMzYyMTM1ODIsNS4zNzE3ODI0IEM5LjYzMzUwMTA3LDQuNzQ4OTA1OTMgOS45OTM0NzUzOSw0LjIyMjg3MzcxIDEwLjQ0MjA1ODgsMy43OTI5NzEwMyBDMTAuODkwNjQyMSwzLjM2MjcxMDk4IDExLjQwNTY4MjMsMy4wMzUwMTI5MiAxMS45ODcxNzkzLDIuODA5NTE5NDkgQzEyLjU2ODY3NjMsMi41ODMzMTEzNCAxMy4xNjQwMTg0LDIuNDcwMDI4NTkgMTMuNzczMjA1NywyLjQ3MDAyODU5IFogTTQuMTUzNTQ5NzgsMCBDNC43ODQ4ODkzNSwwIDUuMzY2Mzg2MzIsMC4xMTcyMTM3MDEgNS44OTgwNDA2OSwwLjM1MTk5ODQ2MSBDNi40Mjk2OTUwNiwwLjU4NjQyNTg2MiA2Ljg4OTAwODQ0LDAuOTAzNDAzMTU2IDcuMjc3MDE5MjIsMS4zMDQwMDI0MiBDNy42NjQ2ODM4NiwxLjcwNDI0NDMyIDcuOTY4OTMxMzgsMi4xNzU5NTggOC4xOTA4MDAxNywyLjcxOTE0MzQ0IEM4LjQxMjMyMjgyLDMuMjYxOTcxNTIgOC41MjMwODQxNSwzLjg0MjMyMjI4IDguNTIzMDg0MTUsNC40NjAxOTU3MiBDOC41MjMwODQxNSw1LjA3NzM1NDQ0IDguNDEyMzIyODIsNS42NjA1NjQwOCA4LjE5MDgwMDE3LDYuMjA5NDY3MjYgQzcuOTY4OTMxMzgsNi43NTgzNzA0NCA3LjY2NDY4Mzg2LDcuMjMyOTQyOTkgNy4yNzcwMTkyMiw3LjYzMzU0MjI1IEM2Ljg4OTAwODQ0LDguMDMzNzg0MTYgNi40MjY5MjYwMyw4LjM1MTExODgxIDUuODg5NzMzNTksOC41ODUxODg4NSBDNS4zNTIxOTUwMiw4LjgxOTYxNjI1IDQuNzY4Mjc1MTUsOC45MzY4Mjk5NSA0LjEzNjkzNTU4LDguOTM2ODI5OTUgTDIuMjU5NTMxMDgsOC45MzY4Mjk5NSBMMi4yNTk1MzEwOCwxMi41MjE4NTM5IEwwLDEyLjUyMTg1MzkgTDAsMCBMNC4xNTM1NDk3OCwwIFogTTE0LjEwNTQ4OTcsNC41ODAyMzI1NiBDMTMuNjk1NjcyOCw0LjU4MDIzMjU2IDEzLjMxMDc3NzEsNC42NTU2MzUyNyAxMi45NTA4MDI4LDQuODA1NzI1OTkgQzEyLjU5MDgyODUsNC45NTU4MTY3IDEyLjI3NzkyNzgsNS4xNjAyMjU5NiAxMi4wMTIxMDA2LDUuNDE3NTI0MzMgQzExLjc0NjI3MzQsNS42NzU1Mzc0MSAxMS41Mzg1OTU5LDUuOTgxNzkzOTQgMTEuMzg5MDY4MSw2LjMzNjI5MzkxIEMxMS4yMzk1NDAzLDYuNjkwNzkzODkgMTEuMTY0Nzc2NCw3LjA3MTczODQxIDExLjE2NDc3NjQsNy40ODAxOTk1NyBDMTEuMTY0Nzc2NCw3Ljg4ODMwMzM3IDExLjIzOTU0MDMsOC4yNzI0NjQxMyAxMS4zODkwNjgxLDguNjMxOTY3MTIgQzExLjUzODU5NTksOC45OTE0NzAxMiAxMS43NDYyNzM0LDkuMzAyNzI5NjcgMTIuMDEyMTAwNiw5LjU2NjQ2MDUgQzEyLjI3NzkyNzgsOS44Mjk0NzY2MSAxMi41OTA4Mjg1LDEwLjAzNjAzIDEyLjk1MDgwMjgsMTAuMTg2ODM1NCBDMTMuMzEwNzc3MSwxMC4zMzY5MjYyIDEzLjY5NTY3MjgsMTAuNDExOTcxNSAxNC4xMDU0ODk3LDEwLjQxMTk3MTUgQzE0LjUyNjM4MjcsMTAuNDExOTcxNSAxNC45MTM3MDEyLDEwLjMzNjkyNjIgMTUuMjY4NDgzNiwxMC4xODY4MzU0IEMxNS42MjI5MTk5LDEwLjAzNjAzIDE1LjkyNzE2NzQsOS44MjY2MTc3NCAxNi4xODIyNjQ2LDkuNTU3ODgzODkgQzE2LjQzNzAxNTYsOS4yODk1MDczOSAxNi42MzkxNTUsOC45Nzc4OTA0OCAxNi43ODg2ODI4LDguNjIzNzQ3ODcgQzE2LjkzODIxMDYsOC4yNjk2MDUyNiAxNy4wMTI5NzQ1LDcuODg4MzAzMzcgMTcuMDEyOTc0NSw3LjQ4MDE5OTU3IEMxNy4wMTI5NzQ1LDcuMDgyODE2NTQgMTYuOTM4MjEwNiw2LjcwNjg3NTAzIDE2Ljc4ODY4MjgsNi4zNTIzNzUwNiBDMTYuNjM5MTU1LDUuOTk3ODc1MDkgMTYuNDM3MDE1Niw1LjY4OTExNzA1IDE2LjE4MjI2NDYsNS40MjYxMDA5NCBDMTUuOTI3MTY3NCw1LjE2MjcyNzQ3IDE1LjYyMjkxOTksNC45NTU4MTY3IDE1LjI2ODQ4MzYsNC44MDU3MjU5OSBDMTQuOTEzNzAxMiw0LjY1NTYzNTI3IDE0LjUyNjM4MjcsNC41ODAyMzI1NiAxNC4xMDU0ODk3LDQuNTgwMjMyNTYgWiBNMy45ODc0MDc3OSwyLjE2MTMwNjI4IEwyLjI1OTUzMTA4LDIuMTYxMzA2MjggTDIuMjU5NTMxMDgsNi43NzU1MjM2NyBMMy45ODc0MDc3OSw2Ljc3NTUyMzY3IEM0LjMxOTY5MTc3LDYuNzc1NTIzNjcgNC42MjQyODU0Miw2LjcxNTQ4NzM4IDQuOTAxMTg4NzQsNi41OTU0MTQ4MSBDNS4xNzc3NDU5Myw2LjQ3NTM0MjI0IDUuNDE2MjI4OTIsNi4zMDk1Mjc3NCA1LjYxNTU5OTMsNi4wOTgzMjg2NiBDNS44MTQ5Njk2OSw1Ljg4Njc3MjIyIDUuOTcwMDM1NTUsNS42NDA1NTE5OCA2LjA4MDc5Njg4LDUuMzYwMzgyNjUgQzYuMTkxMjEyMDgsNS4wODA1NzA2NyA2LjI0NjkzODg3LDQuNzgwMDMxODkgNi4yNDY5Mzg4Nyw0LjQ2MDE5NTcyIEM2LjI0NjkzODg3LDQuMTM5NjQ0ODQgNi4xOTEyMTIwOCwzLjgzOTQ2MzQxIDYuMDgwNzk2ODgsMy41NTkyOTQwOCBDNS45NzAwMzU1NSwzLjI3OTEyNDc1IDUuODE0OTY5NjksMy4wMzYxMjA3MyA1LjYxNTU5OTMsMi44MzAyODIwNCBDNS40MTYyMjg5MiwyLjYyNDQ0MzM0IDUuMTc3NzQ1OTMsMi40NjE0ODc3MSA0LjkwMTE4ODc0LDIuMzQxNDE1MTQgQzQuNjI0Mjg1NDIsMi4yMjEzNDI1NyA0LjMxOTY5MTc3LDIuMTYxMzA2MjggMy45ODc0MDc3OSwyLjE2MTMwNjI4IFoiPjwvcGF0aD48L2NsaXBQYXRoPjwvZGVmcz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMzLjAgLTE2MC4wKSI+PGcgY2xpcC1wYXRoPSJ1cmwoI2kwKSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjMzLjAgMTYwLjApIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC4wMDAxODA1NTU1NTU1NTIwNzU0OCAwLjApIj48ZyBjbGlwLXBhdGg9InVybCgjaTEpIj48cG9seWdvbiBwb2ludHM9IjAsMCA1MiwwIDUyLDUyIDAsNTIgMCwwIiBzdHJva2U9Im5vbmUiIGZpbGw9InVybCgjaTIpIj48L3BvbHlnb24+PC9nPjwvZz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3LjU4MzMzMzMzMzMzMzMzMiAxNC44MDU1NTU1NTU1NTU1NSkiPjxnIGNsaXAtcGF0aD0idXJsKCNpMykiPjxnIGNsaXAtcGF0aD0idXJsKCNpNCkiPjxwb2x5Z29uIHBvaW50cz0iMCwwIDM2LjQ3OTE2NjcsMCAzNi40NzkxNjY3LDUuNjEyMTc5NDkgMCw1LjYxMjE3OTQ5IDAsMCIgc3Ryb2tlPSJub25lIiBmaWxsPSIjRkZGRkZGIj48L3BvbHlnb24+PC9nPjwvZz48L2c+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNy41ODMzMzMzMzMzMzMzNzEgMjQuNDMyMDgwMjU1NDc3MzMpIj48ZyBjbGlwLXBhdGg9InVybCgjaTUpIj48cG9seWdvbiBwb2ludHM9IjAsMCAzNy4xOTQ0NDQ0LDAgMzcuMTk0NDQ0NCwxMi41MzkwMDcxIDAsMTIuNTM5MDA3MSAwLDAiIHN0cm9rZT0ibm9uZSIgZmlsbD0iI0ZGRkZGRiI+PC9wb2x5Z29uPjwvZz48L2c+PC9nPjwvZz48L2c+PC9zdmc+" + }, + "66a0ccb3-bd6a-191f-ee06-e375c50b9846": { + "name": "Thales Bio iOS SDK", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=" + }, + "8836336a-f590-0921-301d-46427531eee6": { + "name": "Thales Bio Android SDK", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=" + }, + "cd69adb5-3c7a-deb9-3177-6800ea6cb72a": { + "name": "Thales PIN Android SDK", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=" + }, + "17290f1e-c212-34d0-1423-365d729f09d9": { + "name": "Thales PIN iOS SDK", + "icon_dark": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=", + "icon_light": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3Ny42IDc3LjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDc3LjYgNzcuNjsiPjxnPjxwYXRoIGZpbGw9IiMyQzJGNzMiIGQ9Ik03Ny42LDU1LjFjLTQuOSwxLjQtMTEuNCwxLjktMTYuMiwybC0yMi43LTQ1LjhoLTEuM2wtMjIuNiw0NS44Yy00LjgtMC4xLTEwLjUtMC42LTE1LjQtMmwyOC43LTUzLjdoMjAuNSBMNzcuNiw1NS4xeiI+PC9wYXRoPjxwYXRoIGZpbGw9IiM1RUJGRDQiIGQ9Ik00Ny43LDQxLjRjMCw1LjMtNC4zLDkuNS05LjYsOS41Yy01LjMsMC05LjUtNC4zLTkuNS05LjVjMC01LjMsNC4zLTkuNSw5LjUtOS41IEM0My40LDMxLjksNDcuNywzNi4xLDQ3LjcsNDEuNCI+PC9wYXRoPjwvZz48L3N2Zz4=" + } +} \ No newline at end of file diff --git a/deploy/docker-compose/quickstart.yaml b/deploy/docker-compose/quickstart.yaml index 21d58f96b..b08c6c69f 100644 --- a/deploy/docker-compose/quickstart.yaml +++ b/deploy/docker-compose/quickstart.yaml @@ -22,7 +22,7 @@ services: - '8000:8000' # public - '8001:8001' # admin restart: unless-stopped - command: serve --config /etc/config/config.yaml all + command: serve --config /etc/config/config.yaml --aaguid-map /etc/config/aaguid.json all volumes: - type: bind source: ./config.yaml From fc55df9a8b47b8acb33212eaf56b85dc218da43d Mon Sep 17 00:00:00 2001 From: Stefan Jacobi Date: Wed, 24 Jan 2024 12:51:57 +0100 Subject: [PATCH 02/24] fix(tests): fix missing constructor extension for aaguid map Closes: #1027 --- backend/handler/email_test.go | 8 +++---- backend/handler/passcode_test.go | 4 ++-- backend/handler/password_test.go | 4 ++-- backend/handler/token_test.go | 12 +++++----- backend/handler/user_test.go | 38 +++++++++++++++--------------- backend/handler/webauthn_test.go | 16 ++++++------- backend/handler/well_known_test.go | 2 +- 7 files changed, 42 insertions(+), 42 deletions(-) diff --git a/backend/handler/email_test.go b/backend/handler/email_test.go index bce6ef061..9458802b4 100644 --- a/backend/handler/email_test.go +++ b/backend/handler/email_test.go @@ -39,7 +39,7 @@ func (s *emailSuite) TestEmailHandler_List() { err := s.LoadFixtures("../test/fixtures/email") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) s.Require().NoError(err) @@ -172,7 +172,7 @@ func (s *emailSuite) TestEmailHandler_Create() { cfg.AuditLog.Storage.Enabled = true cfg.Emails.RequireVerification = currentTest.requiresVerification cfg.Emails.MaxNumOfAddresses = currentTest.maxNumberOfAddresses - e := NewPublicRouter(&cfg, s.Storage, nil) + e := NewPublicRouter(&cfg, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(cfg.Secrets.Keys, s.Storage.GetJwkPersister()) s.Require().NoError(err) sessionManager, err := session.NewManager(jwkManager, cfg) @@ -234,7 +234,7 @@ func (s *emailSuite) TestEmailHandler_SetPrimaryEmail() { err := s.LoadFixtures("../test/fixtures/email") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) s.Require().NoError(err) @@ -278,7 +278,7 @@ func (s *emailSuite) TestEmailHandler_Delete() { err := s.LoadFixtures("../test/fixtures/email") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) userId := uuid.FromStringOrNil("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5") jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) diff --git a/backend/handler/passcode_test.go b/backend/handler/passcode_test.go index 8e3189b76..523b96405 100644 --- a/backend/handler/passcode_test.go +++ b/backend/handler/passcode_test.go @@ -43,7 +43,7 @@ func (s *passcodeSuite) TestPasscodeHandler_Init() { return cfg } - e := NewPublicRouter(cfg(), s.Storage, nil) + e := NewPublicRouter(cfg(), s.Storage, nil, nil) emailId := "51b7c175-ceb6-45ba-aae6-0092221c1b84" unknownEmailId := "83618f24-2db8-4ea2-b370-ac8335f782d8" @@ -278,7 +278,7 @@ func (s *passcodeSuite) TestPasscodeHandler_Finish() { sessionManager, err := session.NewManager(jwkManager, test.DefaultConfig) s.Require().NoError(err) - e := NewPublicRouter(currentTest.cfg(), s.Storage, nil) + e := NewPublicRouter(currentTest.cfg(), s.Storage, nil, nil) // Setup passcode err = s.Storage.GetPasscodePersister().Create(currentTest.passcode) diff --git a/backend/handler/password_test.go b/backend/handler/password_test.go index 0d8363ce5..110ee7fa7 100644 --- a/backend/handler/password_test.go +++ b/backend/handler/password_test.go @@ -101,7 +101,7 @@ func (s *passwordSuite) TestPasswordHandler_Set_Create() { req.AddCookie(cookie) rec := httptest.NewRecorder() - e := NewPublicRouter(cfg, s.Storage, nil) + e := NewPublicRouter(cfg, s.Storage, nil, nil) e.ServeHTTP(rec, req) s.Equal(currentTest.expectedCode, rec.Code) @@ -171,7 +171,7 @@ func (s *passwordSuite) TestPasswordHandler_Login() { req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() - e := NewPublicRouter(currentTest.cfg(), s.Storage, nil) + e := NewPublicRouter(currentTest.cfg(), s.Storage, nil, nil) e.ServeHTTP(rec, req) if s.Equal(currentTest.expectedCode, rec.Code) { diff --git a/backend/handler/token_test.go b/backend/handler/token_test.go index 5617915e3..92d494a5c 100644 --- a/backend/handler/token_test.go +++ b/backend/handler/token_test.go @@ -48,7 +48,7 @@ func (s *tokenSuite) TestToken_Validate_TokenInCookie() { cfg := s.setupConfig() cfg.Session.EnableAuthTokenHeader = false - e := NewPublicRouter(cfg, s.Storage, nil) + e := NewPublicRouter(cfg, s.Storage, nil, nil) e.ServeHTTP(rec, req) s.Equal(rec.Code, http.StatusOK) @@ -94,7 +94,7 @@ func (s *tokenSuite) TestToken_Validate_TokenInHeader() { rec := httptest.NewRecorder() cfg := s.setupConfig() - e := NewPublicRouter(cfg, s.Storage, nil) + e := NewPublicRouter(cfg, s.Storage, nil, nil) e.ServeHTTP(rec, req) s.Equal(rec.Code, http.StatusOK) @@ -127,7 +127,7 @@ func (s *tokenSuite) TestToken_Validate_ExpiredToken() { req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() - e := NewPublicRouter(s.setupConfig(), s.Storage, nil) + e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil) e.ServeHTTP(rec, req) s.Equal(rec.Code, http.StatusUnprocessableEntity) @@ -150,7 +150,7 @@ func (s *tokenSuite) TestToken_Validate_MissingTokenFromRequest() { req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() - e := NewPublicRouter(s.setupConfig(), s.Storage, nil) + e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil) e.ServeHTTP(rec, req) s.Equal(rec.Code, http.StatusBadRequest) @@ -173,7 +173,7 @@ func (s *tokenSuite) TestToken_Validate_InvalidJson() { req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() - e := NewPublicRouter(s.setupConfig(), s.Storage, nil) + e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil) e.ServeHTTP(rec, req) s.Equal(rec.Code, http.StatusBadRequest) @@ -201,7 +201,7 @@ func (s *tokenSuite) TestToken_Validate_TokenNotFound() { req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() - e := NewPublicRouter(s.setupConfig(), s.Storage, nil) + e := NewPublicRouter(s.setupConfig(), s.Storage, nil, nil) e.ServeHTTP(rec, req) s.Equal(rec.Code, http.StatusNotFound) diff --git a/backend/handler/user_test.go b/backend/handler/user_test.go index 4f7b143b8..d24e2b793 100644 --- a/backend/handler/user_test.go +++ b/backend/handler/user_test.go @@ -33,7 +33,7 @@ func (s *userSuite) TestUserHandler_Create_TokenInCookie() { } cfg := test.DefaultConfig - e := NewPublicRouter(&cfg, s.Storage, nil) + e := NewPublicRouter(&cfg, s.Storage, nil, nil) body := UserCreateBody{Email: "jane.doe@example.com"} bodyJson, err := json.Marshal(body) @@ -78,7 +78,7 @@ func (s *userSuite) TestUserHandler_Create_TokenInHeader() { cfg := test.DefaultConfig cfg.Session.EnableAuthTokenHeader = true - e := NewPublicRouter(&cfg, s.Storage, nil) + e := NewPublicRouter(&cfg, s.Storage, nil, nil) body := UserCreateBody{Email: "jane.doe@example.com"} bodyJson, err := json.Marshal(body) @@ -118,7 +118,7 @@ func (s *userSuite) TestUserHandler_Create_CaseInsensitive() { if testing.Short() { s.T().Skip("skipping test in short mode.") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) body := UserCreateBody{Email: "JANE.DOE@EXAMPLE.COM"} bodyJson, err := json.Marshal(body) @@ -153,7 +153,7 @@ func (s *userSuite) TestUserHandler_Create_UserExists() { err := s.LoadFixtures("../test/fixtures/user") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) body := UserCreateBody{Email: "john.doe@example.com"} bodyJson, err := json.Marshal(body) @@ -175,7 +175,7 @@ func (s *userSuite) TestUserHandler_Create_UserExists_CaseInsensitive() { err := s.LoadFixtures("../test/fixtures/user") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) body := UserCreateBody{Email: "JOHN.DOE@EXAMPLE.COM"} bodyJson, err := json.Marshal(body) @@ -194,7 +194,7 @@ func (s *userSuite) TestUserHandler_Create_InvalidEmail() { if testing.Short() { s.T().Skip("skipping test in short mode.") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(`{"email": 123"}`)) req.Header.Set("Content-Type", "application/json") @@ -209,7 +209,7 @@ func (s *userSuite) TestUserHandler_Create_EmailMissing() { if testing.Short() { s.T().Skip("skipping test in short mode.") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(`{"bogus": 123}`)) req.Header.Set("Content-Type", "application/json") @@ -226,7 +226,7 @@ func (s *userSuite) TestUserHandler_Create_AccountCreationDisabled() { } testConfig := test.DefaultConfig testConfig.Account.AllowSignup = false - e := NewPublicRouter(&testConfig, s.Storage, nil) + e := NewPublicRouter(&testConfig, s.Storage, nil, nil) body := UserCreateBody{Email: "jane.doe@example.com"} bodyJson, err := json.Marshal(body) @@ -250,7 +250,7 @@ func (s *userSuite) TestUserHandler_Get() { userId := "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) if err != nil { @@ -290,7 +290,7 @@ func (s *userSuite) TestUserHandler_GetUserWithWebAuthnCredential() { userId := "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) if err != nil { @@ -325,7 +325,7 @@ func (s *userSuite) TestUserHandler_Get_InvalidUserId() { if testing.Short() { s.T().Skip("skipping test in short mode.") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) userId := "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5" @@ -356,7 +356,7 @@ func (s *userSuite) TestUserHandler_GetUserIdByEmail_InvalidEmail() { if testing.Short() { s.T().Skip("skipping test in short mode.") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(`{"email": "123"}`)) req.Header.Set("Content-Type", "application/json") @@ -371,7 +371,7 @@ func (s *userSuite) TestUserHandler_GetUserIdByEmail_InvalidJson() { if testing.Short() { s.T().Skip("skipping test in short mode.") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(`"email": "123}`)) req.Header.Set("Content-Type", "application/json") @@ -386,7 +386,7 @@ func (s *userSuite) TestUserHandler_GetUserIdByEmail_UserNotFound() { if testing.Short() { s.T().Skip("skipping test in short mode.") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(`{"email": "unknownAddress@example.com"}`)) req.Header.Set("Content-Type", "application/json") @@ -406,7 +406,7 @@ func (s *userSuite) TestUserHandler_GetUserIdByEmail() { userId := "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(`{"email": "john.doe@example.com"}`)) req.Header.Set("Content-Type", "application/json") @@ -435,7 +435,7 @@ func (s *userSuite) TestUserHandler_GetUserIdByEmail_CaseInsensitive() { userId := "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(`{"email": "JOHN.DOE@EXAMPLE.COM"}`)) req.Header.Set("Content-Type", "application/json") @@ -464,7 +464,7 @@ func (s *userSuite) TestUserHandler_Me() { userId := "b5dd5267-b462-48be-b70d-bcd6f1bbe7a5" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) if err != nil { @@ -501,7 +501,7 @@ func (s *userSuite) TestUserHandler_Logout() { s.T().Skip("skipping test in short mode.") } userId, _ := uuid.NewV4() - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) if err != nil { @@ -542,7 +542,7 @@ func (s *userSuite) TestUserHandler_Delete() { userId, _ := uuid.FromString("b5dd5267-b462-48be-b70d-bcd6f1bbe7a5") cfg := test.DefaultConfig cfg.Account.AllowDeletion = true - e := NewPublicRouter(&cfg, s.Storage, nil) + e := NewPublicRouter(&cfg, s.Storage, nil, nil) jwkManager, err := jwk.NewDefaultManager(test.DefaultConfig.Secrets.Keys, s.Storage.GetJwkPersister()) if err != nil { diff --git a/backend/handler/webauthn_test.go b/backend/handler/webauthn_test.go index df05c04a7..f0a7757ef 100644 --- a/backend/handler/webauthn_test.go +++ b/backend/handler/webauthn_test.go @@ -33,7 +33,7 @@ func (s *webauthnSuite) TestWebauthnHandler_NewHandler() { if testing.Short() { s.T().Skip("skipping test in short mode") } - handler, err := NewWebauthnHandler(&test.DefaultConfig, s.Storage, s.GetDefaultSessionManager(), test.NewAuditLogger()) + handler, err := NewWebauthnHandler(&test.DefaultConfig, s.Storage, s.GetDefaultSessionManager(), test.NewAuditLogger(), nil) s.NoError(err) s.NotEmpty(handler) } @@ -48,7 +48,7 @@ func (s *webauthnSuite) TestWebauthnHandler_BeginRegistration() { userId := "ec4ef049-5b88-4321-a173-21b0eff06a04" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) sessionManager := s.GetDefaultSessionManager() token, err := sessionManager.GenerateJWT(uuid.FromStringOrNil(userId)) @@ -89,7 +89,7 @@ func (s *webauthnSuite) TestWebauthnHandler_FinalizeRegistration() { userId := "ec4ef049-5b88-4321-a173-21b0eff06a04" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) sessionManager := s.GetDefaultSessionManager() token, err := sessionManager.GenerateJWT(uuid.FromStringOrNil(userId)) @@ -135,7 +135,7 @@ func (s *webauthnSuite) TestWebauthnHandler_FinalizeRegistration_SessionDataExpi userId := "ec4ef049-5b88-4321-a173-21b0eff06a04" - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) sessionManager := s.GetDefaultSessionManager() token, err := sessionManager.GenerateJWT(uuid.FromStringOrNil(userId)) @@ -170,7 +170,7 @@ func (s *webauthnSuite) TestWebauthnHandler_BeginAuthentication() { err := s.LoadFixtures("../test/fixtures/webauthn") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodPost, "/webauthn/login/initialize", nil) rec := httptest.NewRecorder() @@ -194,7 +194,7 @@ func (s *webauthnSuite) TestWebauthnHandler_FinalizeAuthentication() { err := s.LoadFixtures("../test/fixtures/webauthn") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) body := `{ "id": "AaFdkcD4SuPjF-jwUoRwH8-ZHuY5RW46fsZmEvBX6RNKHaGtVzpATs06KQVheIOjYz-YneG4cmQOedzl0e0jF951ukx17Hl9jeGgWz5_DKZCO12p2-2LlzjH", @@ -247,7 +247,7 @@ func (s *webauthnSuite) TestWebauthnHandler_FinalizeAuthentication_SessionDataEx err := s.LoadFixtures("../test/fixtures/webauthn") s.Require().NoError(err) - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) body := `{ "id": "4iVZGFN_jktXJmwmBmaSq0Qr4T62T0jX7PS7XcgAWlM", @@ -279,7 +279,7 @@ func (s *webauthnSuite) TestWebauthnHandler_FinalizeAuthentication_TokenInHeader cfg := test.DefaultConfig cfg.Session.EnableAuthTokenHeader = true - e := NewPublicRouter(&cfg, s.Storage, nil) + e := NewPublicRouter(&cfg, s.Storage, nil, nil) body := `{ "id": "AaFdkcD4SuPjF-jwUoRwH8-ZHuY5RW46fsZmEvBX6RNKHaGtVzpATs06KQVheIOjYz-YneG4cmQOedzl0e0jF951ukx17Hl9jeGgWz5_DKZCO12p2-2LlzjH", diff --git a/backend/handler/well_known_test.go b/backend/handler/well_known_test.go index 7fe1c3ce9..c19af2191 100644 --- a/backend/handler/well_known_test.go +++ b/backend/handler/well_known_test.go @@ -22,7 +22,7 @@ func (s *wellKnownSuite) TestWellKnownHandler_GetPublicKeys() { s.T().Skip("skipping test in short mode") } - e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil) + e := NewPublicRouter(&test.DefaultConfig, s.Storage, nil, nil) req := httptest.NewRequest(http.MethodGet, "/.well-known/jwks.json", nil) rec := httptest.NewRecorder() From 097569ad8b2b30a25ba6f9053316d66d3617db0f Mon Sep 17 00:00:00 2001 From: Stefan Jacobi Date: Wed, 24 Jan 2024 15:25:26 +0100 Subject: [PATCH 03/24] chore(passkeys): change naming of aaguid map to authenticator metadata Closes: #1027 --- backend/Dockerfile.debug | 11 +++-- backend/cmd/serve/all.go | 10 ++--- backend/cmd/serve/public.go | 10 ++--- backend/dto/intern/WebauthnCredential.go | 4 +- backend/handler/public_router.go | 4 +- backend/handler/webauthn.go | 28 ++++++------ backend/mapper/aaguid_mapper.go | 48 -------------------- backend/mapper/authenticator_mapper.go | 50 +++++++++++++++++++++ backend/server/server.go | 4 +- deploy/docker-compose/quickstart.debug.yaml | 2 +- deploy/docker-compose/quickstart.e2e.yaml | 2 +- deploy/docker-compose/quickstart.yaml | 2 +- 12 files changed, 91 insertions(+), 84 deletions(-) delete mode 100644 backend/mapper/aaguid_mapper.go create mode 100644 backend/mapper/authenticator_mapper.go diff --git a/backend/Dockerfile.debug b/backend/Dockerfile.debug index 2e0a2a7da..41c81ebfb 100644 --- a/backend/Dockerfile.debug +++ b/backend/Dockerfile.debug @@ -1,9 +1,9 @@ # Build the hanko binary -FROM golang:1.20 as builder +FROM golang:1.20 AS builder WORKDIR /workspace # Get Delve -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install github.com/go-delve/delve/cmd/dlv@latest +RUN CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" go install github.com/go-delve/delve/cmd/dlv@latest COPY go.mod go.mod COPY go.sum go.sum @@ -29,10 +29,14 @@ COPY build_info build_info/ COPY middleware middleware/ COPY template template/ COPY utils utils/ +COPY mapper mapper/ + +# Load AAGUID map +RUN wget --quiet https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/main/aaguid.json -O aaguid.json # Build RUN go generate ./... -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -gcflags="all=-N -l" -a -o hanko main.go +RUN CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" go build -gcflags="all=-N -l" -a -o hanko main.go # Use distroless as minimal base image to package hanko binary # See https://github.com/GoogleContainerTools/distroless for details @@ -40,6 +44,7 @@ FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /go/bin/dlv . COPY --from=builder /workspace/hanko . +COPY --from=builder /workspace/aaguid.json /etc/config/ USER 65532:65532 EXPOSE 8000 8001 40000 diff --git a/backend/cmd/serve/all.go b/backend/cmd/serve/all.go index 0d058b699..4a0eb27d6 100644 --- a/backend/cmd/serve/all.go +++ b/backend/cmd/serve/all.go @@ -16,8 +16,8 @@ import ( func NewServeAllCommand() *cobra.Command { var ( - configFile string - aaguidMapFile string + configFile string + authenticatorMetadataFile string ) cmd := &cobra.Command{ @@ -30,7 +30,7 @@ func NewServeAllCommand() *cobra.Command { log.Fatal(err) } - aaguidMap := mapper.LoadAaguidMap(&aaguidMapFile) + authenticatorMetadata := mapper.LoadAuthenticatorMetadata(&authenticatorMetadataFile) persister, err := persistence.New(cfg.Database) if err != nil { @@ -41,7 +41,7 @@ func NewServeAllCommand() *cobra.Command { prometheus := echoprometheus.NewMiddleware("hanko") - go server.StartPublic(cfg, &wg, persister, prometheus, aaguidMap) + go server.StartPublic(cfg, &wg, persister, prometheus, authenticatorMetadata) go server.StartAdmin(cfg, &wg, persister, prometheus) wg.Wait() @@ -49,7 +49,7 @@ func NewServeAllCommand() *cobra.Command { } cmd.Flags().StringVar(&configFile, "config", config.DefaultConfigFilePath, "config file") - cmd.Flags().StringVar(&aaguidMapFile, "aaguid-map", "", "aaguid map file") + cmd.Flags().StringVar(&authenticatorMetadataFile, "auth-meta", "", "authenticator metadata file") return cmd } diff --git a/backend/cmd/serve/public.go b/backend/cmd/serve/public.go index 5695901cc..5f1de0f45 100644 --- a/backend/cmd/serve/public.go +++ b/backend/cmd/serve/public.go @@ -15,8 +15,8 @@ import ( func NewServePublicCommand() *cobra.Command { var ( - configFile string - aaguidMapFile string + configFile string + authenticatorMetadataFile string ) cmd := &cobra.Command{ @@ -29,7 +29,7 @@ func NewServePublicCommand() *cobra.Command { log.Fatal(err) } - aaguidMap := mapper.LoadAaguidMap(&aaguidMapFile) + authenticatorMetadata := mapper.LoadAuthenticatorMetadata(&authenticatorMetadataFile) persister, err := persistence.New(cfg.Database) if err != nil { @@ -38,14 +38,14 @@ func NewServePublicCommand() *cobra.Command { var wg sync.WaitGroup wg.Add(1) - go server.StartPublic(cfg, &wg, persister, nil, aaguidMap) + go server.StartPublic(cfg, &wg, persister, nil, authenticatorMetadata) wg.Wait() }, } cmd.Flags().StringVar(&configFile, "config", config.DefaultConfigFilePath, "config file") - cmd.Flags().StringVar(&aaguidMapFile, "aaguid-map", "", "config file") + cmd.Flags().StringVar(&authenticatorMetadataFile, "auth-meta", "", "authenticator metadata file") return cmd } diff --git a/backend/dto/intern/WebauthnCredential.go b/backend/dto/intern/WebauthnCredential.go index ea1f035bd..d44edb0ff 100644 --- a/backend/dto/intern/WebauthnCredential.go +++ b/backend/dto/intern/WebauthnCredential.go @@ -10,14 +10,14 @@ import ( "time" ) -func WebauthnCredentialToModel(credential *webauthn.Credential, userId uuid.UUID, backupEligible bool, backupState bool, aaguidMap mapper.AaguidMap) *models.WebauthnCredential { +func WebauthnCredentialToModel(credential *webauthn.Credential, userId uuid.UUID, backupEligible bool, backupState bool, authenticatorMetadata mapper.AuthenticatorMetadata) *models.WebauthnCredential { now := time.Now().UTC() aaguid, _ := uuid.FromBytes(credential.Authenticator.AAGUID) credentialID := base64.RawURLEncoding.EncodeToString(credential.ID) c := &models.WebauthnCredential{ ID: credentialID, - Name: aaguidMap.GetNameForAaguid(aaguid), + Name: authenticatorMetadata.GetNameForAaguid(aaguid), UserId: userId, PublicKey: base64.RawURLEncoding.EncodeToString(credential.PublicKey), AttestationType: credential.AttestationType, diff --git a/backend/handler/public_router.go b/backend/handler/public_router.go index 8c8ff90a0..691466534 100644 --- a/backend/handler/public_router.go +++ b/backend/handler/public_router.go @@ -18,7 +18,7 @@ import ( "github.com/teamhanko/hanko/backend/template" ) -func NewPublicRouter(cfg *config.Config, persister persistence.Persister, prometheus echo.MiddlewareFunc, aaguidMap mapper.AaguidMap) *echo.Echo { +func NewPublicRouter(cfg *config.Config, persister persistence.Persister, prometheus echo.MiddlewareFunc, authenticatorMetadata mapper.AuthenticatorMetadata) *echo.Echo { e := echo.New() e.Renderer = template.NewTemplateRenderer() e.HideBanner = true @@ -103,7 +103,7 @@ func NewPublicRouter(cfg *config.Config, persister persistence.Persister, promet } healthHandler := NewHealthHandler() - webauthnHandler, err := NewWebauthnHandler(cfg, persister, sessionManager, auditLogger, aaguidMap) + webauthnHandler, err := NewWebauthnHandler(cfg, persister, sessionManager, auditLogger, authenticatorMetadata) if err != nil { panic(fmt.Errorf("failed to create public webauthn handler: %w", err)) } diff --git a/backend/handler/webauthn.go b/backend/handler/webauthn.go index 8542c6fb9..27702c0fd 100644 --- a/backend/handler/webauthn.go +++ b/backend/handler/webauthn.go @@ -24,16 +24,16 @@ import ( ) type WebauthnHandler struct { - persister persistence.Persister - webauthn *webauthn.WebAuthn - sessionManager session.Manager - cfg *config.Config - auditLogger auditlog.Logger - aaguidMap mapper.AaguidMap + persister persistence.Persister + webauthn *webauthn.WebAuthn + sessionManager session.Manager + cfg *config.Config + auditLogger auditlog.Logger + authenticatorMetadata mapper.AuthenticatorMetadata } // NewWebauthnHandler creates a new handler which handles all webauthn related routes -func NewWebauthnHandler(cfg *config.Config, persister persistence.Persister, sessionManager session.Manager, auditLogger auditlog.Logger, aaguidMap mapper.AaguidMap) (*WebauthnHandler, error) { +func NewWebauthnHandler(cfg *config.Config, persister persistence.Persister, sessionManager session.Manager, auditLogger auditlog.Logger, authenticatorMetadata mapper.AuthenticatorMetadata) (*WebauthnHandler, error) { f := false wa, err := webauthn.New(&webauthn.Config{ RPDisplayName: cfg.Webauthn.RelyingParty.DisplayName, @@ -63,12 +63,12 @@ func NewWebauthnHandler(cfg *config.Config, persister persistence.Persister, ses } return &WebauthnHandler{ - persister: persister, - webauthn: wa, - sessionManager: sessionManager, - cfg: cfg, - auditLogger: auditLogger, - aaguidMap: aaguidMap, + persister: persister, + webauthn: wa, + sessionManager: sessionManager, + cfg: cfg, + auditLogger: auditLogger, + authenticatorMetadata: authenticatorMetadata, }, nil } @@ -199,7 +199,7 @@ func (h *WebauthnHandler) FinishRegistration(c echo.Context) error { backupEligible := request.Response.AttestationObject.AuthData.Flags.HasBackupEligible() backupState := request.Response.AttestationObject.AuthData.Flags.HasBackupState() - model := intern.WebauthnCredentialToModel(credential, sessionData.UserId, backupEligible, backupState, h.aaguidMap) + model := intern.WebauthnCredentialToModel(credential, sessionData.UserId, backupEligible, backupState, h.authenticatorMetadata) err = h.persister.GetWebauthnCredentialPersisterWithConnection(tx).Create(*model) if err != nil { return fmt.Errorf("failed to store webauthn credential: %w", err) diff --git a/backend/mapper/aaguid_mapper.go b/backend/mapper/aaguid_mapper.go deleted file mode 100644 index 7f3557c84..000000000 --- a/backend/mapper/aaguid_mapper.go +++ /dev/null @@ -1,48 +0,0 @@ -package mapper - -import ( - "fmt" - "github.com/gofrs/uuid" - "github.com/knadh/koanf/parsers/json" - "github.com/teamhanko/hanko/backend/config" - "log" -) - -type Aaguid struct { - Name string `json:"name"` - IconLight string `json:"icon_light"` - IconDark string `json:"icon_dark"` -} - -type AaguidMap map[string]Aaguid - -func (w AaguidMap) GetNameForAaguid(aaguid uuid.UUID) *string { - if webauthnAaguid, ok := w[aaguid.String()]; ok { - return &webauthnAaguid.Name - } else { - return nil - } -} - -func LoadAaguidMap(aaguidFilePath *string) AaguidMap { - k, err := config.LoadFile(aaguidFilePath, json.Parser()) - - if err != nil { - log.Println(err) - return nil - } - - if k == nil { - log.Println("no aaguid map file provided. Skipping...") - return nil - } - - var aaguidMap AaguidMap - err = k.Unmarshal("", &aaguidMap) - if err != nil { - log.Println(fmt.Errorf("unable to unmarshal aaguid map: %w", err)) - return nil - } - - return aaguidMap -} diff --git a/backend/mapper/authenticator_mapper.go b/backend/mapper/authenticator_mapper.go new file mode 100644 index 000000000..45a223e0f --- /dev/null +++ b/backend/mapper/authenticator_mapper.go @@ -0,0 +1,50 @@ +package mapper + +import ( + "fmt" + "github.com/gofrs/uuid" + "github.com/knadh/koanf/parsers/json" + "github.com/teamhanko/hanko/backend/config" + "log" +) + +type Authenticator struct { + Name string `json:"name"` + IconLight string `json:"icon_light"` + IconDark string `json:"icon_dark"` +} + +type AuthenticatorMetadata map[string]Authenticator + +func (am AuthenticatorMetadata) GetNameForAaguid(aaguid uuid.UUID) *string { + if am != nil { + if authenticatorMetadata, ok := am[aaguid.String()]; ok { + return &authenticatorMetadata.Name + } + } + + return nil +} + +func LoadAuthenticatorMetadata(authMetaFilePath *string) AuthenticatorMetadata { + k, err := config.LoadFile(authMetaFilePath, json.Parser()) + + if err != nil { + log.Println(err) + return nil + } + + if k == nil { + log.Println("no authenticator metadata file provided. Skipping...") + return nil + } + + var authenticatorMetadata AuthenticatorMetadata + err = k.Unmarshal("", &authenticatorMetadata) + if err != nil { + log.Println(fmt.Errorf("unable to unmarshal authenticator metadata: %w", err)) + return nil + } + + return authenticatorMetadata +} diff --git a/backend/server/server.go b/backend/server/server.go index bb8dae96a..fa4d63c04 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -9,9 +9,9 @@ import ( "sync" ) -func StartPublic(cfg *config.Config, wg *sync.WaitGroup, persister persistence.Persister, prometheus echo.MiddlewareFunc, aaguidMap mapper.AaguidMap) { +func StartPublic(cfg *config.Config, wg *sync.WaitGroup, persister persistence.Persister, prometheus echo.MiddlewareFunc, authenticatorMetadata mapper.AuthenticatorMetadata) { defer wg.Done() - router := handler.NewPublicRouter(cfg, persister, prometheus, aaguidMap) + router := handler.NewPublicRouter(cfg, persister, prometheus, authenticatorMetadata) router.Logger.Fatal(router.Start(cfg.Server.Public.Address)) } diff --git a/deploy/docker-compose/quickstart.debug.yaml b/deploy/docker-compose/quickstart.debug.yaml index 7b043d83b..14a0066fe 100644 --- a/deploy/docker-compose/quickstart.debug.yaml +++ b/deploy/docker-compose/quickstart.debug.yaml @@ -29,7 +29,7 @@ services: - '8001:8001' # admin - '40000:40000' # debug restart: unless-stopped - command: serve --config /etc/config/config.yaml all + command: serve --config /etc/config/config.yaml --auth-meta /etc/config/aaguid.json all volumes: - type: bind source: ./config.yaml diff --git a/deploy/docker-compose/quickstart.e2e.yaml b/deploy/docker-compose/quickstart.e2e.yaml index 9ecc3933b..f8e219e2a 100644 --- a/deploy/docker-compose/quickstart.e2e.yaml +++ b/deploy/docker-compose/quickstart.e2e.yaml @@ -37,7 +37,7 @@ services: - '8000:8000' # public - '8001:8001' # admin restart: unless-stopped - command: serve --config /etc/config/config.yaml all + command: serve --config /etc/config/config.yaml --auth-meta /etc/config/aaguid.json all volumes: - type: bind source: ./config.yaml diff --git a/deploy/docker-compose/quickstart.yaml b/deploy/docker-compose/quickstart.yaml index b08c6c69f..49cd16805 100644 --- a/deploy/docker-compose/quickstart.yaml +++ b/deploy/docker-compose/quickstart.yaml @@ -22,7 +22,7 @@ services: - '8000:8000' # public - '8001:8001' # admin restart: unless-stopped - command: serve --config /etc/config/config.yaml --aaguid-map /etc/config/aaguid.json all + command: serve --config /etc/config/config.yaml --auth-meta /etc/config/aaguid.json all volumes: - type: bind source: ./config.yaml From 5345d4cd33b7ed3e4f3c7e1a2283e10d70919908 Mon Sep 17 00:00:00 2001 From: Stefan Jacobi Date: Mon, 29 Jan 2024 11:16:06 +0100 Subject: [PATCH 04/24] chore(aaguid): switch to embedded file instead of docker file wget for agguid json --- backend/Dockerfile | 4 --- backend/Dockerfile.debug | 4 --- .../mapper}/aaguid.json | 0 backend/mapper/authenticator_mapper.go | 32 ++++++++++++++++--- deploy/docker-compose/quickstart.debug.yaml | 2 +- deploy/docker-compose/quickstart.e2e.yaml | 2 +- deploy/docker-compose/quickstart.yaml | 2 +- 7 files changed, 30 insertions(+), 16 deletions(-) rename {deploy/docker-compose => backend/mapper}/aaguid.json (100%) diff --git a/backend/Dockerfile b/backend/Dockerfile index 1cbbe8bf7..d02211af0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -30,9 +30,6 @@ COPY template template/ COPY utils utils/ COPY mapper mapper/ -# Load AAGUID map -RUN wget --quiet https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/main/aaguid.json -O aaguid.json - # Build RUN go generate ./... RUN CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" go build -a -o hanko main.go @@ -42,7 +39,6 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" go build -a -o hanko main.go FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/hanko . -COPY --from=builder /workspace/aaguid.json /etc/config/ USER 65532:65532 ENTRYPOINT ["/hanko"] diff --git a/backend/Dockerfile.debug b/backend/Dockerfile.debug index 41c81ebfb..ea5129bb2 100644 --- a/backend/Dockerfile.debug +++ b/backend/Dockerfile.debug @@ -31,9 +31,6 @@ COPY template template/ COPY utils utils/ COPY mapper mapper/ -# Load AAGUID map -RUN wget --quiet https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/main/aaguid.json -O aaguid.json - # Build RUN go generate ./... RUN CGO_ENABLED=0 GOOS=linux GOARCH="$TARGETARCH" go build -gcflags="all=-N -l" -a -o hanko main.go @@ -44,7 +41,6 @@ FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /go/bin/dlv . COPY --from=builder /workspace/hanko . -COPY --from=builder /workspace/aaguid.json /etc/config/ USER 65532:65532 EXPOSE 8000 8001 40000 diff --git a/deploy/docker-compose/aaguid.json b/backend/mapper/aaguid.json similarity index 100% rename from deploy/docker-compose/aaguid.json rename to backend/mapper/aaguid.json diff --git a/backend/mapper/authenticator_mapper.go b/backend/mapper/authenticator_mapper.go index 45a223e0f..30ee520ee 100644 --- a/backend/mapper/authenticator_mapper.go +++ b/backend/mapper/authenticator_mapper.go @@ -1,13 +1,18 @@ package mapper import ( + _ "embed" + "encoding/json" "fmt" "github.com/gofrs/uuid" - "github.com/knadh/koanf/parsers/json" + kjson "github.com/knadh/koanf/parsers/json" "github.com/teamhanko/hanko/backend/config" "log" ) +//go:embed aaguid.json +var authenticatorMetadataJson []byte + type Authenticator struct { Name string `json:"name"` IconLight string `json:"icon_light"` @@ -27,19 +32,25 @@ func (am AuthenticatorMetadata) GetNameForAaguid(aaguid uuid.UUID) *string { } func LoadAuthenticatorMetadata(authMetaFilePath *string) AuthenticatorMetadata { - k, err := config.LoadFile(authMetaFilePath, json.Parser()) + k, err := config.LoadFile(authMetaFilePath, kjson.Parser()) if err != nil { log.Println(err) return nil } + var authenticatorMetadata AuthenticatorMetadata + if k == nil { - log.Println("no authenticator metadata file provided. Skipping...") - return nil + log.Println("no authenticator metadata file provided. Using embedded one.") + authenticatorMetadata, err = loadFromEmbeddedFile() + if err != nil { + log.Println("no valid authenticator metadata file provided. Skipping...") + } + + return authenticatorMetadata } - var authenticatorMetadata AuthenticatorMetadata err = k.Unmarshal("", &authenticatorMetadata) if err != nil { log.Println(fmt.Errorf("unable to unmarshal authenticator metadata: %w", err)) @@ -48,3 +59,14 @@ func LoadAuthenticatorMetadata(authMetaFilePath *string) AuthenticatorMetadata { return authenticatorMetadata } + +func loadFromEmbeddedFile() (AuthenticatorMetadata, error) { + var authMeta AuthenticatorMetadata + err := json.Unmarshal(authenticatorMetadataJson, &authMeta) + if err != nil { + log.Println(fmt.Errorf("unable to unmarshal authenticator metadata: %w", err)) + return nil, err + } + + return authMeta, nil +} diff --git a/deploy/docker-compose/quickstart.debug.yaml b/deploy/docker-compose/quickstart.debug.yaml index 14a0066fe..7b043d83b 100644 --- a/deploy/docker-compose/quickstart.debug.yaml +++ b/deploy/docker-compose/quickstart.debug.yaml @@ -29,7 +29,7 @@ services: - '8001:8001' # admin - '40000:40000' # debug restart: unless-stopped - command: serve --config /etc/config/config.yaml --auth-meta /etc/config/aaguid.json all + command: serve --config /etc/config/config.yaml all volumes: - type: bind source: ./config.yaml diff --git a/deploy/docker-compose/quickstart.e2e.yaml b/deploy/docker-compose/quickstart.e2e.yaml index f8e219e2a..9ecc3933b 100644 --- a/deploy/docker-compose/quickstart.e2e.yaml +++ b/deploy/docker-compose/quickstart.e2e.yaml @@ -37,7 +37,7 @@ services: - '8000:8000' # public - '8001:8001' # admin restart: unless-stopped - command: serve --config /etc/config/config.yaml --auth-meta /etc/config/aaguid.json all + command: serve --config /etc/config/config.yaml all volumes: - type: bind source: ./config.yaml diff --git a/deploy/docker-compose/quickstart.yaml b/deploy/docker-compose/quickstart.yaml index 49cd16805..21d58f96b 100644 --- a/deploy/docker-compose/quickstart.yaml +++ b/deploy/docker-compose/quickstart.yaml @@ -22,7 +22,7 @@ services: - '8000:8000' # public - '8001:8001' # admin restart: unless-stopped - command: serve --config /etc/config/config.yaml --auth-meta /etc/config/aaguid.json all + command: serve --config /etc/config/config.yaml all volumes: - type: bind source: ./config.yaml From 239748d72d61449ef1b26e10f1b2f82898887e32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:45:07 +0100 Subject: [PATCH 05/24] chore(deps): bump github.com/nicksnyder/go-i18n/v2 in /backend Bumps [github.com/nicksnyder/go-i18n/v2](https://github.com/nicksnyder/go-i18n) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/nicksnyder/go-i18n/releases) - [Changelog](https://github.com/nicksnyder/go-i18n/blob/main/CHANGELOG.md) - [Commits](https://github.com/nicksnyder/go-i18n/compare/v2.3.0...v2.4.0) --- updated-dependencies: - dependency-name: github.com/nicksnyder/go-i18n/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/go.mod | 2 +- backend/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 3ba1c3bd6..0345d17f5 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -26,7 +26,7 @@ require ( github.com/labstack/echo/v4 v4.11.4 github.com/lestrrat-go/jwx/v2 v2.0.19 github.com/lib/pq v1.10.9 - github.com/nicksnyder/go-i18n/v2 v2.3.0 + github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/ory/dockertest/v3 v3.10.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.31.0 diff --git a/backend/go.sum b/backend/go.sum index 62902a3cf..03c57afcc 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -482,8 +482,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ= -github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= +github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= +github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= From 21195939086501ebaf271694a9d0a944c11c5d13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:47:21 +0100 Subject: [PATCH 06/24] chore(deps): bump github.com/opencontainers/runc in /backend Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.5 to 1.1.12. - [Release notes](https://github.com/opencontainers/runc/releases) - [Changelog](https://github.com/opencontainers/runc/blob/v1.1.12/CHANGELOG.md) - [Commits](https://github.com/opencontainers/runc/compare/v1.1.5...v1.1.12) --- updated-dependencies: - dependency-name: github.com/opencontainers/runc dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/go.mod | 2 +- backend/go.sum | 30 ++---------------------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 0345d17f5..dfd59a817 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -129,7 +129,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect - github.com/opencontainers/runc v1.1.5 // indirect + github.com/opencontainers/runc v1.1.12 // indirect github.com/paulmach/orb v0.9.0 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect diff --git a/backend/go.sum b/backend/go.sum index 03c57afcc..c18348dad 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -57,14 +57,11 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -72,13 +69,11 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -91,7 +86,6 @@ github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -106,7 +100,6 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -176,7 +169,6 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -466,7 +458,6 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -477,7 +468,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= @@ -490,10 +480,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -558,14 +546,12 @@ github.com/russellhaering/gosaml2 v0.9.1/go.mod h1:ja+qgbayxm+0mxBRLMSUuX3COqy+s github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= @@ -579,12 +565,10 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= @@ -613,15 +597,11 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -713,7 +693,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= @@ -752,14 +731,12 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -779,9 +756,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From d7f10614a5b52e87eea81a09528b33c0fb475a75 Mon Sep 17 00:00:00 2001 From: lfleischmann <67686424+lfleischmann@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:32:24 +0100 Subject: [PATCH 07/24] chore: update config json schema --- backend/json_schema/hanko.config.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/json_schema/hanko.config.json b/backend/json_schema/hanko.config.json index aa5cb0d70..b31056aea 100644 --- a/backend/json_schema/hanko.config.json +++ b/backend/json_schema/hanko.config.json @@ -130,6 +130,9 @@ "webauthn": { "$ref": "#/$defs/WebauthnSettings" }, + "smtp": { + "$ref": "#/$defs/SMTP" + }, "passcode": { "$ref": "#/$defs/Passcode" }, @@ -379,19 +382,17 @@ "email": { "$ref": "#/$defs/Email" }, - "smtp": { - "$ref": "#/$defs/SMTP" - }, "ttl": { "type": "integer", "default": 300 + }, + "smtp": { + "$ref": "#/$defs/SMTP", + "description": "Deprecated: Use root level Smtp instead" } }, "additionalProperties": false, - "type": "object", - "required": [ - "smtp" - ] + "type": "object" }, "Password": { "properties": { From 2715899bd7eac2b54ee10d03003d079657fa16f8 Mon Sep 17 00:00:00 2001 From: lfleischmann <67686424+lfleischmann@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:49:13 +0100 Subject: [PATCH 08/24] ci: enable dependabot version updates for actions --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 11a31bca7..3a1969bb0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,3 +17,10 @@ updates: # Always increase the version requirement # to match the new version. versioning-strategy: increase + + - package-ecosystem: "github-actions" + # Workflow files stored in the default location of `.github/workflows`. + # (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.) + directory: "/" + schedule: + interval: "weekly" From d871ec2fb1b86b896e779cf1e75bd4b38253fa18 Mon Sep 17 00:00:00 2001 From: lfleischmann <67686424+lfleischmann@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:01:38 +0100 Subject: [PATCH 09/24] fix: make samesite and domain attributes configurable for frontend sdk cookie --- .../hanko-frontend-sdk/AccountConfig.html | 2 +- .../AuthFlowCompletedDetail.html | 2 +- .../jsdoc/hanko-frontend-sdk/Client.html | 2 +- .../jsdoc/hanko-frontend-sdk/Config.html | 2 +- .../hanko-frontend-sdk/ConfigClient.html | 2 +- .../hanko-frontend-sdk/ConflictError.html | 2 +- .../jsdoc/hanko-frontend-sdk/Cookie.html | 12 +-- .../hanko-frontend-sdk/CookieOptions.html | 74 ++++++++++++++++++- .../jsdoc/hanko-frontend-sdk/Credential.html | 2 +- .../jsdoc/hanko-frontend-sdk/Dispatcher.html | 2 +- .../hanko-frontend-sdk/DispatcherOptions.html | 2 +- .../jsdoc/hanko-frontend-sdk/Email.html | 2 +- .../EmailAddressAlreadyExistsError.html | 2 +- .../jsdoc/hanko-frontend-sdk/EmailClient.html | 2 +- .../jsdoc/hanko-frontend-sdk/EmailConfig.html | 2 +- .../jsdoc/hanko-frontend-sdk/Emails.html | 2 +- .../hanko-frontend-sdk/EnterpriseClient.html | 2 +- .../hanko-frontend-sdk/ForbiddenError.html | 2 +- .../jsdoc/hanko-frontend-sdk/Hanko.html | 24 +++--- .../jsdoc/hanko-frontend-sdk/Hanko.ts.html | 15 +++- .../jsdoc/hanko-frontend-sdk/HankoError.html | 2 +- .../hanko-frontend-sdk/HankoOptions.html | 66 ++++++++++++++++- .../jsdoc/hanko-frontend-sdk/Headers.html | 4 +- .../jsdoc/hanko-frontend-sdk/HttpClient.html | 14 ++-- .../hanko-frontend-sdk/HttpClientOptions.html | 55 +++++++++++++- .../jsdoc/hanko-frontend-sdk/Identity.html | 2 +- .../InvalidPasscodeError.html | 2 +- .../InvalidPasswordError.html | 2 +- .../InvalidWebauthnCredentialError.html | 2 +- .../jsdoc/hanko-frontend-sdk/Listener.html | 2 +- .../hanko-frontend-sdk/LocalStorage.html | 2 +- .../LocalStoragePasscode.html | 2 +- .../LocalStoragePassword.html | 2 +- .../LocalStorageSession.html | 2 +- .../hanko-frontend-sdk/LocalStorageUser.html | 2 +- .../hanko-frontend-sdk/LocalStorageUsers.html | 2 +- .../LocalStorageWebauthn.html | 2 +- .../MaxNumOfEmailAddressesReachedError.html | 2 +- .../MaxNumOfPasscodeAttemptsReachedError.html | 2 +- .../hanko-frontend-sdk/NotFoundError.html | 2 +- .../jsdoc/hanko-frontend-sdk/Passcode.html | 2 +- .../hanko-frontend-sdk/PasscodeClient.html | 2 +- .../PasscodeExpiredError.html | 2 +- .../hanko-frontend-sdk/PasscodeState.html | 2 +- .../hanko-frontend-sdk/PasswordClient.html | 2 +- .../hanko-frontend-sdk/PasswordConfig.html | 2 +- .../hanko-frontend-sdk/PasswordState.html | 2 +- .../jsdoc/hanko-frontend-sdk/Relay.html | 2 +- .../hanko-frontend-sdk/RelayOptions.html | 2 +- .../RequestTimeoutError.html | 2 +- .../jsdoc/hanko-frontend-sdk/Response.html | 6 +- .../jsdoc/hanko-frontend-sdk/Scheduler.html | 2 +- .../jsdoc/hanko-frontend-sdk/Session.html | 2 +- .../hanko-frontend-sdk/SessionDetail.html | 2 +- .../hanko-frontend-sdk/SessionOptions.html | 2 +- .../hanko-frontend-sdk/SessionState.html | 2 +- .../SessionStateOptions.html | 2 +- .../jsdoc/hanko-frontend-sdk/State.html | 2 +- .../hanko-frontend-sdk/TechnicalError.html | 2 +- .../hanko-frontend-sdk/ThirdPartyClient.html | 2 +- .../hanko-frontend-sdk/ThirdPartyError.html | 2 +- .../jsdoc/hanko-frontend-sdk/Throttle.html | 2 +- .../hanko-frontend-sdk/ThrottleOptions.html | 2 +- .../jsdoc/hanko-frontend-sdk/TokenClient.html | 2 +- .../hanko-frontend-sdk/TokenFinalized.html | 2 +- .../TooManyRequestsError.html | 2 +- .../hanko-frontend-sdk/UnauthorizedError.html | 2 +- .../static/jsdoc/hanko-frontend-sdk/User.html | 2 +- .../jsdoc/hanko-frontend-sdk/UserClient.html | 2 +- .../jsdoc/hanko-frontend-sdk/UserCreated.html | 2 +- .../jsdoc/hanko-frontend-sdk/UserInfo.html | 2 +- .../jsdoc/hanko-frontend-sdk/UserState.html | 2 +- .../UserVerificationError.html | 2 +- .../hanko-frontend-sdk/WebauthnClient.html | 2 +- .../WebauthnCredential.html | 2 +- .../WebauthnCredentials.html | 2 +- .../hanko-frontend-sdk/WebauthnFinalized.html | 2 +- .../WebauthnRequestCancelledError.html | 2 +- .../hanko-frontend-sdk/WebauthnState.html | 2 +- .../hanko-frontend-sdk/WebauthnSupport.html | 2 +- .../WebauthnTransports.html | 2 +- .../jsdoc/hanko-frontend-sdk/index.html | 2 +- .../hanko-frontend-sdk/lib_Cookie.ts.html | 61 ++++++++++----- .../jsdoc/hanko-frontend-sdk/lib_Dto.ts.html | 2 +- .../hanko-frontend-sdk/lib_Errors.ts.html | 2 +- .../hanko-frontend-sdk/lib_Session.ts.html | 2 +- .../hanko-frontend-sdk/lib_Throttle.ts.html | 2 +- .../lib_WebauthnSupport.ts.html | 2 +- .../lib_client_Client.ts.html | 2 +- .../lib_client_ConfigClient.ts.html | 2 +- .../lib_client_EmailClient.ts.html | 2 +- .../lib_client_EnterpriseClient.ts.html | 2 +- .../lib_client_HttpClient.ts.html | 8 +- .../lib_client_PasscodeClient.ts.html | 2 +- .../lib_client_PasswordClient.ts.html | 2 +- .../lib_client_ThirdPartyClient.ts.html | 2 +- .../lib_client_TokenClient.ts.html | 2 +- .../lib_client_UserClient.ts.html | 2 +- .../lib_client_WebauthnClient.ts.html | 2 +- .../lib_events_CustomEvents.ts.html | 2 +- .../lib_events_Dispatcher.ts.html | 2 +- .../lib_events_Listener.ts.html | 2 +- .../lib_events_Relay.ts.html | 2 +- .../lib_events_Scheduler.ts.html | 2 +- .../lib_state_State.ts.html | 2 +- .../lib_state_session_SessionState.ts.html | 2 +- .../lib_state_users_PasscodeState.ts.html | 2 +- .../lib_state_users_PasswordState.ts.html | 2 +- .../lib_state_users_UserState.ts.html | 2 +- .../lib_state_users_WebauthnState.ts.html | 2 +- frontend/elements/README.md | 3 + frontend/elements/src/Elements.tsx | 10 ++- frontend/frontend-sdk/src/Hanko.ts | 13 ++++ frontend/frontend-sdk/src/index.ts | 6 ++ frontend/frontend-sdk/src/lib/Cookie.ts | 59 ++++++++++----- .../frontend-sdk/src/lib/client/HttpClient.ts | 2 + .../frontend-sdk/tests/lib/Cookie.spec.ts | 33 +++++++++ 117 files changed, 483 insertions(+), 180 deletions(-) diff --git a/docs/static/jsdoc/hanko-frontend-sdk/AccountConfig.html b/docs/static/jsdoc/hanko-frontend-sdk/AccountConfig.html index bc65e2665..68dc2bd0c 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/AccountConfig.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/AccountConfig.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/AuthFlowCompletedDetail.html b/docs/static/jsdoc/hanko-frontend-sdk/AuthFlowCompletedDetail.html index 17d083c60..c84d7c32f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/AuthFlowCompletedDetail.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/AuthFlowCompletedDetail.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Client.html b/docs/static/jsdoc/hanko-frontend-sdk/Client.html index 7e042f91a..a94ac628e 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Client.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Client.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Config.html b/docs/static/jsdoc/hanko-frontend-sdk/Config.html index 69ca91d6f..d2b81c635 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Config.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Config.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/ConfigClient.html b/docs/static/jsdoc/hanko-frontend-sdk/ConfigClient.html index 410fa6f3f..477c9e09b 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/ConfigClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/ConfigClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/ConflictError.html b/docs/static/jsdoc/hanko-frontend-sdk/ConflictError.html index aa131628b..290110a4a 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/ConflictError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/ConflictError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Cookie.html b/docs/static/jsdoc/hanko-frontend-sdk/Cookie.html index 69009569a..0a83c5347 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Cookie.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Cookie.html @@ -66,7 +66,7 @@ @@ -218,7 +218,7 @@
Parameters:

View Source - lib/Cookie.ts, line 10 + lib/Cookie.ts, line 11

@@ -347,7 +347,7 @@

View Source - lib/Cookie.ts, line 66 + lib/Cookie.ts, line 78

@@ -467,7 +467,7 @@

View Source - lib/Cookie.ts, line 79 + lib/Cookie.ts, line 91

@@ -582,7 +582,7 @@

Parameters:
-SetAuthCookieOptions +CookieAttributes @@ -640,7 +640,7 @@
Parameters:

View Source - lib/Cookie.ts, line 74 + lib/Cookie.ts, line 86

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/CookieOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/CookieOptions.html index 6c64c3373..8ebeb664d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/CookieOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/CookieOptions.html @@ -66,7 +66,7 @@ @@ -114,6 +114,8 @@
Properties:
Type + Attributes + @@ -139,6 +141,12 @@
Properties:
+ + + + + + @@ -146,6 +154,68 @@
Properties:
+ + + + cookieDomain + + + + + +string + + + + + + + + + <optional>
+ + + + + + + + + The domain where the cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. + + + + + + + cookieSameSite + + + + + +string + + + + + + + + + <optional>
+ + + + + + + + + Specify whether/when cookies are sent with cross-site requests. Defaults to "lax". + + + @@ -187,7 +257,7 @@
Properties:

View Source - lib/Cookie.ts, line 42 + lib/Cookie.ts, line 60

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Credential.html b/docs/static/jsdoc/hanko-frontend-sdk/Credential.html index 8ac9a438e..2bb4c712f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Credential.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Credential.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Dispatcher.html b/docs/static/jsdoc/hanko-frontend-sdk/Dispatcher.html index 2089ab41e..9ef32ab74 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Dispatcher.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Dispatcher.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/DispatcherOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/DispatcherOptions.html index 47efdd8b3..a056169ca 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/DispatcherOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/DispatcherOptions.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Email.html b/docs/static/jsdoc/hanko-frontend-sdk/Email.html index f6d4a5837..65e67b318 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Email.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Email.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/EmailAddressAlreadyExistsError.html b/docs/static/jsdoc/hanko-frontend-sdk/EmailAddressAlreadyExistsError.html index 5d0ecac9e..6c6367c7b 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/EmailAddressAlreadyExistsError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/EmailAddressAlreadyExistsError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/EmailClient.html b/docs/static/jsdoc/hanko-frontend-sdk/EmailClient.html index 4d03f3d4d..c27a87185 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/EmailClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/EmailClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/EmailConfig.html b/docs/static/jsdoc/hanko-frontend-sdk/EmailConfig.html index b7dc49881..71b6089f1 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/EmailConfig.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/EmailConfig.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Emails.html b/docs/static/jsdoc/hanko-frontend-sdk/Emails.html index 10e34e766..685557d7f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Emails.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Emails.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/EnterpriseClient.html b/docs/static/jsdoc/hanko-frontend-sdk/EnterpriseClient.html index 022bd1bc9..d72860934 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/EnterpriseClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/EnterpriseClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/ForbiddenError.html b/docs/static/jsdoc/hanko-frontend-sdk/ForbiddenError.html index ec0997283..25ba3d80b 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/ForbiddenError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/ForbiddenError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Hanko.html b/docs/static/jsdoc/hanko-frontend-sdk/Hanko.html index 88e5e05b2..f3a337115 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Hanko.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Hanko.html @@ -66,7 +66,7 @@ @@ -386,7 +386,7 @@

View Source - Hanko.ts, line 56 + Hanko.ts, line 62

@@ -459,7 +459,7 @@

View Source - Hanko.ts, line 81 + Hanko.ts, line 87

@@ -532,7 +532,7 @@

View Source - Hanko.ts, line 91 + Hanko.ts, line 97

@@ -605,7 +605,7 @@

View Source - Hanko.ts, line 76 + Hanko.ts, line 82

@@ -678,7 +678,7 @@

View Source - Hanko.ts, line 71 + Hanko.ts, line 77

@@ -751,7 +751,7 @@

View Source - Hanko.ts, line 101 + Hanko.ts, line 107

@@ -824,7 +824,7 @@

View Source - Hanko.ts, line 106 + Hanko.ts, line 112

@@ -897,7 +897,7 @@

View Source - Hanko.ts, line 86 + Hanko.ts, line 92

@@ -970,7 +970,7 @@

View Source - Hanko.ts, line 96 + Hanko.ts, line 102

@@ -1043,7 +1043,7 @@

View Source - Hanko.ts, line 61 + Hanko.ts, line 67

@@ -1116,7 +1116,7 @@

View Source - Hanko.ts, line 66 + Hanko.ts, line 72

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Hanko.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/Hanko.ts.html index 87ff634aa..76b874259 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Hanko.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Hanko.ts.html @@ -68,7 +68,7 @@ @@ -97,6 +97,7 @@

Hanko.ts

import { Listener } from "./lib/events/Listener"; import { Relay } from "./lib/events/Relay"; import { Session } from "./lib/Session"; +import { CookieSameSite } from "./lib/Cookie"; /** * The options for the Hanko class @@ -104,11 +105,15 @@

Hanko.ts

* @interface * @property {number=} timeout - The http request timeout in milliseconds. Defaults to 13000ms * @property {string=} cookieName - The name of the session cookie set from the SDK. Defaults to "hanko" + * @property {string=} cookieDomain - The domain where the cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. + * @property {string=} cookieSameSite - Specify whether/when cookies are sent with cross-site requests. Defaults to "lax". * @property {string=} localStorageKey - The prefix / name of the local storage keys. Defaults to "hanko" */ export interface HankoOptions { timeout?: number; cookieName?: string; + cookieDomain?: string; + cookieSameSite?: CookieSameSite; localStorageKey?: string; } @@ -150,6 +155,12 @@

Hanko.ts

if (options?.localStorageKey !== undefined) { opts.localStorageKey = options.localStorageKey; } + if (options?.cookieDomain !== undefined) { + opts.cookieDomain = options.cookieDomain; + } + if (options?.cookieSameSite !== undefined) { + opts.cookieSameSite = options.cookieSameSite; + } this.api = api; /** @@ -214,6 +225,8 @@

Hanko.ts

export interface InternalOptions { timeout: number; cookieName: string; + cookieDomain?: string; + cookieSameSite?: CookieSameSite; localStorageKey: string; } diff --git a/docs/static/jsdoc/hanko-frontend-sdk/HankoError.html b/docs/static/jsdoc/hanko-frontend-sdk/HankoError.html index c884b9307..cd5e991b7 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/HankoError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/HankoError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/HankoOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/HankoOptions.html index a0afe9565..8b3839ab9 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/HankoOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/HankoOptions.html @@ -66,7 +66,7 @@ @@ -188,6 +188,68 @@
Properties:
+ + + cookieDomain + + + + + +string + + + + + + + + + <optional>
+ + + + + + + + + The domain where the cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. + + + + + + + cookieSameSite + + + + + +string + + + + + + + + + <optional>
+ + + + + + + + + Specify whether/when cookies are sent with cross-site requests. Defaults to "lax". + + + + localStorageKey @@ -259,7 +321,7 @@
Properties:

View Source - Hanko.ts, line 123 + Hanko.ts, line 130

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Headers.html b/docs/static/jsdoc/hanko-frontend-sdk/Headers.html index ee7dbe249..3fb4b5912 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Headers.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Headers.html @@ -66,7 +66,7 @@ @@ -398,7 +398,7 @@
Parameters:

View Source - lib/client/HttpClient.ts, line 286 + lib/client/HttpClient.ts, line 287

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/HttpClient.html b/docs/static/jsdoc/hanko-frontend-sdk/HttpClient.html index aec134bbc..89e9d6e6d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/HttpClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/HttpClient.html @@ -66,7 +66,7 @@ @@ -422,7 +422,7 @@
Parameters:

View Source - lib/client/HttpClient.ts, line 376 + lib/client/HttpClient.ts, line 378

@@ -630,7 +630,7 @@
Parameters:

View Source - lib/client/HttpClient.ts, line 333 + lib/client/HttpClient.ts, line 335

@@ -883,7 +883,7 @@
Parameters:

View Source - lib/client/HttpClient.ts, line 366 + lib/client/HttpClient.ts, line 368

@@ -1136,7 +1136,7 @@
Parameters:

View Source - lib/client/HttpClient.ts, line 344 + lib/client/HttpClient.ts, line 346

@@ -1370,7 +1370,7 @@
Parameters:

View Source - lib/client/HttpClient.ts, line 323 + lib/client/HttpClient.ts, line 325

@@ -1563,7 +1563,7 @@
Parameters:

View Source - lib/client/HttpClient.ts, line 355 + lib/client/HttpClient.ts, line 357

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/HttpClientOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/HttpClientOptions.html index b5052fd3f..8c2ab2c22 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/HttpClientOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/HttpClientOptions.html @@ -66,7 +66,7 @@ @@ -114,6 +114,8 @@
Properties:
Type + Attributes + @@ -139,6 +141,12 @@
Properties:
+ + + + + + @@ -162,6 +170,12 @@
Properties:
+ + + + + + @@ -170,6 +184,37 @@
Properties:
+ + + cookieDomain + + + + + +string + + + + + + + + + <optional>
+ + + + + + + + + The domain where cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. + + + + localStorageKey @@ -185,6 +230,12 @@
Properties:
+ + + + + + @@ -233,7 +284,7 @@
Properties:

View Source - lib/client/HttpClient.ts, line 304 + lib/client/HttpClient.ts, line 305

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Identity.html b/docs/static/jsdoc/hanko-frontend-sdk/Identity.html index 96f701154..787e14572 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Identity.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Identity.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasscodeError.html b/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasscodeError.html index 4d19a505c..d72e7c899 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasscodeError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasscodeError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasswordError.html b/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasswordError.html index 1f0cfa4d6..51b22d374 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasswordError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/InvalidPasswordError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/InvalidWebauthnCredentialError.html b/docs/static/jsdoc/hanko-frontend-sdk/InvalidWebauthnCredentialError.html index 1da32bef1..35f5d336b 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/InvalidWebauthnCredentialError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/InvalidWebauthnCredentialError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Listener.html b/docs/static/jsdoc/hanko-frontend-sdk/Listener.html index 958c41dc9..5b916451e 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Listener.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Listener.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorage.html b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorage.html index 28ce81452..254b8cb6d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorage.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorage.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePasscode.html b/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePasscode.html index 96e58333e..e8adb8311 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePasscode.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePasscode.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePassword.html b/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePassword.html index a7c9f993c..57827f977 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePassword.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/LocalStoragePassword.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageSession.html b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageSession.html index 59d233ed1..cb02b1d2e 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageSession.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageSession.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUser.html b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUser.html index 438626481..f81e974be 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUser.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUser.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUsers.html b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUsers.html index 2fca9d500..e05866716 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUsers.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageUsers.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageWebauthn.html b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageWebauthn.html index 0728d61ae..261c738cf 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageWebauthn.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/LocalStorageWebauthn.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfEmailAddressesReachedError.html b/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfEmailAddressesReachedError.html index 3dc2bcaf0..88d94759c 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfEmailAddressesReachedError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfEmailAddressesReachedError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfPasscodeAttemptsReachedError.html b/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfPasscodeAttemptsReachedError.html index 4cf108460..c41d28d72 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfPasscodeAttemptsReachedError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/MaxNumOfPasscodeAttemptsReachedError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/NotFoundError.html b/docs/static/jsdoc/hanko-frontend-sdk/NotFoundError.html index d0fc34530..ed6970d58 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/NotFoundError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/NotFoundError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Passcode.html b/docs/static/jsdoc/hanko-frontend-sdk/Passcode.html index e0d6fcdfd..89f6565c1 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Passcode.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Passcode.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/PasscodeClient.html b/docs/static/jsdoc/hanko-frontend-sdk/PasscodeClient.html index 7d4d41b99..19c737053 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/PasscodeClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/PasscodeClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/PasscodeExpiredError.html b/docs/static/jsdoc/hanko-frontend-sdk/PasscodeExpiredError.html index 0cf43869e..7f2412fed 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/PasscodeExpiredError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/PasscodeExpiredError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/PasscodeState.html b/docs/static/jsdoc/hanko-frontend-sdk/PasscodeState.html index 0d28585a3..8a451b21f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/PasscodeState.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/PasscodeState.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/PasswordClient.html b/docs/static/jsdoc/hanko-frontend-sdk/PasswordClient.html index f54241251..7449c2b90 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/PasswordClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/PasswordClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/PasswordConfig.html b/docs/static/jsdoc/hanko-frontend-sdk/PasswordConfig.html index 2bdcf695d..8cf85148c 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/PasswordConfig.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/PasswordConfig.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/PasswordState.html b/docs/static/jsdoc/hanko-frontend-sdk/PasswordState.html index 01e2c053f..0dee2f22d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/PasswordState.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/PasswordState.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Relay.html b/docs/static/jsdoc/hanko-frontend-sdk/Relay.html index 7797c2fc4..0c7a0f8e1 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Relay.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Relay.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/RelayOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/RelayOptions.html index f2ebbf822..20c035807 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/RelayOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/RelayOptions.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/RequestTimeoutError.html b/docs/static/jsdoc/hanko-frontend-sdk/RequestTimeoutError.html index 252baa49c..2db3eb6a3 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/RequestTimeoutError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/RequestTimeoutError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Response.html b/docs/static/jsdoc/hanko-frontend-sdk/Response.html index 2ad5c8404..971caf8fb 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Response.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Response.html @@ -66,7 +66,7 @@ @@ -719,7 +719,7 @@

View Source - lib/client/HttpClient.ts, line 294 + lib/client/HttpClient.ts, line 295

@@ -891,7 +891,7 @@

Parameters:

View Source - lib/client/HttpClient.ts, line 303 + lib/client/HttpClient.ts, line 304

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Scheduler.html b/docs/static/jsdoc/hanko-frontend-sdk/Scheduler.html index 07134183e..3969b8363 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Scheduler.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Scheduler.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Session.html b/docs/static/jsdoc/hanko-frontend-sdk/Session.html index e55c87291..6d44b46d2 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Session.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Session.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/SessionDetail.html b/docs/static/jsdoc/hanko-frontend-sdk/SessionDetail.html index 5e2a3165e..1a0c90b20 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/SessionDetail.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/SessionDetail.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/SessionOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/SessionOptions.html index c841139b5..fae4cb94a 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/SessionOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/SessionOptions.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/SessionState.html b/docs/static/jsdoc/hanko-frontend-sdk/SessionState.html index 4fb81d91b..51dfc09ae 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/SessionState.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/SessionState.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/SessionStateOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/SessionStateOptions.html index 01fcd71ec..6f8bdaaee 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/SessionStateOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/SessionStateOptions.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/State.html b/docs/static/jsdoc/hanko-frontend-sdk/State.html index 09f084ce3..2c813751c 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/State.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/State.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/TechnicalError.html b/docs/static/jsdoc/hanko-frontend-sdk/TechnicalError.html index f7bf83035..5dc6c3a15 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/TechnicalError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/TechnicalError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html b/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html index 1b2465e9b..b95807c48 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyError.html b/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyError.html index 21daf9a36..5b7114d0f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Throttle.html b/docs/static/jsdoc/hanko-frontend-sdk/Throttle.html index 104142dc7..581cdd79b 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Throttle.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Throttle.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/ThrottleOptions.html b/docs/static/jsdoc/hanko-frontend-sdk/ThrottleOptions.html index 41de9598c..d92114346 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/ThrottleOptions.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/ThrottleOptions.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/TokenClient.html b/docs/static/jsdoc/hanko-frontend-sdk/TokenClient.html index 3926763a4..4eab75efb 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/TokenClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/TokenClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/TokenFinalized.html b/docs/static/jsdoc/hanko-frontend-sdk/TokenFinalized.html index f33edb3ee..0023b7cb3 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/TokenFinalized.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/TokenFinalized.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/TooManyRequestsError.html b/docs/static/jsdoc/hanko-frontend-sdk/TooManyRequestsError.html index 39758d764..92d10fe1d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/TooManyRequestsError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/TooManyRequestsError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/UnauthorizedError.html b/docs/static/jsdoc/hanko-frontend-sdk/UnauthorizedError.html index b613be261..837430c5f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/UnauthorizedError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/UnauthorizedError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/User.html b/docs/static/jsdoc/hanko-frontend-sdk/User.html index 427c1244b..f221fec4e 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/User.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/User.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/UserClient.html b/docs/static/jsdoc/hanko-frontend-sdk/UserClient.html index fb3b4ae50..0a7801f16 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/UserClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/UserClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/UserCreated.html b/docs/static/jsdoc/hanko-frontend-sdk/UserCreated.html index 1fbfd6a59..88ca452fa 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/UserCreated.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/UserCreated.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/UserInfo.html b/docs/static/jsdoc/hanko-frontend-sdk/UserInfo.html index 9b5ae7000..043b86de2 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/UserInfo.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/UserInfo.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/UserState.html b/docs/static/jsdoc/hanko-frontend-sdk/UserState.html index 6e5226710..2c20b6a35 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/UserState.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/UserState.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/UserVerificationError.html b/docs/static/jsdoc/hanko-frontend-sdk/UserVerificationError.html index 436d05107..7197e1732 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/UserVerificationError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/UserVerificationError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnClient.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnClient.html index 3e3817243..03458bbe9 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnClient.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html index 0c5595386..bb1ea9994 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html index 30d5062a1..82b5ccbbd 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnFinalized.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnFinalized.html index c1ecba99c..dbe1ac4d7 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnFinalized.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnFinalized.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnRequestCancelledError.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnRequestCancelledError.html index 6047d222c..fabdfca4c 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnRequestCancelledError.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnRequestCancelledError.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnState.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnState.html index 133d8b950..dabf3c9a7 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnState.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnState.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnSupport.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnSupport.html index dc3d490c8..5b69fed72 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnSupport.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnSupport.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnTransports.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnTransports.html index d9eda2ec1..9e42f6bd7 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnTransports.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnTransports.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/index.html b/docs/static/jsdoc/hanko-frontend-sdk/index.html index 48aad6d60..76c55ecc7 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/index.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/index.html @@ -66,7 +66,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_Cookie.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_Cookie.ts.html index 6077e3c5a..64a27cd51 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_Cookie.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_Cookie.ts.html @@ -68,7 +68,7 @@ @@ -85,7 +85,8 @@

lib/Cookie.ts

-
import JSCookie from "js-cookie";
+            
import JSCookie, { CookieAttributes } from "js-cookie";
+import { TechnicalError } from "./Errors";
 
 /**
  * Options for Cookie
@@ -93,23 +94,22 @@ 

lib/Cookie.ts

* @category SDK * @subcategory Internal * @property {string} cookieName - The name of the session cookie set from the SDK. + * @property {string=} cookieDomain - The domain where the cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. + * @property {string=} cookieSameSite -Specify whether/when cookies are sent with cross-site requests. Defaults to "lax". */ interface CookieOptions { cookieName: string; + cookieDomain?: string; + cookieSameSite?: CookieSameSite; } -/** - * Options for setting the auth cookie. - * - * @category SDK - * @subcategory Internal - * @property {boolean} secure - Indicates if the Secure attribute of the cookie should be set. - * @property {number | Date | undefined} expires - The expiration of the cookie. - */ -interface SetAuthCookieOptions { - secure?: boolean; - expires?: number | Date | undefined; -} +export type CookieSameSite = + | "strict" + | "Strict" + | "lax" + | "Lax" + | "none" + | "None"; /** * A class to manage cookies. @@ -120,10 +120,14 @@

lib/Cookie.ts

*/ export class Cookie { authCookieName: string; + authCookieDomain?: string; + authCookieSameSite: CookieSameSite; // eslint-disable-next-line require-jsdoc constructor(options: CookieOptions) { this.authCookieName = options.cookieName; + this.authCookieDomain = options.cookieDomain; + this.authCookieSameSite = options.cookieSameSite ?? "lax"; } /** @@ -139,13 +143,30 @@

lib/Cookie.ts

* Stores the authentication token to the cookie. * * @param {string} token - The authentication token to be stored. - * @param {SetAuthCookieOptions} options - Options for setting the auth cookie. + * @param {CookieAttributes} options - Options for setting the auth cookie. */ - setAuthCookie( - token: string, - options: SetAuthCookieOptions = { secure: true }, - ) { - JSCookie.set(this.authCookieName, token, options); + setAuthCookie(token: string, options?: CookieAttributes) { + const defaults: CookieAttributes = { + secure: true, + sameSite: this.authCookieSameSite, + }; + + if (this.authCookieDomain !== undefined) { + defaults.domain = this.authCookieDomain; + } + + const o: CookieAttributes = { ...defaults, ...options }; + + if ( + (o.sameSite === "none" || o.sameSite === "None") && + o.secure === false + ) { + throw new TechnicalError( + new Error("Secure attribute must be set when SameSite=None"), + ); + } + + JSCookie.set(this.authCookieName, token, o); } /** diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html index 72371bf8b..530758b23 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_Errors.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_Errors.ts.html index b68be7853..3c41c61ef 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_Errors.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_Errors.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_Session.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_Session.ts.html index c5730013f..c29946656 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_Session.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_Session.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_Throttle.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_Throttle.ts.html index e4d1155d9..a93130286 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_Throttle.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_Throttle.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_WebauthnSupport.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_WebauthnSupport.ts.html index 0e2926922..ed305924c 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_WebauthnSupport.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_WebauthnSupport.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_Client.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_Client.ts.html index 46a9c230c..a07251b18 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_Client.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_Client.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ConfigClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ConfigClient.ts.html index bcca834bb..af96b2e07 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ConfigClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ConfigClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EmailClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EmailClient.ts.html index 2992cd719..a8d0a32ba 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EmailClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EmailClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EnterpriseClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EnterpriseClient.ts.html index 1a34d602f..c62556ef6 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EnterpriseClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_EnterpriseClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_HttpClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_HttpClient.ts.html index 0e255456a..5f5b325a3 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_HttpClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_HttpClient.ts.html @@ -68,7 +68,7 @@ @@ -201,11 +201,13 @@

lib/client/HttpClient.ts

* @subcategory Internal * @property {number} timeout - The http request timeout in milliseconds. * @property {string} cookieName - The name of the session cookie set from the SDK. + * @property {string=} cookieDomain - The domain where cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. * @property {string} localStorageKey - The prefix / name of the local storage keys. */ export interface HttpClientOptions { timeout: number; cookieName: string; + cookieDomain?: string; localStorageKey: string; } @@ -302,7 +304,9 @@

lib/client/HttpClient.ts

}); if (jwt) { - const secure = !!this.api.match("^https://"); + const https = new RegExp("^https://"); + const secure = + !!this.api.match(https) && !!window.location.href.match(https); const expires = new Date(new Date().getTime() + expirationSeconds * 1000); this.cookie.setAuthCookie(jwt, { secure, expires }); } diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasscodeClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasscodeClient.ts.html index ea5d349ae..4059a128b 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasscodeClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasscodeClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasswordClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasswordClient.ts.html index ba50f3ddb..00d491d09 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasswordClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_PasswordClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html index d671095bc..fb3a1d09b 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_TokenClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_TokenClient.ts.html index f1d8fc127..5e897f6d5 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_TokenClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_TokenClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_UserClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_UserClient.ts.html index a936848a8..4b9341ea1 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_UserClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_UserClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_WebauthnClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_WebauthnClient.ts.html index b995a1b3d..ac811b41d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_WebauthnClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_WebauthnClient.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_CustomEvents.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_CustomEvents.ts.html index b80436770..2c35399f6 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_CustomEvents.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_CustomEvents.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Dispatcher.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Dispatcher.ts.html index 77020f2cb..9e934bad8 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Dispatcher.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Dispatcher.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Listener.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Listener.ts.html index a753b1053..c7474343c 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Listener.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Listener.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Relay.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Relay.ts.html index 702d53650..346cb8a0f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Relay.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Relay.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Scheduler.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Scheduler.ts.html index c097f574d..1fefacbde 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Scheduler.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_events_Scheduler.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_State.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_State.ts.html index 0230c5a9d..4d4c558d1 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_State.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_State.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_session_SessionState.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_session_SessionState.ts.html index 19da77999..09c8fa889 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_session_SessionState.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_session_SessionState.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasscodeState.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasscodeState.ts.html index 4a1804f2e..71fd0f617 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasscodeState.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasscodeState.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasswordState.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasswordState.ts.html index 7fd1bb1f1..1eec7b93f 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasswordState.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_PasswordState.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_UserState.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_UserState.ts.html index 71e6580eb..58989c440 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_UserState.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_UserState.ts.html @@ -68,7 +68,7 @@ diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_WebauthnState.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_WebauthnState.ts.html index 9eb42f358..f6ec12d7d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_WebauthnState.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_state_users_WebauthnState.ts.html @@ -68,7 +68,7 @@ diff --git a/frontend/elements/README.md b/frontend/elements/README.md index 091704e91..30bf74e94 100644 --- a/frontend/elements/README.md +++ b/frontend/elements/README.md @@ -122,6 +122,9 @@ const defaultOptions = { translationsLocation: "/i18n", // The URL or path where the translation files are located. fallbackLanguage: "en", // The fallback language to be used if a translation is not available. storageKey: "hanko", // The name of the cookie the session token is stored in and the prefix / name of local storage keys + cookieDomain: undefined, // The domain where the cookie set from the SDK is available. When undefined, + // defaults to the domain of the page where the cookie was created. + cookieSameSite: "lax", // Specify whether/when cookies are sent with cross-site requests. }; const { hanko } = await register( diff --git a/frontend/elements/src/Elements.tsx b/frontend/elements/src/Elements.tsx index ce265515d..af9cbb134 100644 --- a/frontend/elements/src/Elements.tsx +++ b/frontend/elements/src/Elements.tsx @@ -4,7 +4,7 @@ import AppProvider, { ComponentName, GlobalOptions, } from "./contexts/AppProvider"; -import { Hanko } from "@teamhanko/hanko-frontend-sdk"; +import { CookieSameSite, Hanko } from "@teamhanko/hanko-frontend-sdk"; import { defaultTranslations, Translations } from "./i18n/translations"; export interface HankoAuthAdditionalProps { @@ -43,6 +43,8 @@ export interface RegisterOptions { translationsLocation?: string; fallbackLanguage?: string; storageKey?: string; + cookieDomain?: string; + cookieSameSite?: CookieSameSite; } export interface RegisterResult { @@ -59,7 +61,7 @@ const globalOptions: GlobalOptions = {}; const createHankoComponent = ( componentName: ComponentName, - props: Record + props: Record, ) => ( => { options = { shadow: true, @@ -107,6 +109,8 @@ export const register = async ( globalOptions.hanko = new Hanko(api, { cookieName: options.storageKey, + cookieDomain: options.cookieDomain, + cookieSameSite: options.cookieSameSite, localStorageKey: options.storageKey, }); globalOptions.injectStyles = options.injectStyles; diff --git a/frontend/frontend-sdk/src/Hanko.ts b/frontend/frontend-sdk/src/Hanko.ts index 1d0e93180..28d0c6fb1 100644 --- a/frontend/frontend-sdk/src/Hanko.ts +++ b/frontend/frontend-sdk/src/Hanko.ts @@ -10,6 +10,7 @@ import { TokenClient } from "./lib/client/TokenClient"; import { Listener } from "./lib/events/Listener"; import { Relay } from "./lib/events/Relay"; import { Session } from "./lib/Session"; +import { CookieSameSite } from "./lib/Cookie"; /** * The options for the Hanko class @@ -17,11 +18,15 @@ import { Session } from "./lib/Session"; * @interface * @property {number=} timeout - The http request timeout in milliseconds. Defaults to 13000ms * @property {string=} cookieName - The name of the session cookie set from the SDK. Defaults to "hanko" + * @property {string=} cookieDomain - The domain where the cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. + * @property {string=} cookieSameSite - Specify whether/when cookies are sent with cross-site requests. Defaults to "lax". * @property {string=} localStorageKey - The prefix / name of the local storage keys. Defaults to "hanko" */ export interface HankoOptions { timeout?: number; cookieName?: string; + cookieDomain?: string; + cookieSameSite?: CookieSameSite; localStorageKey?: string; } @@ -63,6 +68,12 @@ class Hanko extends Listener { if (options?.localStorageKey !== undefined) { opts.localStorageKey = options.localStorageKey; } + if (options?.cookieDomain !== undefined) { + opts.cookieDomain = options.cookieDomain; + } + if (options?.cookieSameSite !== undefined) { + opts.cookieSameSite = options.cookieSameSite; + } this.api = api; /** @@ -127,6 +138,8 @@ class Hanko extends Listener { export interface InternalOptions { timeout: number; cookieName: string; + cookieDomain?: string; + cookieSameSite?: CookieSameSite; localStorageKey: string; } diff --git a/frontend/frontend-sdk/src/index.ts b/frontend/frontend-sdk/src/index.ts index ac6d6a94f..c88d8c94d 100644 --- a/frontend/frontend-sdk/src/index.ts +++ b/frontend/frontend-sdk/src/index.ts @@ -147,3 +147,9 @@ export { userDeletedType, CustomEventWithDetail, }; + +// Misc + +import { CookieSameSite } from "./lib/Cookie"; + +export type { CookieSameSite }; diff --git a/frontend/frontend-sdk/src/lib/Cookie.ts b/frontend/frontend-sdk/src/lib/Cookie.ts index 383d7673c..8b9aa1c96 100644 --- a/frontend/frontend-sdk/src/lib/Cookie.ts +++ b/frontend/frontend-sdk/src/lib/Cookie.ts @@ -1,4 +1,5 @@ -import JSCookie from "js-cookie"; +import JSCookie, { CookieAttributes } from "js-cookie"; +import { TechnicalError } from "./Errors"; /** * Options for Cookie @@ -6,23 +7,22 @@ import JSCookie from "js-cookie"; * @category SDK * @subcategory Internal * @property {string} cookieName - The name of the session cookie set from the SDK. + * @property {string=} cookieDomain - The domain where the cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. + * @property {string=} cookieSameSite -Specify whether/when cookies are sent with cross-site requests. Defaults to "lax". */ interface CookieOptions { cookieName: string; + cookieDomain?: string; + cookieSameSite?: CookieSameSite; } -/** - * Options for setting the auth cookie. - * - * @category SDK - * @subcategory Internal - * @property {boolean} secure - Indicates if the Secure attribute of the cookie should be set. - * @property {number | Date | undefined} expires - The expiration of the cookie. - */ -interface SetAuthCookieOptions { - secure?: boolean; - expires?: number | Date | undefined; -} +export type CookieSameSite = + | "strict" + | "Strict" + | "lax" + | "Lax" + | "none" + | "None"; /** * A class to manage cookies. @@ -33,10 +33,14 @@ interface SetAuthCookieOptions { */ export class Cookie { authCookieName: string; + authCookieDomain?: string; + authCookieSameSite: CookieSameSite; // eslint-disable-next-line require-jsdoc constructor(options: CookieOptions) { this.authCookieName = options.cookieName; + this.authCookieDomain = options.cookieDomain; + this.authCookieSameSite = options.cookieSameSite ?? "lax"; } /** @@ -52,13 +56,30 @@ export class Cookie { * Stores the authentication token to the cookie. * * @param {string} token - The authentication token to be stored. - * @param {SetAuthCookieOptions} options - Options for setting the auth cookie. + * @param {CookieAttributes} options - Options for setting the auth cookie. */ - setAuthCookie( - token: string, - options: SetAuthCookieOptions = { secure: true }, - ) { - JSCookie.set(this.authCookieName, token, options); + setAuthCookie(token: string, options?: CookieAttributes) { + const defaults: CookieAttributes = { + secure: true, + sameSite: this.authCookieSameSite, + }; + + if (this.authCookieDomain !== undefined) { + defaults.domain = this.authCookieDomain; + } + + const o: CookieAttributes = { ...defaults, ...options }; + + if ( + (o.sameSite === "none" || o.sameSite === "None") && + o.secure === false + ) { + throw new TechnicalError( + new Error("Secure attribute must be set when SameSite=None"), + ); + } + + JSCookie.set(this.authCookieName, token, o); } /** diff --git a/frontend/frontend-sdk/src/lib/client/HttpClient.ts b/frontend/frontend-sdk/src/lib/client/HttpClient.ts index 42d21761b..133f867c0 100644 --- a/frontend/frontend-sdk/src/lib/client/HttpClient.ts +++ b/frontend/frontend-sdk/src/lib/client/HttpClient.ts @@ -114,11 +114,13 @@ class Response { * @subcategory Internal * @property {number} timeout - The http request timeout in milliseconds. * @property {string} cookieName - The name of the session cookie set from the SDK. + * @property {string=} cookieDomain - The domain where cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. * @property {string} localStorageKey - The prefix / name of the local storage keys. */ export interface HttpClientOptions { timeout: number; cookieName: string; + cookieDomain?: string; localStorageKey: string; } diff --git a/frontend/frontend-sdk/tests/lib/Cookie.spec.ts b/frontend/frontend-sdk/tests/lib/Cookie.spec.ts index 78a645389..3958ed98e 100644 --- a/frontend/frontend-sdk/tests/lib/Cookie.spec.ts +++ b/frontend/frontend-sdk/tests/lib/Cookie.spec.ts @@ -1,6 +1,7 @@ import JSCookie from "js-cookie"; import { Cookie } from "../../src/lib/Cookie"; import { fakeTimerNow } from "../setup"; +import { Response } from "../../src/lib/client/HttpClient"; describe("Cookie()", () => { let cookie: Cookie; @@ -15,6 +16,8 @@ describe("Cookie()", () => { cookie.setAuthCookie("test-token", { secure: false }); expect(JSCookie.set).toHaveBeenCalledWith("hanko", "test-token", { secure: false, + sameSite: "lax", + domain: undefined, }); }); @@ -24,6 +27,8 @@ describe("Cookie()", () => { expect(JSCookie.set).toHaveBeenCalledWith("hanko", "test-token", { secure: true, + domain: undefined, + sameSite: "lax", }); }); @@ -33,9 +38,37 @@ describe("Cookie()", () => { cookie.setAuthCookie("test-token", { secure: true, expires }); expect(JSCookie.set).toHaveBeenCalledWith("hanko", "test-token", { secure: true, + sameSite: "lax", + domain: undefined, expires, }); }); + + it("should set a new cookie with given SameSite value", async () => { + jest.spyOn(JSCookie, "set"); + cookie.setAuthCookie("test-token", { sameSite: "strict" }); + expect(JSCookie.set).toHaveBeenCalledWith("hanko", "test-token", { + secure: true, + sameSite: "strict", + }); + }); + + it("should throw if not Secure and SameSite value is none", async () => { + jest.spyOn(JSCookie, "set"); + expect(() => { + cookie.setAuthCookie("test-token", { secure: false, sameSite: "none" }); + }).toThrow("Technical error"); + }); + + it("should set a new cookie with given domain value", async () => { + jest.spyOn(JSCookie, "set"); + cookie.setAuthCookie("test-token", { domain: ".test.app" }); + expect(JSCookie.set).toHaveBeenCalledWith("hanko", "test-token", { + secure: true, + sameSite: "lax", + domain: ".test.app", + }); + }); }); describe("cookie.getAuthCookie()", () => { From 92b7d05ad8113fb8b415531aa686a09ecf4ed44e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:02:05 +0100 Subject: [PATCH 10/24] chore(deps): bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-frontend.yml | 2 +- .github/workflows/cli-publish.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/docker-publish.yml | 2 +- .github/workflows/docs-deploy.yml | 2 +- .github/workflows/docs-test-deploy.yml | 2 +- .github/workflows/e2e.yml | 6 +++--- .github/workflows/go.yml | 2 +- .github/workflows/release-frontend-sdk.yml | 4 ++-- .github/workflows/release-hanko-elements.yml | 4 ++-- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-frontend.yml b/.github/workflows/build-frontend.yml index 3bc9cb1f9..f79cac331 100644 --- a/.github/workflows/build-frontend.yml +++ b/.github/workflows/build-frontend.yml @@ -10,7 +10,7 @@ jobs: tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v3 diff --git a/.github/workflows/cli-publish.yml b/.github/workflows/cli-publish.yml index 0f6e8a4ce..e8109f96b 100644 --- a/.github/workflows/cli-publish.yml +++ b/.github/workflows/cli-publish.yml @@ -11,7 +11,7 @@ jobs: goreleaser: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-go@v4 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 81806dac5..a222a3d4c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 2972dc51e..3bbfeec3f 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index dcf658ffc..e27e6c690 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -8,7 +8,7 @@ jobs: name: Deploy to GitHub Pages runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/docs-test-deploy.yml b/.github/workflows/docs-test-deploy.yml index 9870cb43d..80397826c 100644 --- a/.github/workflows/docs-test-deploy.yml +++ b/.github/workflows/docs-test-deploy.yml @@ -10,7 +10,7 @@ jobs: name: Test deployment runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3b2980233..f6efe4bf3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Start containers working-directory: ./deploy/docker-compose @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Start containers working-directory: ./deploy/docker-compose @@ -59,7 +59,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Copy config working-directory: ./deploy/docker-compose diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e62ba6656..5965c37c7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v3 diff --git a/.github/workflows/release-frontend-sdk.yml b/.github/workflows/release-frontend-sdk.yml index 641f9bb9b..04d6d88b3 100644 --- a/.github/workflows/release-frontend-sdk.yml +++ b/.github/workflows/release-frontend-sdk.yml @@ -13,7 +13,7 @@ jobs: check-matching-versions: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: get-npm-version id: package-version uses: martinbeentjes/npm-get-version-action@main @@ -35,7 +35,7 @@ jobs: needs: check-matching-versions runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 16 diff --git a/.github/workflows/release-hanko-elements.yml b/.github/workflows/release-hanko-elements.yml index ce0973b14..d2348d523 100644 --- a/.github/workflows/release-hanko-elements.yml +++ b/.github/workflows/release-hanko-elements.yml @@ -13,7 +13,7 @@ jobs: check-matching-versions: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: get-npm-version id: package-version uses: martinbeentjes/npm-get-version-action@main @@ -35,7 +35,7 @@ jobs: needs: check-matching-versions runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 16 From a2f637349e7500786a818ab5872780c517bc1ecf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:02:28 +0100 Subject: [PATCH 11/24] chore(deps): bump docker/build-push-action from 4 to 5 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 3bbfeec3f..aed1366d1 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -77,7 +77,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: platforms: linux/amd64,linux/arm64 context: ${{ matrix.context }} From eadcfefb64c39806868123a129eb0653817878c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:02:49 +0100 Subject: [PATCH 12/24] chore(deps): bump actions/setup-go from 3 to 5 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cli-publish.yml | 2 +- .github/workflows/go.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cli-publish.yml b/.github/workflows/cli-publish.yml index e8109f96b..1f92426ac 100644 --- a/.github/workflows/cli-publish.yml +++ b/.github/workflows/cli-publish.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: stable - uses: goreleaser/goreleaser-action@v5 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5965c37c7..79b45f80b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: '1.20' From 1764094de0fbdabff86ca479186e545dc067d2ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:03:35 +0100 Subject: [PATCH 13/24] chore(deps): bump github.com/rs/zerolog in /backend Bumps [github.com/rs/zerolog](https://github.com/rs/zerolog) from 1.31.0 to 1.32.0. - [Release notes](https://github.com/rs/zerolog/releases) - [Commits](https://github.com/rs/zerolog/compare/v1.31.0...v1.32.0) --- updated-dependencies: - dependency-name: github.com/rs/zerolog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/go.mod | 2 +- backend/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index dfd59a817..668518167 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -29,7 +29,7 @@ require ( github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/ory/dockertest/v3 v3.10.0 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.31.0 + github.com/rs/zerolog v1.32.0 github.com/russellhaering/gosaml2 v0.9.1 github.com/russellhaering/goxmldsig v1.4.0 github.com/sethvargo/go-limiter v0.7.2 diff --git a/backend/go.sum b/backend/go.sum index c18348dad..a5d24b0c3 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -539,8 +539,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russellhaering/gosaml2 v0.9.1 h1:H/whrl8NuSoxyW46Ww5lKPskm+5K+qYLw9afqJ/Zef0= github.com/russellhaering/gosaml2 v0.9.1/go.mod h1:ja+qgbayxm+0mxBRLMSUuX3COqy+sb0RRhIGun/W2kc= github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= From 9fb0abc88418ec494de3b8918b33239975f7b8b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:05:21 +0100 Subject: [PATCH 14/24] chore(deps): bump github/codeql-action from 2 to 3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a222a3d4c..e51815def 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -69,4 +69,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 84f97ab3cebca7ecf9baa86b2d140cbc23d89251 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:23:34 +0100 Subject: [PATCH 15/24] chore(deps): bump actions/setup-node from 3 to 4 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-frontend.yml | 2 +- .github/workflows/docs-deploy.yml | 2 +- .github/workflows/docs-test-deploy.yml | 2 +- .github/workflows/release-frontend-sdk.yml | 2 +- .github/workflows/release-hanko-elements.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-frontend.yml b/.github/workflows/build-frontend.yml index f79cac331..e6958d548 100644 --- a/.github/workflows/build-frontend.yml +++ b/.github/workflows/build-frontend.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: v18.14.2 diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index e27e6c690..060ed966d 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: npm diff --git a/.github/workflows/docs-test-deploy.yml b/.github/workflows/docs-test-deploy.yml index 80397826c..921125bc8 100644 --- a/.github/workflows/docs-test-deploy.yml +++ b/.github/workflows/docs-test-deploy.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: npm diff --git a/.github/workflows/release-frontend-sdk.yml b/.github/workflows/release-frontend-sdk.yml index 04d6d88b3..de359c528 100644 --- a/.github/workflows/release-frontend-sdk.yml +++ b/.github/workflows/release-frontend-sdk.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 16 registry-url: https://registry.npmjs.org/ diff --git a/.github/workflows/release-hanko-elements.yml b/.github/workflows/release-hanko-elements.yml index d2348d523..8f5e84aec 100644 --- a/.github/workflows/release-hanko-elements.yml +++ b/.github/workflows/release-hanko-elements.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 16 registry-url: https://registry.npmjs.org/ From 159fa6047e187e9d89a666f0cc2ed55c5d771177 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:07:24 +0100 Subject: [PATCH 16/24] chore(deps): bump docker/metadata-action from 4 to 5 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4 to 5. - [Release notes](https://github.com/docker/metadata-action/releases) - [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md) - [Commits](https://github.com/docker/metadata-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index aed1366d1..e38439d78 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -64,7 +64,7 @@ jobs: # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ matrix.image }} tags: | From 652da474d15c2414fdb9003e6b710fe6b3f020bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:08:22 +0100 Subject: [PATCH 17/24] chore(deps): bump docker/setup-qemu-action from 2 to 3 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index e38439d78..21d483c13 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -45,7 +45,7 @@ jobs: uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Setup Docker buildx uses: docker/setup-buildx-action@v2 From c253bb8fb039f132935606f175bcfed4645e116a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 12:37:04 +0100 Subject: [PATCH 18/24] chore(deps): bump docker/setup-buildx-action from 2 to 3 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 21d483c13..30f938bff 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -48,7 +48,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Setup Docker buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # Login against a Docker registry except on PR # https://github.com/docker/login-action From 9020f69ca4d351b87f84ce33d3fb42d476a766d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 12:49:24 +0100 Subject: [PATCH 19/24] chore(deps): bump docker/login-action from 2 to 3 Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 30f938bff..6a0755a43 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -54,7 +54,7 @@ jobs: # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} From 1da5f32df413b20d967fbf2918f35545d6bfea91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 12:50:09 +0100 Subject: [PATCH 20/24] chore(deps): bump actions/github-script from 6 to 7 Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/github-script dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release-frontend-sdk.yml | 2 +- .github/workflows/release-hanko-elements.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-frontend-sdk.yml b/.github/workflows/release-frontend-sdk.yml index de359c528..66665e337 100644 --- a/.github/workflows/release-frontend-sdk.yml +++ b/.github/workflows/release-frontend-sdk.yml @@ -26,7 +26,7 @@ jobs: - run: echo ${{ steps.tag-version.outputs.git_tag_version }} - name: Version correctly set check if: steps.package-version.outputs.current-version != steps.tag-version.outputs.git_tag_version - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | core.setFailed('version in package.json is not equal to git tag version!') diff --git a/.github/workflows/release-hanko-elements.yml b/.github/workflows/release-hanko-elements.yml index 8f5e84aec..a955105d6 100644 --- a/.github/workflows/release-hanko-elements.yml +++ b/.github/workflows/release-hanko-elements.yml @@ -26,7 +26,7 @@ jobs: - run: echo ${{ steps.tag-version.outputs.git_tag_version }} - name: Version correctly set check if: steps.package-version.outputs.current-version != steps.tag-version.outputs.git_tag_version - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | core.setFailed('version in package.json is not equal to git tag version!') From 7d19279241e607789ec441c2e8e85cc252657bd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 20:35:26 +0100 Subject: [PATCH 21/24] chore(deps): bump golang.org/x/crypto from 0.18.0 to 0.19.0 in /backend Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.18.0 to 0.19.0. - [Commits](https://github.com/golang/crypto/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/go.mod | 4 ++-- backend/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 668518167..513b13d18 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -36,7 +36,7 @@ require ( github.com/sethvargo/go-redisstore v0.3.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.18.0 + golang.org/x/crypto v0.19.0 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 golang.org/x/oauth2 v0.16.0 golang.org/x/text v0.14.0 @@ -158,7 +158,7 @@ require ( golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.20.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/backend/go.sum b/backend/go.sum index a5d24b0c3..38aeffc03 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -659,8 +659,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= @@ -764,8 +764,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 5238d1877f438e3f4368a8d0b5095283b6f13b80 Mon Sep 17 00:00:00 2001 From: lfleischmann <67686424+lfleischmann@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:07:32 +0100 Subject: [PATCH 22/24] feat: extend third-party account linking Introduce a per provider configuration option that allows users to define whether automatic linking of accounts on sign-up/sign-in is enabled or disabled. --- backend/README.md | 20 ++- backend/config/config.go | 24 ++- backend/docs/Config.md | 32 +++- backend/dto/email.go | 22 ++- backend/dto/thirdparty.go | 11 ++ .../handler/thirdparty_callback_error_test.go | 3 +- backend/handler/thirdparty_callback_test.go | 165 ++++++++++++++---- backend/handler/thirdparty_test.go | 27 +-- backend/json_schema/hanko.config.json | 6 +- backend/persistence/models/audit_log.go | 1 + backend/persistence/models/email.go | 20 ++- backend/persistence/models/identity.go | 12 +- backend/persistence/models/user.go | 11 +- backend/persistence/user_persister.go | 21 ++- .../fixtures/thirdparty/primary_emails.yaml | 5 + backend/test/user_persister.go | 9 + backend/thirdparty/error.go | 5 + backend/thirdparty/linking.go | 51 ++++-- deploy/docker-compose/config.yaml | 4 +- .../jsdoc/hanko-frontend-sdk/Email.html | 23 +++ .../jsdoc/hanko-frontend-sdk/Emails.html | 2 +- .../jsdoc/hanko-frontend-sdk/Identity.html | 12 +- .../hanko-frontend-sdk/ThirdPartyClient.html | 4 +- .../WebauthnCredential.html | 2 +- .../WebauthnCredentials.html | 2 +- .../jsdoc/hanko-frontend-sdk/lib_Dto.ts.html | 6 +- .../lib_client_ThirdPartyClient.ts.html | 4 +- .../accordion/ListEmailsAccordion.tsx | 15 +- frontend/elements/src/i18n/bn.ts | 3 +- frontend/elements/src/i18n/de.ts | 3 +- frontend/elements/src/i18n/en.ts | 3 +- frontend/elements/src/i18n/fr.ts | 3 +- frontend/elements/src/i18n/it.ts | 3 +- frontend/elements/src/i18n/pt-BR.ts | 3 +- frontend/elements/src/i18n/translations.ts | 2 +- frontend/elements/src/i18n/zh.ts | 3 +- frontend/frontend-sdk/src/lib/Dto.ts | 6 +- .../src/lib/client/ThirdPartyClient.ts | 4 +- 38 files changed, 433 insertions(+), 119 deletions(-) diff --git a/backend/README.md b/backend/README.md index 792115357..280c022cb 100644 --- a/backend/README.md +++ b/backend/README.md @@ -434,8 +434,24 @@ Hanko service behind a proxy or gateway (e.g. Kong, Traefik) to provide addition ### Social Logins Hanko supports OAuth-based ([authorization code flow](https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1)) third -party provider logins. Please view the official [docs](https://docs.hanko.io/guides/social) for a list of supported -providers and guides. +party provider logins. See the `third_party` option in the [configuration reference](./docs/Config.md) on how to +configure them. All provider configurations require provider credentials. See the guides in the official +documentation for instructions on how to obtain these: + +- [Apple](https://docs.hanko.io/guides/authentication-methods/oauth/apple) +- [GitHub](https://docs.hanko.io/guides/authentication-methods/oauth/github) +- [Google](https://docs.hanko.io/guides/authentication-methods/oauth/google) + +#### Account linking + +The `allow_linking` configuration option for providers determines whether automatic account linking for this provider +is activated. Note that account linking is based on e-mail addresses and OAuth providers may allow account holders to +use unverified e-mail addresses or may not provide any information at all about the verification status of e-mail +addresses. This poses a security risk and potentially allows bad actors to hijack existing Hanko +accounts associated with the same address. It is therefore recommended to make sure you trust the provider and to +also enable `emails.require_verification` in your configuration to ensure that only verified third party provider +addresses may be used. + ### User import You can import an existing user pool into Hanko using json in the following format: diff --git a/backend/config/config.go b/backend/config/config.go index 62f6d7a4b..65f45f483 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -9,9 +9,9 @@ import ( "github.com/knadh/koanf" "github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/providers/file" + zeroLogger "github.com/rs/zerolog/log" "github.com/teamhanko/hanko/backend/ee/saml/config" "golang.org/x/exp/slices" - zeroLogger "github.com/rs/zerolog/log" "log" "strings" "time" @@ -170,6 +170,19 @@ func DefaultConfig() *Config { AllowDeletion: false, AllowSignup: true, }, + ThirdParty: ThirdParty{ + Providers: ThirdPartyProviders{ + Google: ThirdPartyProvider{ + AllowLinking: true, + }, + GitHub: ThirdPartyProvider{ + AllowLinking: true, + }, + Apple: ThirdPartyProvider{ + AllowLinking: true, + }, + }, + }, } } @@ -585,9 +598,10 @@ func (t *ThirdParty) PostProcess() error { } type ThirdPartyProvider struct { - Enabled bool `yaml:"enabled" json:"enabled" koanf:"enabled"` - ClientID string `yaml:"client_id" json:"client_id" koanf:"client_id" split_words:"true"` - Secret string `yaml:"secret" json:"secret" koanf:"secret"` + Enabled bool `yaml:"enabled" json:"enabled" koanf:"enabled"` + ClientID string `yaml:"client_id" json:"client_id" koanf:"client_id" split_words:"true"` + Secret string `yaml:"secret" json:"secret" koanf:"secret"` + AllowLinking bool `yaml:"allow_linking" json:"allow_linking" koanf:"allow_linking" split_words:"true"` } func (p *ThirdPartyProvider) Validate() error { @@ -665,7 +679,7 @@ func (c *Config) arrangeSmtpSettings() { zeroLogger.Warn().Msg("Both root smtp and passcode.smtp are set. Using smtp settings from root configuration") return } - + c.Smtp = c.Passcode.Smtp } } diff --git a/backend/docs/Config.md b/backend/docs/Config.md index 2de0f9953..fe6010cd7 100644 --- a/backend/docs/Config.md +++ b/backend/docs/Config.md @@ -439,7 +439,7 @@ third_party: # Required if any providers are enabled. # List of URLS the backend is allowed to redirect to after third party sign-in was successful. # (see also the 'redirect_to' parameter for the third party auth initialization endpoint - # - https://docs.hanko.io/api/public#tag/Third-Party/operation/thirdPartyAuth) + # - https://docs.hanko.io/api-reference/public/third-party/initialize-third-party-login) # # Supports wildcard matching through globbing. e.g. https://*.example.com will allow https://foo.example.com and https://bar.example.com to be accepted. # Globbing is also supported for paths, e.g. https://foo.example.com/* will match https://foo.example.com/page1 and https://foo.example.com/page2. @@ -506,6 +506,16 @@ third_party: # Required if provider is enabled. # secret: "CHANGE_ME" + ## + # + # Indicates whether accounts can be linked with this provider. + # This option only controls linking for existing accounts. Account registrations + # are not affected (see the 'accounts.allow_signup' option for controlling + # account registration). + # + # Default: true + # + allow_linking: true ## # # The Google provider configuration @@ -534,6 +544,16 @@ third_party: # Required if provider is enabled. # secret: "CHANGE_ME" + ## + # + # Indicates whether accounts can be linked with this provider. + # This option only controls linking for existing accounts. Account registrations + # are not affected (see the 'accounts.allow_signup' option for controlling + # account registration). + # + # Default: true + # + allow_linking: true ## # # The GitHub provider configuration @@ -562,6 +582,16 @@ third_party: # Required if provider is enabled. # secret: "CHANGE_ME" + ## + # + # Indicates whether accounts can be linked with this provider. + # This option only controls linking for existing accounts. Account registrations + # are not affected (see the 'accounts.allow_signup' option for controlling + # account registration). + # + # Default: true + # + allow_linking: true log: ## log_health_and_metrics # diff --git a/backend/dto/email.go b/backend/dto/email.go index 1f3f5b6a0..0522fef2d 100644 --- a/backend/dto/email.go +++ b/backend/dto/email.go @@ -6,11 +6,12 @@ import ( ) type EmailResponse struct { - ID uuid.UUID `json:"id"` - Address string `json:"address"` - IsVerified bool `json:"is_verified"` - IsPrimary bool `json:"is_primary"` - Identity *Identity `json:"identity"` + ID uuid.UUID `json:"id"` + Address string `json:"address"` + IsVerified bool `json:"is_verified"` + IsPrimary bool `json:"is_primary"` + Identity *Identity `json:"identity,omitempty"` // Deprecated + Identities Identities `json:"identities,omitempty"` } type EmailCreateRequest struct { @@ -23,11 +24,18 @@ type EmailUpdateRequest struct { // FromEmailModel Converts the DB model to a DTO object func FromEmailModel(email *models.Email) *EmailResponse { - return &EmailResponse{ + emailResponse := &EmailResponse{ ID: email.ID, Address: email.Address, IsVerified: email.Verified, IsPrimary: email.IsPrimary(), - Identity: FromIdentityModel(email.Identity), + Identities: FromIdentitiesModel(email.Identities), } + + if len(email.Identities) > 0 { + identity := FromIdentityModel(&email.Identities[0]) + emailResponse.Identity = identity + } + + return emailResponse } diff --git a/backend/dto/thirdparty.go b/backend/dto/thirdparty.go index 1341a2228..aab8c16ff 100644 --- a/backend/dto/thirdparty.go +++ b/backend/dto/thirdparty.go @@ -28,6 +28,17 @@ type Identity struct { Provider string `json:"provider"` } +type Identities []Identity + +func FromIdentitiesModel(identities models.Identities) Identities { + var result Identities + for _, i := range identities { + identity := FromIdentityModel(&i) + result = append(result, *identity) + } + return result +} + func FromIdentityModel(identity *models.Identity) *Identity { if identity == nil { return nil diff --git a/backend/handler/thirdparty_callback_error_test.go b/backend/handler/thirdparty_callback_error_test.go index 1ae293dd4..db24a2e02 100644 --- a/backend/handler/thirdparty_callback_error_test.go +++ b/backend/handler/thirdparty_callback_error_test.go @@ -10,7 +10,7 @@ import ( "testing" ) -func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Error_SignUpUserConflict() { +func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Error_LinkingNotAllowedForProvider() { defer gock.Off() if testing.Short() { s.T().Skip("skipping test in short mode.") @@ -34,6 +34,7 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Error_SignUpUserConflic }) cfg := s.setUpConfig([]string{"google"}, []string{"https://example.com"}) + cfg.ThirdParty.Providers.Google.AllowLinking = false state, err := thirdparty.GenerateState(cfg, "google", "https://example.com") s.NoError(err) diff --git a/backend/handler/thirdparty_callback_test.go b/backend/handler/thirdparty_callback_test.go index 64c1d4559..4c1d9a625 100644 --- a/backend/handler/thirdparty_callback_test.go +++ b/backend/handler/thirdparty_callback_test.go @@ -59,10 +59,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignUp_Google() { s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("google", "google_abcde") s.NotNil(identity) - s.Equal("google", identity.ProviderName) - s.Equal("google_abcde", identity.ProviderID) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signup_succeeded"}, user.ID.String(), email.Address, "", "") s.NoError(lerr) @@ -122,10 +120,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_Google() { s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("google", "google_abcde") s.NotNil(identity) - s.Equal("google", identity.ProviderName) - s.Equal("google_abcde", identity.ProviderID) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), "", "", "") s.NoError(lerr) @@ -192,10 +188,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignUp_GitHub() { s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("github", "1234") s.NotNil(identity) - s.Equal("github", identity.ProviderName) - s.Equal("1234", identity.ProviderID) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signup_succeeded"}, user.ID.String(), email.Address, "", "") s.NoError(lerr) @@ -265,10 +259,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_GitHub() { s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("github", "1234") s.NotNil(identity) - s.Equal("github", identity.ProviderName) - s.Equal("1234", identity.ProviderID) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), email.Address, "", "") s.NoError(lerr) @@ -323,10 +315,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignUp_Apple() { s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("apple", "apple_abcde") s.NotNil(identity) - s.Equal("apple", identity.ProviderName) - s.Equal("apple_abcde", identity.ProviderID) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signup_succeeded"}, user.ID.String(), email.Address, "", "") s.NoError(lerr) @@ -384,10 +374,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_Apple() { s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("apple", "apple_abcde") s.NotNil(identity) - s.Equal("apple", identity.ProviderName) - s.Equal("apple_abcde", identity.ProviderID) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), email.Address, "", "") s.NoError(lerr) @@ -447,10 +435,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignUp_WithUnclaimedEma s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("google", "google_unclaimed_email") s.NotNil(identity) - s.Equal("google", identity.ProviderName) - s.Equal("google_unclaimed_email", identity.ProviderID) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signup_succeeded"}, user.ID.String(), email.Address, "", "") s.NoError(lerr) @@ -509,10 +495,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_ProviderEMailCha s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("google", "google_abcde") s.NotNil(identity) - s.Equal("google", identity.ProviderName) - s.Equal("google_abcde", identity.ProviderID) s.Equal("test-with-google-identity-changed@example.com", identity.Data["email"]) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), user.Emails.GetPrimary().Address, "", "") @@ -572,10 +556,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_ProviderEMailCha s.NoError(err) s.NotNil(user) - identity := email.Identity + identity := email.Identities.GetIdentity("google", "google_abcde") s.NotNil(identity) - s.Equal("google", identity.ProviderName) - s.Equal("google_abcde", identity.ProviderID) s.Equal("unclaimed-email@example.com", identity.Data["email"]) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), user.Emails.GetPrimary().Address, "", "") @@ -636,10 +618,8 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_ProviderEMailCha s.NotNil(user) s.Len(user.Emails, 3) - identity := email.Identity + identity := email.Identities.GetIdentity("google", "google_abcde") s.NotNil(identity) - s.Equal("google", identity.ProviderName) - s.Equal("google_abcde", identity.ProviderID) s.Equal("non-existent-email@example.com", identity.Data["email"]) logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_signin_succeeded"}, user.ID.String(), user.Emails.GetPrimary().Address, "", "") @@ -647,3 +627,128 @@ func (s *thirdPartySuite) TestThirdPartyHandler_Callback_SignIn_ProviderEMailCha s.Len(logs, 1) } } + +func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Link_ExistingAccountNoIdentities() { + defer gock.Off() + if testing.Short() { + s.T().Skip("skipping test in short mode.") + } + + err := s.LoadFixtures("../test/fixtures/thirdparty") + s.NoError(err) + + gock.New(thirdparty.GoogleOauthTokenEndpoint). + Post("/"). + Reply(200). + JSON(map[string]string{"access_token": "fakeAccessToken"}) + + gock.New(thirdparty.GoogleUserInfoEndpoint). + Get("/"). + Reply(200). + JSON(&thirdparty.GoogleUser{ + ID: "google_1234", + Email: "test-no-identity@example.com", + EmailVerified: true, + }) + + cfg := s.setUpConfig([]string{"google"}, []string{"https://example.com"}) + + state, err := thirdparty.GenerateState(cfg, "google", "https://example.com") + s.NoError(err) + + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/thirdparty/callback?code=abcde&state=%s", state), nil) + req.AddCookie(&http.Cookie{ + Name: utils.HankoThirdpartyStateCookie, + Value: string(state), + }) + + c, rec := s.setUpContext(req) + handler := s.setUpHandler(cfg) + + if s.NoError(handler.Callback(c)) { + s.Equal(http.StatusTemporaryRedirect, rec.Code) + + s.assertLocationHeaderHasToken(rec) + s.assertStateCookieRemoved(rec) + + email, err := s.Storage.GetEmailPersister().FindByAddress("test-no-identity@example.com") + s.NoError(err) + s.NotNil(email) + + user, err := s.Storage.GetUserPersister().Get(*email.UserID) + s.NoError(err) + s.NotNil(user) + s.Len(user.Emails, 1) + + identity := email.Identities.GetIdentity("google", "google_1234") + s.NotNil(identity) + s.Equal("test-no-identity@example.com", identity.Data["email"]) + + logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_linking_succeeded"}, user.ID.String(), user.Emails.GetPrimary().Address, "", "") + s.NoError(lerr) + s.Len(logs, 1) + } +} + +func (s *thirdPartySuite) TestThirdPartyHandler_Callback_Link_GoogleToAccountWithGithubIdentity() { + defer gock.Off() + if testing.Short() { + s.T().Skip("skipping test in short mode.") + } + + err := s.LoadFixtures("../test/fixtures/thirdparty") + s.NoError(err) + + gock.New(thirdparty.GoogleOauthTokenEndpoint). + Post("/"). + Reply(200). + JSON(map[string]string{"access_token": "fakeAccessToken"}) + + gock.New(thirdparty.GoogleUserInfoEndpoint). + Get("/"). + Reply(200). + JSON(&thirdparty.GoogleUser{ + ID: "google_1234", + Email: "test-with-github-identity@example.com", + EmailVerified: true, + }) + + cfg := s.setUpConfig([]string{"google", "github"}, []string{"https://example.com"}) + + state, err := thirdparty.GenerateState(cfg, "google", "https://example.com") + s.NoError(err) + + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/thirdparty/callback?code=abcde&state=%s", state), nil) + req.AddCookie(&http.Cookie{ + Name: utils.HankoThirdpartyStateCookie, + Value: string(state), + }) + + c, rec := s.setUpContext(req) + handler := s.setUpHandler(cfg) + + if s.NoError(handler.Callback(c)) { + s.Equal(http.StatusTemporaryRedirect, rec.Code) + + s.assertLocationHeaderHasToken(rec) + s.assertStateCookieRemoved(rec) + + email, err := s.Storage.GetEmailPersister().FindByAddress("test-with-github-identity@example.com") + s.NoError(err) + s.NotNil(email) + s.Len(email.Identities, 2) + + user, err := s.Storage.GetUserPersister().Get(*email.UserID) + s.NoError(err) + s.NotNil(user) + s.Len(user.Emails, 1) + + identity := email.Identities.GetIdentity("google", "google_1234") + s.NotNil(identity) + s.Equal("test-with-github-identity@example.com", identity.Data["email"]) + + logs, lerr := s.Storage.GetAuditLogPersister().List(0, 0, nil, nil, []string{"thirdparty_linking_succeeded"}, user.ID.String(), user.Emails.GetPrimary().Address, "", "") + s.NoError(lerr) + s.Len(logs, 1) + } +} diff --git a/backend/handler/thirdparty_test.go b/backend/handler/thirdparty_test.go index 09c9977f0..7edde67ff 100644 --- a/backend/handler/thirdparty_test.go +++ b/backend/handler/thirdparty_test.go @@ -56,18 +56,22 @@ func (s *thirdPartySuite) setUpConfig(enabledProviders []string, allowedRedirect ThirdParty: config.ThirdParty{ Providers: config.ThirdPartyProviders{ Apple: config.ThirdPartyProvider{ - Enabled: false, - ClientID: "fakeClientID", - Secret: "fakeClientSecret", + Enabled: false, + ClientID: "fakeClientID", + Secret: "fakeClientSecret", + AllowLinking: true, }, Google: config.ThirdPartyProvider{ - Enabled: false, - ClientID: "fakeClientID", - Secret: "fakeClientSecret", - }, GitHub: config.ThirdPartyProvider{ - Enabled: false, - ClientID: "fakeClientID", - Secret: "fakeClientSecret", + Enabled: false, + ClientID: "fakeClientID", + Secret: "fakeClientSecret", + AllowLinking: true, + }, + GitHub: config.ThirdPartyProvider{ + Enabled: false, + ClientID: "fakeClientID", + Secret: "fakeClientSecret", + AllowLinking: true, }}, ErrorRedirectURL: "https://error.test.example", RedirectURL: "https://api.test.example/callback", @@ -82,6 +86,9 @@ func (s *thirdPartySuite) setUpConfig(enabledProviders []string, allowedRedirect Emails: config.Emails{ MaxNumOfAddresses: 5, }, + Account: config.Account{ + AllowSignup: true, + }, } for _, provider := range enabledProviders { diff --git a/backend/json_schema/hanko.config.json b/backend/json_schema/hanko.config.json index b31056aea..7b7ddf665 100644 --- a/backend/json_schema/hanko.config.json +++ b/backend/json_schema/hanko.config.json @@ -676,6 +676,9 @@ }, "secret": { "type": "string" + }, + "allow_linking": { + "type": "boolean" } }, "additionalProperties": false, @@ -683,7 +686,8 @@ "required": [ "enabled", "client_id", - "secret" + "secret", + "allow_linking" ] }, "ThirdPartyProviders": { diff --git a/backend/persistence/models/audit_log.go b/backend/persistence/models/audit_log.go index e57120897..a8a6d04de 100644 --- a/backend/persistence/models/audit_log.go +++ b/backend/persistence/models/audit_log.go @@ -56,6 +56,7 @@ var ( AuditLogThirdPartySignUpSucceeded AuditLogType = "thirdparty_signup_succeeded" AuditLogThirdPartySignInSucceeded AuditLogType = "thirdparty_signin_succeeded" + AuditLogThirdPartyLinkingSucceeded AuditLogType = "thirdparty_linking_succeeded" AuditLogThirdPartySignInSignUpFailed AuditLogType = "thirdparty_signin_signup_failed" AuditLogTokenExchangeSucceeded AuditLogType = "token_exchange_succeeded" diff --git a/backend/persistence/models/email.go b/backend/persistence/models/email.go index 697d81648..5a421a59e 100644 --- a/backend/persistence/models/email.go +++ b/backend/persistence/models/email.go @@ -16,7 +16,7 @@ type Email struct { Verified bool `db:"verified" json:"verified"` PrimaryEmail *PrimaryEmail `has_one:"primary_emails" json:"primary_emails,omitempty"` User *User `belongs_to:"user" json:"user,omitempty"` - Identity *Identity `has_one:"identities" json:"identity,omitempty"` + Identities Identities `has_many:"identities" json:"identity,omitempty"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } @@ -73,6 +73,24 @@ func (emails Emails) SetPrimary(primary *PrimaryEmail) { return } +func (emails Emails) GetEmailByAddress(address string) *Email { + for _, email := range emails { + if email.Address == address { + return &email + } + } + return nil +} + +func (emails Emails) GetEmailById(emailId uuid.UUID) *Email { + for _, email := range emails { + if email.ID.String() == emailId.String() { + return &email + } + } + return nil +} + // Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method. func (email *Email) Validate(tx *pop.Connection) (*validate.Errors, error) { return validate.Validate( diff --git a/backend/persistence/models/identity.go b/backend/persistence/models/identity.go index 3632e1045..e12777753 100644 --- a/backend/persistence/models/identity.go +++ b/backend/persistence/models/identity.go @@ -17,13 +17,23 @@ type Identity struct { ProviderName string `json:"provider_name" db:"provider_name"` Data slices.Map `json:"data" db:"data"` EmailID uuid.UUID `json:"email_id" db:"email_id"` - Email *Email `json:"email" belongs_to:"email"` + Email *Email `json:"email,omitempty" belongs_to:"email"` CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } type Identities []Identity +func (identities Identities) GetIdentity(providerName string, providerId string) *Identity { + for _, identity := range identities { + if identity.ProviderName == providerName && identity.ProviderID == providerId { + return &identity + } + } + + return nil +} + func NewIdentity(provider string, identityData map[string]interface{}, emailID uuid.UUID) (*Identity, error) { providerID, ok := identityData["sub"] if !ok { diff --git a/backend/persistence/models/user.go b/backend/persistence/models/user.go index 01c07454a..629d0bb97 100644 --- a/backend/persistence/models/user.go +++ b/backend/persistence/models/user.go @@ -27,12 +27,11 @@ func NewUser() User { } func (user *User) GetEmailById(emailId uuid.UUID) *Email { - for _, email := range user.Emails { - if email.ID.String() == emailId.String() { - return &email - } - } - return nil + return user.Emails.GetEmailById(emailId) +} + +func (user *User) GetEmailByAddress(address string) *Email { + return user.Emails.GetEmailByAddress(address) } // Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method. diff --git a/backend/persistence/user_persister.go b/backend/persistence/user_persister.go index 07b3def40..99f0a6c97 100644 --- a/backend/persistence/user_persister.go +++ b/backend/persistence/user_persister.go @@ -12,6 +12,7 @@ import ( type UserPersister interface { Get(uuid.UUID) (*models.User, error) + GetByEmailAddress(string) (*models.User, error) Create(models.User) error Update(models.User) error Delete(models.User) error @@ -30,7 +31,7 @@ func NewUserPersister(db *pop.Connection) UserPersister { func (p *userPersister) Get(id uuid.UUID) (*models.User, error) { user := models.User{} - err := p.db.EagerPreload("Emails", "Emails.PrimaryEmail", "Emails.Identity", "WebauthnCredentials").Find(&user, id) + err := p.db.EagerPreload("Emails", "Emails.PrimaryEmail", "Emails.Identities", "WebauthnCredentials").Find(&user, id) if err != nil && errors.Is(err, sql.ErrNoRows) { return nil, nil } @@ -41,17 +42,23 @@ func (p *userPersister) Get(id uuid.UUID) (*models.User, error) { return &user, nil } -func (p *userPersister) GetByEmail(email string) (*models.User, error) { - user := models.User{} - err := p.db.Eager().Where("email = (?)", email).First(&user) +func (p *userPersister) GetByEmailAddress(emailAddress string) (*models.User, error) { + email := models.Email{} + err := p.db.Where("address = (?)", emailAddress).First(&email) + if err != nil && errors.Is(err, sql.ErrNoRows) { return nil, nil } + if err != nil { - return nil, fmt.Errorf("failed to get user: %w", err) + return nil, fmt.Errorf("failed to get user by email address: %w", err) } - return &user, nil + if email.UserID == nil { + return nil, nil + } + + return p.Get(*email.UserID) } func (p *userPersister) Create(user models.User) error { @@ -115,7 +122,7 @@ func (p *userPersister) List(page int, perPage int, userId uuid.UUID, email stri func (p *userPersister) All() ([]models.User, error) { users := []models.User{} - err := p.db.EagerPreload("Emails", "Emails.PrimaryEmail", "Emails.Identity", "WebauthnCredentials").All(&users) + err := p.db.EagerPreload("Emails", "Emails.PrimaryEmail", "Emails.Identities", "WebauthnCredentials").All(&users) if err != nil && errors.Is(err, sql.ErrNoRows) { return users, nil } diff --git a/backend/test/fixtures/thirdparty/primary_emails.yaml b/backend/test/fixtures/thirdparty/primary_emails.yaml index 1e89ab892..ce0d3bbec 100644 --- a/backend/test/fixtures/thirdparty/primary_emails.yaml +++ b/backend/test/fixtures/thirdparty/primary_emails.yaml @@ -13,3 +13,8 @@ user_id: b3537e49-de92-4e16-8981-ae4beb44c447 created_at: 2020-12-31 23:59:59 updated_at: 2020-12-31 23:59:59 +- id: c57715eb-0c63-4910-b429-9b6165c50fab + email_id: 527afce8-3b7b-41b6-b1ed-33d408c5a7bb + user_id: 43fb7e88-4d5d-4b2b-9335-391e78d7e472 + created_at: 2020-12-31 23:59:59 + updated_at: 2020-12-31 23:59:59 diff --git a/backend/test/user_persister.go b/backend/test/user_persister.go index 6f1045580..ab4cf47a8 100644 --- a/backend/test/user_persister.go +++ b/backend/test/user_persister.go @@ -88,3 +88,12 @@ func (p *userPersister) All() ([]models.User, error) { func (p *userPersister) Count(userId uuid.UUID, email string) (int, error) { return len(p.users), nil } + +func (p *userPersister) GetByEmailAddress(s string) (*models.User, error) { + for _, user := range p.users { + if email := user.Emails.GetEmailByAddress(s); email != nil { + return &user, nil + } + } + return nil, nil +} diff --git a/backend/thirdparty/error.go b/backend/thirdparty/error.go index ec39e6901..c1dc9d98a 100644 --- a/backend/thirdparty/error.go +++ b/backend/thirdparty/error.go @@ -74,6 +74,10 @@ func ErrorMaxNumberOfAddresses(desc string) *ThirdPartyError { return &ThirdPartyError{Code: ErrorCodeMaxNumberOfAddresses, Description: desc} } +func ErrorSignUpDisabled(desc string) *ThirdPartyError { + return &ThirdPartyError{Code: ErrorCodeSignUpDisabled, Description: desc} +} + const ( ErrorCodeInvalidRequest = "invalid_request" ErrorCodeServerError = "server_error" @@ -81,4 +85,5 @@ const ( ErrorCodeMultipleAccounts = "multiple_accounts" ErrorCodeUnverifiedProviderEmail = "unverified_email" ErrorCodeMaxNumberOfAddresses = "email_maxnum" + ErrorCodeSignUpDisabled = "signup_disabled" ) diff --git a/backend/thirdparty/linking.go b/backend/thirdparty/linking.go index 4de620456..64ebacd7a 100644 --- a/backend/thirdparty/linking.go +++ b/backend/thirdparty/linking.go @@ -14,22 +14,53 @@ type AccountLinkingResult struct { } func LinkAccount(tx *pop.Connection, cfg *config.Config, p persistence.Persister, userData *UserData, providerName string) (*AccountLinkingResult, error) { + if cfg.Emails.RequireVerification && !userData.Metadata.EmailVerified { + return nil, ErrorUnverifiedProviderEmail("third party provider email must be verified") + } + identity, err := p.GetIdentityPersister().Get(userData.Metadata.Subject, providerName) if err != nil { return nil, ErrorServer("could not get identity").WithCause(err) } - if cfg.Emails.RequireVerification && !userData.Metadata.EmailVerified { - return nil, ErrorUnverifiedProviderEmail("third party provider email must be verified") - } - if identity == nil { - return signUp(tx, p, userData, providerName) + user, err := p.GetUserPersisterWithConnection(tx).GetByEmailAddress(userData.Metadata.Email) + if err != nil { + return nil, ErrorServer("could not get identity").WithCause(err) + } + + if user == nil { + return signUp(tx, cfg, p, userData, providerName) + } else { + return link(tx, cfg, p, userData, providerName, user) + } } else { return signIn(tx, cfg, p, userData, identity) } } +func link(tx *pop.Connection, cfg *config.Config, p persistence.Persister, userData *UserData, providerName string, user *models.User) (*AccountLinkingResult, error) { + if !cfg.ThirdParty.Providers.Get(providerName).AllowLinking { + return nil, ErrorUserConflict("third party account linking for existing user with same email disallowed") + } + + email := user.GetEmailByAddress(userData.Metadata.Email) + identity, err := models.NewIdentity(providerName, userData.ToMap(), email.ID) + if err != nil { + return nil, ErrorServer("could not create identity").WithCause(err) + } + + err = p.GetIdentityPersisterWithConnection(tx).Create(*identity) + if err != nil { + return nil, ErrorServer("could not create identity").WithCause(err) + } + + return &AccountLinkingResult{ + Type: models.AuditLogThirdPartyLinkingSucceeded, + User: user, + }, nil +} + func signIn(tx *pop.Connection, cfg *config.Config, p persistence.Persister, userData *UserData, identity *models.Identity) (*AccountLinkingResult, error) { var linkingResult *AccountLinkingResult @@ -110,7 +141,11 @@ func signIn(tx *pop.Connection, cfg *config.Config, p persistence.Persister, use return linkingResult, nil } -func signUp(tx *pop.Connection, p persistence.Persister, userData *UserData, providerName string) (*AccountLinkingResult, error) { +func signUp(tx *pop.Connection, cfg *config.Config, p persistence.Persister, userData *UserData, providerName string) (*AccountLinkingResult, error) { + if !cfg.Account.AllowSignup { + return nil, ErrorSignUpDisabled("account signup is disabled") + } + var linkingResult *AccountLinkingResult userPersister := p.GetUserPersisterWithConnection(tx) @@ -123,10 +158,6 @@ func signUp(tx *pop.Connection, p persistence.Persister, userData *UserData, pro return nil, ErrorServer("could not get email").WithCause(terr) } - if email != nil && email.UserID != nil { - return nil, ErrorUserConflict("third party account linking for existing user with same email disallowed") - } - user := models.NewUser() terr = userPersister.Create(user) if terr != nil { diff --git a/deploy/docker-compose/config.yaml b/deploy/docker-compose/config.yaml index 2890914ba..4515d139c 100644 --- a/deploy/docker-compose/config.yaml +++ b/deploy/docker-compose/config.yaml @@ -5,8 +5,8 @@ database: port: 5432 dialect: postgres smtp: - host: "mailslurper" - port: "2500" + host: "mailslurper" + port: "2500" passcode: email: from_address: no-reply@hanko.io diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Email.html b/docs/static/jsdoc/hanko-frontend-sdk/Email.html index 65e67b318..a3987c45e 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Email.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Email.html @@ -236,6 +236,29 @@
Properties:
+ + + + identities + + + + + +Array.<Identity> + + + + + + + + + + A list of identities, each identity indicates that this email is linked to a third party account. + + + diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Emails.html b/docs/static/jsdoc/hanko-frontend-sdk/Emails.html index 685557d7f..3169e6d23 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Emails.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Emails.html @@ -181,7 +181,7 @@
Properties:

View Source - lib/Dto.ts, line 131 + lib/Dto.ts, line 132

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/Identity.html b/docs/static/jsdoc/hanko-frontend-sdk/Identity.html index 787e14572..19efd56fc 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/Identity.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/Identity.html @@ -106,6 +106,8 @@
Properties:
+ Name + Type @@ -122,11 +124,13 @@
Properties:
+ id + -id +string @@ -143,11 +147,13 @@
Properties:
+ provider + -provider +string @@ -202,7 +208,7 @@
Properties:

View Source - lib/Dto.ts, line 159 + lib/Dto.ts, line 160

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html b/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html index b95807c48..22723e284 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/ThirdPartyClient.html @@ -474,7 +474,7 @@
Parameters:

View Source - lib/client/ThirdPartyClient.ts, line 85 + lib/client/ThirdPartyClient.ts, line 87

@@ -616,7 +616,7 @@

View Source - lib/client/ThirdPartyClient.ts, line 91 + lib/client/ThirdPartyClient.ts, line 93

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html index bb1ea9994..19c94561d 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredential.html @@ -398,7 +398,7 @@

Properties:

View Source - lib/Dto.ts, line 138 + lib/Dto.ts, line 139

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html index 82b5ccbbd..e366a3280 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/WebauthnCredentials.html @@ -181,7 +181,7 @@
Properties:

View Source - lib/Dto.ts, line 152 + lib/Dto.ts, line 153

diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html index 530758b23..5850581cf 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_Dto.ts.html @@ -266,6 +266,7 @@

lib/Dto.ts

* @property {boolean} is_verified - Indicates whether the email address is verified. * @property {boolean} is_primary - Indicates it's the primary email address. * @property {Identity} identity - Indicates that this email is linked to a third party account. + * @property {Identity[]} identities - A list of identities, each identity indicates that this email is linked to a third party account. */ export interface Email { id: string; @@ -273,6 +274,7 @@

lib/Dto.ts

is_verified: boolean; is_primary: boolean; identity: Identity; + identities: Identity[]; } /** @@ -319,8 +321,8 @@

lib/Dto.ts

* @interface * @category SDK * @subcategory DTO - * @property {id} - The subject ID with the third party provider. - * @property {provider} - The third party provider name. + * @property {string} id - The subject ID with the third party provider. + * @property {string} provider - The third party provider name. */ export interface Identity { id: string; diff --git a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html index fb3a1d09b..5002256a1 100644 --- a/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html +++ b/docs/static/jsdoc/hanko-frontend-sdk/lib_client_ThirdPartyClient.ts.html @@ -87,7 +87,6 @@

lib/client/ThirdPartyClient.ts

import { Client } from "./Client";
 import { ThirdPartyError } from "../Errors";
-import { HankoOptions } from "../../Hanko";
 
 /**
  * A class that handles communication with the Hanko API for the purposes
@@ -157,6 +156,9 @@ 

lib/client/ThirdPartyClient.ts

case "email_maxnum": code = "maxNumOfEmailAddressesReached"; break; + case "signup_disabled": + code = "signupDisabled"; + break; default: code = "somethingWentWrong"; } diff --git a/frontend/elements/src/components/accordion/ListEmailsAccordion.tsx b/frontend/elements/src/components/accordion/ListEmailsAccordion.tsx index 1bac52655..5dbeb4330 100644 --- a/frontend/elements/src/components/accordion/ListEmailsAccordion.tsx +++ b/frontend/elements/src/components/accordion/ListEmailsAccordion.tsx @@ -203,11 +203,7 @@ const ListEmailsAccordion = ({ {t("headlines.emailDelete")} - {email.identity - ? `${t("texts.emailDeleteThirdPartyConnection", { - provider: email.identity.provider, - })}` - : t("texts.emailDelete")} + {t("texts.emailDelete")}
)} - {email.identity ? ( + {email.identities?.length > 0 ? ( + + + {t("headlines.connectedAccounts")} + {email.identities.map((i) => i.provider).join(", ")} + + + ) : email.identity ? ( {t("headlines.connectedAccounts")} diff --git a/frontend/elements/src/i18n/bn.ts b/frontend/elements/src/i18n/bn.ts index b839e2c9e..5e1bd6cf2 100644 --- a/frontend/elements/src/i18n/bn.ts +++ b/frontend/elements/src/i18n/bn.ts @@ -47,8 +47,6 @@ export const bn: Translation = { emailUnverified: "এই ইমেল ঠিকানা যাচাই করা হয়নি.", emailDelete: "আপনি এই ইমেল ঠিকানা মুছে ফেললে, এটি আর সাইন ইন করতে ব্যবহার করা যাবে না.", - emailDeleteThirdPartyConnection: - "আপনি এই ইমেল ঠিকানা মুছে ফেললে, এটি আর সাইন ইন করতে ব্যবহার করা যাবে না.", emailDeletePrimary: "প্রাথমিক ইমেল ঠিকানা মুছে ফেলা যাবে না.", renamePasskey: @@ -119,5 +117,6 @@ export const bn: Translation = { "অ্যাকাউন্ট শনাক্ত করতে পারে না। ইমেল ঠিকানা একাধিক অ্যাকাউন্ট দ্বারা ব্যবহৃত হয়।", thirdPartyUnverifiedEmail: "ইমেল যাচাইকরণ প্রয়োজন. অনুগ্রহ করে আপনার প্রদানকারীর সাথে ব্যবহৃত ইমেল ঠিকানা যাচাই করুন।", + signupDisabled: "অ্যাকাউন্ট নিবন্ধন নিষ্ক্রিয় করা হয়েছে", }, }; diff --git a/frontend/elements/src/i18n/de.ts b/frontend/elements/src/i18n/de.ts index ff3df55ef..6ac1d6e96 100644 --- a/frontend/elements/src/i18n/de.ts +++ b/frontend/elements/src/i18n/de.ts @@ -49,8 +49,6 @@ export const de: Translation = { emailUnverified: "Diese E-Mail-Adresse wurde noch nicht verifiziert.", emailDelete: "Wenn Sie diese E-Mail-Adresse löschen, kann sie nicht mehr für die Anmeldung bei Ihrem Konto verwendet werden. Passkeys, die möglicherweise mit dieser E-Mail-Adresse erstellt wurden, funktionieren weiterhin.", - emailDeleteThirdPartyConnection: - "Wenn Sie diese E-Mail-Adresse löschen, kann sie nicht mehr für die Anmeldung bei Ihrem Konto verwendet werden. Sie können das verbundene {provider}-Konto ebenfalls nicht mehr zur Anmeldung nutzen oder dieses neu verbinden. Passkeys, die möglicherweise mit dieser E-Mail-Adresse erstellt wurden, funktionieren weiterhin.", emailDeletePrimary: "Die primäre E-Mail-Adresse kann nicht gelöscht werden. Fügen Sie zuerst eine andere E-Mail-Adresse hinzu und legen Sie diese als primär fest.", renamePasskey: @@ -125,5 +123,6 @@ export const de: Translation = { "Konto kann nicht eindeutig identifiziert werden. Die genutzte E-Mail-Adresse wird von mehreren Konten verwendet.", thirdPartyUnverifiedEmail: "Verifizierung der E-Mail-Adresse erforderlich. Bitte verifizieren sie die genutzte E-Mail-Adresse bei ihrem Provider.", + signupDisabled: "Die Kontoregistrierung ist deaktiviert.", }, }; diff --git a/frontend/elements/src/i18n/en.ts b/frontend/elements/src/i18n/en.ts index 2a0822230..05111bf4e 100644 --- a/frontend/elements/src/i18n/en.ts +++ b/frontend/elements/src/i18n/en.ts @@ -47,8 +47,6 @@ export const en: Translation = { emailUnverified: "This email address has not been verified.", emailDelete: "If you delete this email address, it can no longer be used to sign in.", - emailDeleteThirdPartyConnection: - "If you delete this email address, it can no longer be used to sign in.", emailDeletePrimary: "The primary email address cannot be deleted.", renamePasskey: @@ -119,5 +117,6 @@ export const en: Translation = { "Cannot identify account. The email address is used by multiple accounts.", thirdPartyUnverifiedEmail: "Email verification required. Please verify the used email address with your provider.", + signupDisabled: "Account registration is disabled.", }, }; diff --git a/frontend/elements/src/i18n/fr.ts b/frontend/elements/src/i18n/fr.ts index 4d14a75e5..6a65fe5b3 100644 --- a/frontend/elements/src/i18n/fr.ts +++ b/frontend/elements/src/i18n/fr.ts @@ -49,8 +49,6 @@ export const fr: Translation = { emailUnverified: "Cette adresse e-mail n'a pas été vérifiée.", emailDelete: "Si vous supprimez cette adresse e-mail, elle ne pourra plus être utilisée pour vous connecter à votre compte. Les clés d'identification qui ont pu être créées avec cette adresse e-mail resteront intactes.", - emailDeleteThirdPartyConnection: - "Si vous supprimez cette adresse e-mail, elle ne pourra plus être utilisée pour se connecter. Vous ne pourrez également plus vous connecter avec ou reconnecter votre compte {provider}. Les clés d'identification qui ont pu être créées avec cette adresse e-mail resteront intactes.", emailDeletePrimary: "L'adresse e-mail principale ne peut pas être supprimée. Ajoutez d'abord une autre adresse e-mail et définissez-la comme adresse e-mail principale.", renamePasskey: @@ -124,5 +122,6 @@ export const fr: Translation = { "Impossible d'identifier le compte. L'adresse e-mail est utilisée par plusieurs comptes.", thirdPartyUnverifiedEmail: "Vérification de l'adresse e-mail requise. Veuillez vérifier l'adresse e-mail utilisée avec votre fournisseur.", + signupDisabled: "L'enregistrement du compte est désactivé.", }, }; diff --git a/frontend/elements/src/i18n/it.ts b/frontend/elements/src/i18n/it.ts index 2f6b60c99..f754861e2 100644 --- a/frontend/elements/src/i18n/it.ts +++ b/frontend/elements/src/i18n/it.ts @@ -46,8 +46,6 @@ export const it: Translation = { emailUnverified: "Questo indirizzo email non è stato verificato.", emailDelete: "Se cancelli questo indirizzo email, non potrà più essere utilizzato per accedere.", - emailDeleteThirdPartyConnection: - "Se cancelli questo indirizzo email, non potrà più essere utilizzato per accedere.", emailDeletePrimary: "L'indirizzo email principale non può essere eliminato.", renamePasskey: @@ -118,5 +116,6 @@ export const it: Translation = { "Impossibile identificare l'account. L'indirizzo email è utilizzato in più account.", thirdPartyUnverifiedEmail: "Verifica email richiesta. Verifica l'indirizzo email utilizzato con il tuo provider.", + signupDisabled: "La registrazione dell'account è disabilitata.", }, }; diff --git a/frontend/elements/src/i18n/pt-BR.ts b/frontend/elements/src/i18n/pt-BR.ts index ccda84e2c..93fff01a3 100644 --- a/frontend/elements/src/i18n/pt-BR.ts +++ b/frontend/elements/src/i18n/pt-BR.ts @@ -48,8 +48,6 @@ export const ptBR: Translation = { emailUnverified: "Este e-mail não foi verificado.", emailDelete: "Se você apagar esse e-mail, não poderá mais usá-lo para entrar em sua conta.", - emailDeleteThirdPartyConnection: - "Se você apagar esse e-mail, não poderá mais usá-lo para entrar em sua conta.", emailDeletePrimary: "O seu e-mail principal não pode ser apagado.", renamePasskey: "Defina um nome para a chave de acesso.", deletePasskey: "Remova essa chave de acesso da sua conta.", @@ -118,5 +116,6 @@ export const ptBR: Translation = { "Não foi possível identificar a conta. O endereço de e-mail é usado por várias contas.", thirdPartyUnverifiedEmail: "Verificação de e-mail necessária. Por favor, verifique o e-mail utilizado com o seu provedor.", + signupDisabled: "O registro da conta está desativado.", }, }; diff --git a/frontend/elements/src/i18n/translations.ts b/frontend/elements/src/i18n/translations.ts index ffe124038..6f157664a 100644 --- a/frontend/elements/src/i18n/translations.ts +++ b/frontend/elements/src/i18n/translations.ts @@ -45,7 +45,6 @@ export interface Translation { emailVerified: string; emailUnverified: string; emailDelete: string; - emailDeleteThirdPartyConnection: string; emailDeletePrimary: string; renamePasskey: string; deletePasskey: string; @@ -103,6 +102,7 @@ export interface Translation { thirdPartyAccessDenied: string; thirdPartyMultipleAccounts: string; thirdPartyUnverifiedEmail: string; + signupDisabled: string; }; } diff --git a/frontend/elements/src/i18n/zh.ts b/frontend/elements/src/i18n/zh.ts index 75c0b2425..daa76bda0 100644 --- a/frontend/elements/src/i18n/zh.ts +++ b/frontend/elements/src/i18n/zh.ts @@ -44,8 +44,6 @@ export const zh: Translation = { emailUnverified: "此电子邮件地址尚未验证。", emailDelete: "如果您删除此电子邮件地址,将无法再用于登录您的账户。可能已经用此电子邮件地址创建的密钥将保持完整。", - emailDeleteThirdPartyConnection: - "如果你删除这个电子邮件地址,你将不能用它登录。你也不能再用你的{provider}帐户登录或重新连接。可能已经用此电子邮件地址创建的密钥将保持完整。", emailDeletePrimary: "主要电子邮件地址不能被删除。请先添加另一个电子邮件地址并设定为主要电子邮件地址。", renamePasskey: "为密钥设定名称,帮助您识别其所存储的位置。", @@ -109,5 +107,6 @@ export const zh: Translation = { thirdPartyMultipleAccounts: "无法确定账户。电子邮件地址被多个账户使用。", thirdPartyUnverifiedEmail: "需要电子邮件验证。请与您的提供商验证使用的电子邮件地址。", + signupDisabled: "帐户注册被禁用。", }, }; diff --git a/frontend/frontend-sdk/src/lib/Dto.ts b/frontend/frontend-sdk/src/lib/Dto.ts index 795d47a27..a67ac24d0 100644 --- a/frontend/frontend-sdk/src/lib/Dto.ts +++ b/frontend/frontend-sdk/src/lib/Dto.ts @@ -179,6 +179,7 @@ export interface Attestation extends PublicKeyCredentialWithAttestationJSON { * @property {boolean} is_verified - Indicates whether the email address is verified. * @property {boolean} is_primary - Indicates it's the primary email address. * @property {Identity} identity - Indicates that this email is linked to a third party account. + * @property {Identity[]} identities - A list of identities, each identity indicates that this email is linked to a third party account. */ export interface Email { id: string; @@ -186,6 +187,7 @@ export interface Email { is_verified: boolean; is_primary: boolean; identity: Identity; + identities: Identity[]; } /** @@ -232,8 +234,8 @@ export interface WebauthnCredentials extends Array {} * @interface * @category SDK * @subcategory DTO - * @property {id} - The subject ID with the third party provider. - * @property {provider} - The third party provider name. + * @property {string} id - The subject ID with the third party provider. + * @property {string} provider - The third party provider name. */ export interface Identity { id: string; diff --git a/frontend/frontend-sdk/src/lib/client/ThirdPartyClient.ts b/frontend/frontend-sdk/src/lib/client/ThirdPartyClient.ts index eb007cea0..9b5718d19 100644 --- a/frontend/frontend-sdk/src/lib/client/ThirdPartyClient.ts +++ b/frontend/frontend-sdk/src/lib/client/ThirdPartyClient.ts @@ -1,6 +1,5 @@ import { Client } from "./Client"; import { ThirdPartyError } from "../Errors"; -import { HankoOptions } from "../../Hanko"; /** * A class that handles communication with the Hanko API for the purposes @@ -70,6 +69,9 @@ export class ThirdPartyClient extends Client { case "email_maxnum": code = "maxNumOfEmailAddressesReached"; break; + case "signup_disabled": + code = "signupDisabled"; + break; default: code = "somethingWentWrong"; } From b02fce6046043b8854e3e7a1e07b0ba37bd53922 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:22:34 +0100 Subject: [PATCH 23/24] chore(deps): bump github.com/go-webauthn/webauthn in /backend Bumps [github.com/go-webauthn/webauthn](https://github.com/go-webauthn/webauthn) from 0.10.0 to 0.10.1. - [Release notes](https://github.com/go-webauthn/webauthn/releases) - [Commits](https://github.com/go-webauthn/webauthn/compare/v0.10.0...v0.10.1) --- updated-dependencies: - dependency-name: github.com/go-webauthn/webauthn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/go.mod | 4 ++-- backend/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 513b13d18..f0068530a 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.17.0 github.com/go-sql-driver/mysql v1.7.1 github.com/go-testfixtures/testfixtures/v3 v3.9.0 - github.com/go-webauthn/webauthn v0.10.0 + github.com/go-webauthn/webauthn v0.10.1 github.com/gobuffalo/nulls v0.4.2 github.com/gobuffalo/pop/v6 v6.1.1 github.com/gobuffalo/validate/v3 v3.3.3 @@ -74,7 +74,7 @@ require ( github.com/go-faster/errors v0.6.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-webauthn/x v0.1.6 // indirect + github.com/go-webauthn/x v0.1.8 // indirect github.com/gobuffalo/envy v1.10.2 // indirect github.com/gobuffalo/fizz v1.14.4 // indirect github.com/gobuffalo/flect v1.0.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 38aeffc03..dc958e9c2 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -134,10 +134,10 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= -github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk= -github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y= -github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ= -github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA= +github.com/go-webauthn/webauthn v0.10.1 h1:+RFKj4yHPy282teiiy5sqTYPfRilzBpJyedrz9KsNFE= +github.com/go-webauthn/webauthn v0.10.1/go.mod h1:a7BwAtrSMkeuJXtIKz433Av99nAv01pdfzB0a9xkDnI= +github.com/go-webauthn/x v0.1.8 h1:f1C6k1AyUlDvnIzWSW+G9rN9nbp1hhLXZagUtyxZ8nc= +github.com/go-webauthn/x v0.1.8/go.mod h1:i8UNlGVt3oy6oAFcP4SZB1djZLx/4pbekCbWowjTaJg= github.com/gobuffalo/attrs v1.0.3/go.mod h1:KvDJCE0avbufqS0Bw3UV7RQynESY0jjod+572ctX4t8= github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= From 7a8ed1a2711c55474b323f98c38d7d02e513a33b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:23:07 +0100 Subject: [PATCH 24/24] chore(deps): bump golang.org/x/oauth2 from 0.16.0 to 0.17.0 in /backend Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/oauth2/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/go.mod | 4 ++-- backend/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index f0068530a..74478be0b 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -38,7 +38,7 @@ require ( github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.19.0 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 - golang.org/x/oauth2 v0.16.0 + golang.org/x/oauth2 v0.17.0 golang.org/x/text v0.14.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v3 v3.0.1 @@ -156,7 +156,7 @@ require ( go.opentelemetry.io/otel v1.15.0 // indirect go.opentelemetry.io/otel/trace v1.15.0 // indirect golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index dc958e9c2..86187abb3 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -701,13 +701,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=