From 2d1a02b567ef412a4282b71e130ee9f2446ffad3 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Fri, 23 Oct 2026 17:05:37 +0000 Subject: [PATCH 01/11] feat(accounts): consume owner-projected managed key services --- module/KEY_ROTATION.md | 21 +++- module/configurations/local/key-service.env | 5 + module/services/accounts/code/go.mod | 6 + module/services/accounts/code/go.sum | 6 + .../accounts/code/pkg/keyservice/load.go | 29 ++++- .../accounts/code/pkg/keyservice/managed.go | 70 +++++++++++ .../code/pkg/keyservice/managed_test.go | 116 ++++++++++++++++++ 7 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 module/services/accounts/code/pkg/keyservice/managed.go create mode 100644 module/services/accounts/code/pkg/keyservice/managed_test.go diff --git a/module/KEY_ROTATION.md b/module/KEY_ROTATION.md index 604c0bc2f..4e5c5f71a 100644 --- a/module/KEY_ROTATION.md +++ b/module/KEY_ROTATION.md @@ -62,7 +62,26 @@ TTL — or within TTL + grace if accounts is unreachable for the whole window. > so there is no projection to read and no agent that owns the driver the way > `service-vault` owns Vault's. Filed as codefly-dev/cli#924. Everything above > the seam is already vendor-neutral, so when that kind exists the driver moves -> below it and nothing here changes. +> below it and nothing here changes. New managed providers use the owner runtime +> client described below; the legacy GCP implementation remains unchanged. + +`KEY_SERVICE_MANAGED_BINDING` optionally carries the deployment-produced, +non-secret provider descriptor consumed by `service-vault/keyservice`. Accounts +depends on that client's generic encryption, MAC and wrapping operations; no +second cloud SDK or driver lives here. The existing `kms` / `kms-wrapped` +selections still apply. Mixing a descriptor with legacy KMS key names is refused. + +The client owns provider ciphertexts, key-version checks and workload identity; +Accounts retains `cfs1` envelope framing, domain errors, tenant-key selection and +the `signing-key-wrap` purpose. The unwrapped value must still be a base64 +Ed25519 seed. A provider outage never falls back to ephemeral signing, including +when a local caller otherwise permits a fixture key. + +Build or reuse: the existing Host `Sealer`, `KeyFactory`, `Cipher` and wrapped-seed +path already define the required contracts. The owner client's provider driver +fills the missing implementation; no new Host crypto primitive, signing algorithm +or key store is introduced. Infrastructure allocation, exact workload grants, +recovery custody and live deployment remain separate qualification requirements. Two backends, selected per key family in the `key-service` group diff --git a/module/configurations/local/key-service.env b/module/configurations/local/key-service.env index b75efdf25..85b20b80c 100644 --- a/module/configurations/local/key-service.env +++ b/module/configurations/local/key-service.env @@ -119,3 +119,8 @@ KEY_SERVICE_KMS_MAC_KEY= # in the envelope framing, each by name. KEY_SERVICE_KMS_SIGNING_WRAP_KEY= KEY_SERVICE_SIGNING_KEY_WRAPPED= + +# Optional provider-owned managed installation. Supplied by the deployment +# owner; mutually exclusive with the legacy KMS key names above. The existing +# kms and kms-wrapped selections retain their custody and signing semantics. +KEY_SERVICE_MANAGED_BINDING= diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index e60dbd6b1..40f734494 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -15,6 +15,7 @@ require ( github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458 github.com/codefly-dev/service-postgres/libs/go v0.0.0-20260929200903-bae6c31c7211 github.com/codefly-dev/service-redis/cache v0.3.0 + github.com/codefly-dev/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9 github.com/go-webauthn/webauthn v0.18.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 @@ -49,6 +50,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect @@ -125,3 +128,6 @@ require ( golang.org/x/text v0.42.0 // indirect oras.land/oras-go/v2 v2.6.2 // indirect ) + +// DEVELOPMENT CANDIDATE: service-vault PR #86; replace with an owner release before promotion. +replace github.com/codefly-dev/service-vault/keyservice => github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9 diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index 61ad3a8f9..c66b6513c 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -20,6 +20,10 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDH github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 h1:MaKvxE6D0KkjOg6Wd9M00iqP5PR0kUxCfiezes4JweM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0/go.mod h1:i2h9fsTFKZorh8RdV2IcSUf/Qj98GlTkrTvUbX/s8as= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.9.0 h1:MDT4FxAPve5FnYn6vOL1r7RCRDG+l9cI7a5LlCuHsqA= @@ -78,6 +82,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9 h1:HoqnbhWwg0u4Gna8NU+4RpJmrD/2xSrwBXwVZDAurNY= +github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9/go.mod h1:15o4NPE329wd93fpl/zFPlj+Dx7bGoQqrDT5+nO7Yso= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/module/services/accounts/code/pkg/keyservice/load.go b/module/services/accounts/code/pkg/keyservice/load.go index 34dc30606..915b820b4 100644 --- a/module/services/accounts/code/pkg/keyservice/load.go +++ b/module/services/accounts/code/pkg/keyservice/load.go @@ -9,6 +9,7 @@ import ( "strings" codefly "github.com/codefly-dev/sdk-go" + managed "github.com/codefly-dev/service-vault/keyservice" "accounts/pkg/vaultconnection" ) @@ -67,7 +68,8 @@ type Binding struct { // VaultHealth probes the Vault that holds the envelope key, or nil. VaultHealth func(ctx context.Context) error - kms KMSConfig + kms KMSConfig + managed managed.Client // wrappedSigningKey is the configured ciphertext, read once at selection so // a missing one refuses with every other configuration problem rather than // after a key service has already been reached. @@ -138,7 +140,24 @@ func Load(ctx context.Context) (*Binding, error) { Backend: backend, Previous: previous, SigningBackend: signing, wrappedSigningKey: groupValue(ctx, SigningKeyWrappedKey), } - if backend == BackendKMS || previous == BackendKMS || signing == BackendKMSWrapped { + descriptor := groupValue(ctx, ManagedBindingKey) + usesManaged := backend == BackendKMS || previous == BackendKMS || signing == BackendKMSWrapped + if descriptor != "" { + if !usesManaged { + return nil, errors.New("managed key-service binding requires a kms consumer") + } + for _, key := range []string{KMSEnvelopeKeyKey, KMSMACKeyKey, KMSSigningWrapKeyKey} { + if groupValue(ctx, key) != "" { + return nil, errors.New("managed key-service binding cannot be combined with legacy KMS key names") + } + } + if signing == BackendKMSWrapped && binding.wrappedSigningKey == "" { + return nil, fmt.Errorf("%s is required for wrapped signing", SigningKeyWrappedKey) + } + if binding.managed, err = managed.New(ctx, descriptor); err != nil { + return nil, err + } + } else if usesManaged { if binding.kms, err = kmsConfig(ctx, backend, previous, signing); err != nil { return nil, err } @@ -175,6 +194,9 @@ func (b *Binding) sealer(ctx context.Context, backend Backend) (Sealer, error) { } return sealer, nil case BackendKMS: + if b.managed != nil { + return managedSealer{b.managed}, nil + } return NewKMSSealer(ctx, b.kms) default: return nil, fmt.Errorf("unsupported key-service backend %q", backend) @@ -209,6 +231,9 @@ func (b *Binding) vaultConnection(ctx context.Context) (*vaultconnection.Connect // gateway pinned. func (b *Binding) SigningKey(ctx context.Context, ephemeral bool) (ed25519.PrivateKey, error) { if b.SigningBackend == BackendKMSWrapped { + if b.managed != nil { + return managedSigningKey(ctx, b.managed, b.wrappedSigningKey) + } wrap, err := NewKMSKeyWrap(ctx, b.kms) if err != nil { return nil, err diff --git a/module/services/accounts/code/pkg/keyservice/managed.go b/module/services/accounts/code/pkg/keyservice/managed.go new file mode 100644 index 000000000..a635a8ee2 --- /dev/null +++ b/module/services/accounts/code/pkg/keyservice/managed.go @@ -0,0 +1,70 @@ +package keyservice + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "errors" + "fmt" + + managed "github.com/codefly-dev/service-vault/keyservice" + + "accounts/pkg/business" +) + +// ManagedBindingKey is the non-secret installation projected by the key-service +// owner. Provider drivers live in that owner's runtime client, never in Host. +const ManagedBindingKey = "KEY_SERVICE_MANAGED_BINDING" + +type managedSealer struct{ managed.Client } + +func (s managedSealer) Accepts(e Envelope) bool { + return e.Backend == s.Tag() && s.Client.Accepts(e.Payload) +} + +func (s managedSealer) Open(ctx context.Context, purpose, payload string) (string, error) { + value, err := s.Client.Open(ctx, purpose, payload) + if errors.Is(err, managed.ErrInvalidCiphertext) { + return "", fmt.Errorf("%w: %w", business.ErrInvalidSecretEnvelope, err) + } + return value, err +} + +func (s managedSealer) MAC(ctx context.Context, value string) (string, error) { + hash, err := s.Client.MAC(ctx, value) + if err != nil { + return "", err + } + return (Envelope{Backend: s.Tag(), Payload: hash}).String(), nil +} + +func (s managedSealer) SealerFor(ctx context.Context, key string) (Sealer, error) { + client, err := s.Client.ForKey(ctx, key) + if err != nil { + return nil, err + } + return managedSealer{client}, nil +} + +func managedSigningKey(ctx context.Context, client managed.Client, value string) (ed25519.PrivateKey, error) { + envelope, err := ParseEnvelope(value) + if err != nil { + return nil, err + } + if envelope.Backend != client.Tag() { + return nil, errors.New("wrapped signing key belongs to another key service") + } + encoded, err := client.Unwrap(ctx, signingKeyWrapPurpose, envelope.Payload) + if err != nil { + return nil, fmt.Errorf("unwrap managed signing key: %w", err) + } + seed, err := base64.StdEncoding.Strict().DecodeString(encoded) + if err != nil || len(seed) != ed25519.SeedSize { + return nil, errors.New("managed signing key must unwrap to a base64 Ed25519 seed") + } + defer clear(seed) + return ed25519.NewKeyFromSeed(seed), nil +} + +var _ Sealer = managedSealer{} +var _ KeyFactory = managedSealer{} diff --git a/module/services/accounts/code/pkg/keyservice/managed_test.go b/module/services/accounts/code/pkg/keyservice/managed_test.go new file mode 100644 index 000000000..92277d7b2 --- /dev/null +++ b/module/services/accounts/code/pkg/keyservice/managed_test.go @@ -0,0 +1,116 @@ +package keyservice + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "errors" + "testing" + + managed "github.com/codefly-dev/service-vault/keyservice" + "github.com/stretchr/testify/require" + + "accounts/pkg/business" +) + +// Only Host's adaptation is controlled here. Provider cryptography and retained +// key behavior are qualified by the runtime client's own SDK transport suite. +type projectedClient struct { + managed.Client + sealer testSealer + seed string + err error + purpose string +} + +func (c *projectedClient) Tag() string { return c.sealer.Tag() } +func (c *projectedClient) Identity() string { return c.sealer.Identity() } +func (c *projectedClient) Accepts(payload string) bool { + return c.sealer.Accepts(Envelope{Backend: c.Tag(), Payload: payload}) +} +func (c *projectedClient) Seal(ctx context.Context, purpose, value string) (string, error) { + return c.sealer.Seal(ctx, purpose, value) +} +func (c *projectedClient) Open(ctx context.Context, purpose, value string) (string, error) { + if c.err != nil { + return "", c.err + } + return c.sealer.Open(ctx, purpose, value) +} +func (c *projectedClient) MAC(ctx context.Context, value string) (string, error) { + return c.sealer.MAC(ctx, value) +} +func (c *projectedClient) Unwrap(_ context.Context, purpose, _ string) (string, error) { + c.purpose = purpose + return c.seed, c.err +} +func (c *projectedClient) ForKey(_ context.Context, key string) (managed.Client, error) { + return &projectedClient{sealer: testSealer{tag: c.Tag(), key: key}}, nil +} + +func TestManagedSealerRetainsHostFramingAndErrorSemantics(t *testing.T) { + client := &projectedClient{sealer: testSealer{tag: "managed-test", key: "deployment"}} + sealer := managedSealer{client} + cipher, err := NewCipher(sealer, nil) + require.NoError(t, err) + stored, err := cipher.EncryptSecret(t.Context(), "row", "credential") + require.NoError(t, err) + require.True(t, IsEnvelopeFraming(stored)) + parsed, err := ParseEnvelope(stored) + require.NoError(t, err) + require.Equal(t, "managed-test", parsed.Backend) + opened, err := cipher.DecryptSecret(t.Context(), "row", stored) + require.NoError(t, err) + require.Equal(t, "credential", opened) + hash, err := sealer.MAC(t.Context(), "api-key") + require.NoError(t, err) + require.Equal(t, "cfs1:managed-test:deployment:api-key", hash) + org, err := sealer.SealerFor(t.Context(), "organization") + require.NoError(t, err) + require.NotEqual(t, sealer.Identity(), org.Identity()) + client.err = managed.ErrInvalidCiphertext + _, err = cipher.DecryptSecret(t.Context(), "row", stored) + require.ErrorIs(t, err, business.ErrInvalidSecretEnvelope) + client.err = errors.New("provider unavailable") + _, err = cipher.DecryptSecret(t.Context(), "row", stored) + require.ErrorIs(t, err, client.err) + require.NotErrorIs(t, err, business.ErrInvalidSecretEnvelope) +} + +func TestManagedWrappedSigningPreservesExistingEd25519Contract(t *testing.T) { + seed := make([]byte, ed25519.SeedSize) + seed[0] = 19 + client := &projectedClient{sealer: testSealer{tag: "managed-test"}, seed: base64.StdEncoding.EncodeToString(seed)} + binding := &Binding{SigningBackend: BackendKMSWrapped, managed: client, wrappedSigningKey: "cfs1:managed-test:opaque"} + key, err := binding.SigningKey(t.Context(), false) + require.NoError(t, err) + require.Equal(t, ed25519.NewKeyFromSeed(seed), key) + require.Equal(t, signingKeyWrapPurpose, client.purpose) + again, err := binding.SigningKey(t.Context(), false) + require.NoError(t, err) + require.Equal(t, key, again) + client.err = errors.New("access revoked") + _, err = binding.SigningKey(t.Context(), true) + require.ErrorIs(t, err, client.err) + require.NotErrorIs(t, err, ErrNoSigningKey, "managed custody must never permit ephemeral fallback") + client.err = nil + client.seed = base64.StdEncoding.EncodeToString([]byte("short")) + _, err = binding.SigningKey(t.Context(), false) + require.Error(t, err) + binding.wrappedSigningKey = "cfs1:other:opaque" + _, err = binding.SigningKey(t.Context(), false) + require.Error(t, err) +} + +func TestManagedSelectionRefusesMixedBindingsBeforeProviderAccess(t *testing.T) { + t.Setenv(BackendKey, "kms") + t.Setenv(SigningBackendKey, "kms-wrapped") + t.Setenv(ManagedBindingKey, `{"provider":"unreachable"}`) + t.Setenv(KMSEnvelopeKeyKey, "legacy-key") + _, err := Load(t.Context()) + require.ErrorContains(t, err, "legacy KMS") + t.Setenv(KMSEnvelopeKeyKey, "") + t.Setenv(SigningKeyWrappedKey, "") + _, err = Load(t.Context()) + require.ErrorContains(t, err, SigningKeyWrappedKey) +} From a84865152962ee98be85cb6ff55873aaea5420d5 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:05:44 +0000 Subject: [PATCH 02/11] fix(keyservice): adopt Managed HSM generated-nonce correction --- module/services/accounts/code/go.mod | 4 ++-- module/services/accounts/code/go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index 40f734494..f99125b83 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -15,7 +15,7 @@ require ( github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458 github.com/codefly-dev/service-postgres/libs/go v0.0.0-20260929200903-bae6c31c7211 github.com/codefly-dev/service-redis/cache v0.3.0 - github.com/codefly-dev/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9 + github.com/codefly-dev/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 github.com/go-webauthn/webauthn v0.18.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 @@ -130,4 +130,4 @@ require ( ) // DEVELOPMENT CANDIDATE: service-vault PR #86; replace with an owner release before promotion. -replace github.com/codefly-dev/service-vault/keyservice => github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9 +replace github.com/codefly-dev/service-vault/keyservice => github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index c66b6513c..13609cbe2 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -82,8 +82,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9 h1:HoqnbhWwg0u4Gna8NU+4RpJmrD/2xSrwBXwVZDAurNY= -github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023164748-c4743f2f97e9/go.mod h1:15o4NPE329wd93fpl/zFPlj+Dx7bGoQqrDT5+nO7Yso= +github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 h1:QhfPRUrZ8tp6XFg2/jXvx1fwtNqVHWYOkKeXkb7bZ14= +github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7/go.mod h1:15o4NPE329wd93fpl/zFPlj+Dx7bGoQqrDT5+nO7Yso= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 3e9f20a706ff5fe12a378bb092c424e1190cc6fb Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:50:09 +0000 Subject: [PATCH 03/11] fix(frontendpayload): read Azure registry bundles with workload identity Reuse the Azure identity and container registry SDKs for token exchange and renewal, and the existing repository transport and bundle verifier for scoped reads. Preserve the existing labelled workload-reader adapter until Codefly owns cloud identity resolution. --- module/frontendpayload/azure.go | 64 +++++++++++++++++++++ module/frontendpayload/azure_test.go | 84 ++++++++++++++++++++++++++++ module/frontendpayload/go.mod | 10 ++++ module/frontendpayload/go.sum | 35 +++++++++++- module/frontendpayload/workload.go | 13 +++-- 5 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 module/frontendpayload/azure.go create mode 100644 module/frontendpayload/azure_test.go diff --git a/module/frontendpayload/azure.go b/module/frontendpayload/azure.go new file mode 100644 index 000000000..5c18a4eda --- /dev/null +++ b/module/frontendpayload/azure.go @@ -0,0 +1,64 @@ +package frontendpayload + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + "github.com/codefly-dev/core/solutionhost" + "oras.land/oras-go/v2/registry" +) + +// The same workload-reader stopgap as workload.go, using Azure's credential, +// token exchange and renewal implementation rather than another auth flow. +type azureWorkloadSource struct { + client *azcontainerregistry.BlobClient + repository string +} + +func newAzureWorkloadSource(repository string) (Source, error) { + ref, err := registry.ParseReference(repository) + if err != nil || ref.Reference != "" || ref.Repository == "" { + return nil, errors.New("frontend artifact repository must name one repository without a tag or digest") + } + credential, err := azidentity.NewWorkloadIdentityCredential(nil) + if err != nil { + return nil, &readerIdentityError{"Azure frontend artifact reads require the pod's bound workload identity"} + } + return azureRegistrySource(ref, credential, http.DefaultTransport) +} + +func azureRegistrySource(ref registry.Reference, credential azcore.TokenCredential, base http.RoundTripper) (Source, error) { + client, err := azcontainerregistry.NewBlobClient("https://"+ref.Registry, credential, + &azcontainerregistry.BlobClientOptions{ClientOptions: azcore.ClientOptions{ + Transport: &http.Client{Timeout: 30 * time.Second, + Transport: repositoryTransport{host: ref.Registry, scheme: "https", base: base}}, + }}) + if err != nil { + return nil, &readerIdentityError{"could not configure the Azure frontend artifact reader"} + } + return &azureWorkloadSource{client: client, repository: ref.Repository}, nil +} + +func (s *azureWorkloadSource) Open(ctx context.Context, digest solutionhost.PayloadDigest) (io.ReadCloser, error) { + response, err := s.client.GetBlob(ctx, s.repository, string(digest), nil) + if err != nil { + var status *azcore.ResponseError + if errors.As(err, &status) { + return nil, fmt.Errorf("%w: the registry answered HTTP %d", ErrUnavailable, status.StatusCode) + } + return nil, unavailable(err) + } + if response.DockerContentDigest == nil || *response.DockerContentDigest != string(digest) || + response.ContentLength == nil || *response.ContentLength < 0 || *response.ContentLength > MaxBundleBytes { + response.BlobData.Close() + return nil, errors.New("frontend artifact descriptor differs from the admitted bundle") + } + return response.BlobData, nil +} diff --git a/module/frontendpayload/azure_test.go b/module/frontendpayload/azure_test.go new file mode 100644 index 000000000..2c5baf1dc --- /dev/null +++ b/module/frontendpayload/azure_test.go @@ -0,0 +1,84 @@ +package frontendpayload + +import ( + "context" + "encoding/base64" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/stretchr/testify/require" + "oras.land/oras-go/v2/registry" +) + +type azureTestCredential struct{ calls int } + +func (c *azureTestCredential) GetToken(_ context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) { + c.calls++ + return azcore.AccessToken{Token: "test-workload-token", ExpiresOn: time.Now().Add(time.Hour)}, nil +} + +func TestAzureWorkloadSourceExchangesIdentityAndReadsAdmittedBundle(t *testing.T) { + data := archiveFor(t, "azure-bundle") + frontend := frontendFor(data) + digest := string(frontend.Payload.Digest) + refresh := "e30." + base64.RawStdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"exp":%d}`, time.Now().Add(time.Hour).Unix()))) + ".sig" + mode := "ok" + var server *httptest.Server + server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/oauth2/exchange": + require.NoError(t, r.ParseForm()) + require.Equal(t, "test-workload-token", r.Form.Get("access_token")) + require.Equal(t, "access_token", r.Form.Get("grant_type")) + fmt.Fprintf(w, `{"refresh_token":%q}`, refresh) + case "/oauth2/token": + require.NoError(t, r.ParseForm()) + require.Equal(t, refresh, r.Form.Get("refresh_token")) + require.Equal(t, "repository:frontend-bundles:pull", r.Form.Get("scope")) + fmt.Fprint(w, `{"access_token":"test-registry-token"}`) + default: + require.Equal(t, "/v2/frontend-bundles/blobs/"+digest, r.URL.Path) + if r.Header.Get("Authorization") != "Bearer test-registry-token" { + w.Header().Set("WWW-Authenticate", `Bearer realm="`+server.URL+`/oauth2/token",service="`+strings.TrimPrefix(server.URL, "https://")+`",scope="repository:frontend-bundles:pull"`) + w.WriteHeader(http.StatusUnauthorized) + return + } + if mode == "forbidden" { + http.Error(w, "private-response-must-not-leak", http.StatusForbidden) + return + } + if mode == "wrong-digest" { + w.Header().Set("Docker-Content-Digest", "sha256:"+strings.Repeat("0", 64)) + } else { + w.Header().Set("Docker-Content-Digest", digest) + } + w.Header().Set("Content-Length", fmt.Sprint(len(data))) + _, _ = w.Write(data) + } + })) + defer server.Close() + ref, err := registry.ParseReference(strings.TrimPrefix(server.URL, "https://") + "/frontend-bundles") + require.NoError(t, err) + credential := &azureTestCredential{} + source, err := azureRegistrySource(ref, credential, server.Client().Transport) + require.NoError(t, err) + bundle, err := Prepare(t.Context(), source, frontend) + require.NoError(t, err) + require.Equal(t, "azure-bundle", readAsset(t, bundle, "deferred.js")) + require.Equal(t, 1, credential.calls) + mode = "wrong-digest" + _, err = source.Open(t.Context(), frontend.Payload.Digest) + require.ErrorContains(t, err, "descriptor differs") + mode = "forbidden" + _, err = source.Open(t.Context(), frontend.Payload.Digest) + require.ErrorIs(t, err, ErrUnavailable) + require.ErrorContains(t, err, "HTTP 403") + require.NotContains(t, err.Error(), "private-response") + require.Equal(t, 1, credential.calls, "the SDK reuses its workload token exchange") +} diff --git a/module/frontendpayload/go.mod b/module/frontendpayload/go.mod index c8088de2f..9b584d05a 100644 --- a/module/frontendpayload/go.mod +++ b/module/frontendpayload/go.mod @@ -3,6 +3,9 @@ module github.com/codefly-dev/module-saas-starter/module/frontendpayload go 1.27.3 require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 + github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 github.com/codefly-dev/core v0.19.5-0.20261014005330-835460f16fb5 github.com/stretchr/testify v1.12.1 oras.land/oras-go/v2 v2.6.2 @@ -13,6 +16,8 @@ require ( buf.build/go/protovalidate v1.4.0 // indirect cel.dev/cel-go v0.32.0 // indirect cel.dev/expr v0.25.3 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect @@ -33,19 +38,24 @@ require ( github.com/go-openapi/swag/yamlutils v0.29.2 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gofrs/flock v0.13.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/go-github/v89 v89.0.0 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect github.com/yoheimuta/go-protoparser/v4 v4.14.2 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect golang.org/x/net v0.60.0 // indirect golang.org/x/sync v0.23.0 // indirect diff --git a/module/frontendpayload/go.sum b/module/frontendpayload/go.sum index 24dc91555..48bbdb089 100644 --- a/module/frontendpayload/go.sum +++ b/module/frontendpayload/go.sum @@ -6,6 +6,20 @@ cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= @@ -36,6 +50,7 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -110,6 +125,8 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gofrs/flock v0.13.1 h1:jjREztyBeSKBZYAC+mgc1laB+xsgy4kYMf3FbKF2UBo= github.com/gofrs/flock v0.13.1/go.mod h1:sf4BFiHwnvgxa25DlQoDqXQnwRMEOwqxRq37P6MzzmE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= @@ -128,6 +145,14 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= @@ -151,12 +176,16 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28= github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= @@ -193,12 +222,15 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= @@ -213,8 +245,9 @@ google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/module/frontendpayload/workload.go b/module/frontendpayload/workload.go index bcb35f1ee..7d0a1da6a 100644 --- a/module/frontendpayload/workload.go +++ b/module/frontendpayload/workload.go @@ -12,8 +12,8 @@ package frontendpayload // which carries the same label for the same reason: Codefly has no // "this cell's workload identity" dependency kind yet, so there is nothing // to resolve a reader through. When one exists this moves below that seam. -// Another cloud's registry is refused by name here rather than read -// anonymously. +// Azure Container Registry uses the pod's federated workload identity through +// the Azure SDK (azure.go). Registries without a workload reader are refused. import ( "context" @@ -32,12 +32,15 @@ const workloadMetadataBase = "http://metadata.google.internal/computeMetadata/v1 // NewWorkloadRegistrySource reads a published bundle repository over HTTPS as // the pod's own workload identity. -// Any other registry is refused when the host starts, by name: failing closed +// Unsupported registries are refused when the host starts, by name: failing closed // at boot is visible, where an anonymous read would leave every page pending. -func NewWorkloadRegistrySource(repository string) (*RegistrySource, error) { +func NewWorkloadRegistrySource(repository string) (Source, error) { host, _, _ := strings.Cut(repository, "/") + if strings.HasSuffix(host, ".azurecr.io") { + return newAzureWorkloadSource(repository) + } if !strings.HasSuffix(host, ".pkg.dev") { - return nil, fmt.Errorf("frontend artifact repository %q is not on a registry this host reads as its workload identity (Artifact Registry)", repository) + return nil, fmt.Errorf("frontend artifact repository %q is not on a registry this host reads as its workload identity (Artifact Registry or Azure Container Registry)", repository) } return workloadRegistrySource(repository, &workloadToken{metadataBase: workloadMetadataBase, client: &http.Client{Timeout: 10 * time.Second}, now: time.Now}, nil) } From b20924346f0eec38a7af7dbfc8ec62a7b5286b31 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:52:31 +0000 Subject: [PATCH 04/11] fix(host): consume the Azure frontend reader candidate --- module/services/accounts/code/go.mod | 4 ++++ module/services/accounts/code/go.sum | 6 ++++-- module/services/auth-gateway/code/go.mod | 11 +++++++++++ module/services/auth-gateway/code/go.sum | 24 ++++++++++++++++++++++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index f99125b83..c08b6e238 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -49,6 +49,7 @@ require ( cel.dev/expr v0.25.3 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect @@ -131,3 +132,6 @@ require ( // DEVELOPMENT CANDIDATE: service-vault PR #86; replace with an owner release before promotion. replace github.com/codefly-dev/service-vault/keyservice => github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 + +// DEVELOPMENT CANDIDATE: frontend registry reader in PR #1234; adopt an owner release before promotion. +replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index 13609cbe2..b1fbdce25 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -18,6 +18,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5C github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 h1:MaKvxE6D0KkjOg6Wd9M00iqP5PR0kUxCfiezes4JweM= @@ -68,8 +70,6 @@ github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1 github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= github.com/codefly-dev/interface-cache/go/cache v0.4.1 h1:S7a20cVs3ilW8/HSpzmsyfJx/GJHgJ1xW9Dm9MYXYPI= github.com/codefly-dev/interface-cache/go/cache v0.4.1/go.mod h1:ki57cBwXnpFLx1uO29KiDY06lzyG/gXXBkqgi5ZNKWQ= -github.com/codefly-dev/module-saas-starter/module/frontendpayload v0.0.0-20261018173826-0d423cb8bd17 h1:ppOzy5ops7mEO9PApDfIcAiUBNy4z1MHOf3UJ8xXNNw= -github.com/codefly-dev/module-saas-starter/module/frontendpayload v0.0.0-20261018173826-0d423cb8bd17/go.mod h1:OxvwxMdo3HD190xFiMOPHtUsBC7/7Jt1evsEts/2HRs= github.com/codefly-dev/sdk-go v0.4.1 h1:WUKYWxXfojTr88SkLtq0T8aqpV0o5pR/muUUcsUUlzk= github.com/codefly-dev/sdk-go v0.4.1/go.mod h1:zr9ts3kkWNv2XEFUXZB+ydfW6x5b6/NbUvBm4KVYNqs= github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458 h1:wBOL7QsuoC0HsS43gsH2+tpsipk4os077C2VUDkMUHg= @@ -82,6 +82,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff h1:dtG4SGf3deNecwpfQzpFlZOY9fwY86E/xOCVCgqs3BA= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 h1:QhfPRUrZ8tp6XFg2/jXvx1fwtNqVHWYOkKeXkb7bZ14= github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7/go.mod h1:15o4NPE329wd93fpl/zFPlj+Dx7bGoQqrDT5+nO7Yso= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= diff --git a/module/services/auth-gateway/code/go.mod b/module/services/auth-gateway/code/go.mod index 48f936d53..c6e1ade5e 100644 --- a/module/services/auth-gateway/code/go.mod +++ b/module/services/auth-gateway/code/go.mod @@ -33,6 +33,11 @@ require ( buf.build/go/protovalidate v1.4.0 // indirect cel.dev/cel-go v0.32.0 // indirect cel.dev/expr v0.25.3 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect @@ -70,6 +75,7 @@ require ( github.com/google/go-querystring v1.2.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.24 // indirect @@ -78,6 +84,7 @@ require ( github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect @@ -97,6 +104,7 @@ require ( go.opentelemetry.io/otel/trace v1.47.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect golang.org/x/net v0.60.0 // indirect golang.org/x/sync v0.23.0 // indirect @@ -107,3 +115,6 @@ require ( ) exclude google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 + +// DEVELOPMENT CANDIDATE: frontend registry reader in PR #1234; adopt an owner release before promotion. +replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff diff --git a/module/services/auth-gateway/code/go.sum b/module/services/auth-gateway/code/go.sum index 9c9a03077..7b503bd1c 100644 --- a/module/services/auth-gateway/code/go.sum +++ b/module/services/auth-gateway/code/go.sum @@ -8,6 +8,20 @@ cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= @@ -46,8 +60,6 @@ github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda h1:ytTihtIEfVh github.com/codefly-dev/core v0.19.6-0.20261018051341-b81d92e6feda/go.mod h1:0uX/7lTBVaGV0RI2Mb3mpEr/PPdx/jZqXHym/U8FVDI= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= -github.com/codefly-dev/module-saas-starter/module/frontendpayload v0.0.0-20261018173826-0d423cb8bd17 h1:ppOzy5ops7mEO9PApDfIcAiUBNy4z1MHOf3UJ8xXNNw= -github.com/codefly-dev/module-saas-starter/module/frontendpayload v0.0.0-20261018173826-0d423cb8bd17/go.mod h1:OxvwxMdo3HD190xFiMOPHtUsBC7/7Jt1evsEts/2HRs= github.com/codefly-dev/sdk-go v0.4.1 h1:WUKYWxXfojTr88SkLtq0T8aqpV0o5pR/muUUcsUUlzk= github.com/codefly-dev/sdk-go v0.4.1/go.mod h1:zr9ts3kkWNv2XEFUXZB+ydfW6x5b6/NbUvBm4KVYNqs= github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458 h1:wBOL7QsuoC0HsS43gsH2+tpsipk4os077C2VUDkMUHg= @@ -56,6 +68,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff h1:dtG4SGf3deNecwpfQzpFlZOY9fwY86E/xOCVCgqs3BA= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -174,12 +188,16 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= @@ -205,6 +223,8 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pjbgf/sha1cd v0.7.0 h1:ZRNPKHj+gfkLBf0KJv/p1Hmz+7bqCT5o311YX0nq+DA= github.com/pjbgf/sha1cd v0.7.0/go.mod h1:pKR5Li+qTCo+ebqITZfwPlJGoCFCvSO00qxjbNtTUFQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= From a9a3c7c95144add25ab6a9ad7eb971dcbaa39d3c Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:47:22 +0000 Subject: [PATCH 05/11] fix(host): reuse Authority held-installation lookup --- .../source-read-sdk/library.codefly.yaml | 2 +- .../api/accounts/authority/contract.binpb | Bin 1301662 -> 1303193 bytes .../authority/proto/host/DIGESTS.sha256 | 2 +- .../host/installation/v1/installation.proto | 34 ++- .../api/accounts/connect/contract.binpb | Bin 1301662 -> 1303193 bytes .../connect/proto/host/DIGESTS.sha256 | 2 +- .../host/installation/v1/installation.proto | 34 ++- module/contracts/api/catalog.codefly.json | 5 +- module/module.package.codefly.yaml | 4 +- module/services/accounts/AUTHZ_MATRIX.md | 5 +- module/services/accounts/code/go.mod | 10 +- module/services/accounts/code/go.sum | 20 +- .../connect_handlers_host_interface.go | 4 + .../code/pkg/adapters/host_interface.go | 22 ++ .../impersonation_method_restriction_test.go | 3 +- .../code/pkg/business/interface_policies.go | 5 +- .../code/pkg/cataloggen/authz_methods_test.go | 3 +- .../host/installation/v1/installation.pb.go | 222 +++++++++++++++--- .../installation/v1/installation_grpc.pb.go | 60 ++++- .../installation.connect.go | 51 +++- .../generated/api-contract-surfaces.json | 1 + .../accounts/generated/authz-methods.json | 37 +++ .../accounts/generated/gateway-routes.json | 16 ++ .../accounts/generated/service-catalog.json | 44 ++++ .../accounts/proto/host/DIGESTS.sha256 | 2 +- .../host/installation/v1/installation.proto | 34 ++- .../auth-gateway/code/authz_catalog_gen.go | 1 + .../auth-gateway/code/routing_catalog_gen.go | 1 + .../services/frontend/code/package-lock.json | 4 +- module/services/frontend/code/package.json | 2 +- .../saas-sdk/generated/library.codefly.yaml | 2 +- .../host/installation/v1/installation_pb.ts | 94 +++++++- .../code/packages/saas-sdk/package.json | 2 +- .../host/installation/v1/installation_pb.ts | 94 +++++++- 34 files changed, 731 insertions(+), 91 deletions(-) diff --git a/libraries/source-read-sdk/library.codefly.yaml b/libraries/source-read-sdk/library.codefly.yaml index 0171cc00f..c41a49ae5 100644 --- a/libraries/source-read-sdk/library.codefly.yaml +++ b/libraries/source-read-sdk/library.codefly.yaml @@ -12,7 +12,7 @@ sources: module: saas-starter service: accounts endpoint: authority - contract-digest: sha256:743b63bad9a546f105e994c754d845865e189465e6c72816e5785efe38ab7831 + contract-digest: sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b services: - ModuleCapabilitiesService generated-by: diff --git a/module/contracts/api/accounts/authority/contract.binpb b/module/contracts/api/accounts/authority/contract.binpb index 8cfc99aa6440f15bb3a019fd7983d61c3a6f64b3..2de38bd1ba61a5ad3d36508530c569f8618caf17 100644 GIT binary patch delta 6810 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 delta 5565 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 delta 5565 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 Generated from protobuf service descriptors and `saas.policy.v1.method_policy`; only the prose description is joined from `pkg/business/introspection.go`. Do not edit by hand. Run `go generate ./pkg/business` from `module/services/accounts/code`. -Inventory: **296 RPCs** across **41 services**. Missing, invalid, or unclassified procedures are denied by both Connect and gRPC interceptors. +Inventory: **297 RPCs** across **41 services**. Missing, invalid, or unclassified procedures are denied by both Connect and gRPC interceptors. The compact tier is retained for compatibility. Guards, resource bindings, audit events, limiter class, and data sensitivity are descriptor-authoritative. Domain handlers may enforce stronger state-dependent rules but may not weaken this floor. @@ -27,6 +27,7 @@ The compact tier is retained for compatibility. Guards, resource bindings, audit | `/host.installation.v1.InstallationService/GrantRole` | unary | `—` | `auth` | exposure=AUTHENTICATED; tenant=NONE; impersonation=FORBIDDEN | — | — | SUCCESS: saas.authority.role_granted | FORBIDDEN / STANDARD_WRITE | CONFIDENTIAL → CONFIDENTIAL | Grant a person or a team one of an installed solution's roles. | | `/host.installation.v1.InstallationService/Install` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN; impersonation=FORBIDDEN | — | — | SUCCESS: saas.authority.installation_requested, saas.authority.installation_activated | FORBIDDEN / STANDARD_WRITE | CONFIDENTIAL → CONFIDENTIAL | Install one solution at one organisation under the consent its administrator reviewed; pending until its install step converges. | | `/host.installation.v1.InstallationService/List` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN; impersonation=FORBIDDEN | — | — | — | OPTIONAL / STANDARD_READ | CONFIDENTIAL → CONFIDENTIAL | List what one organisation has installed. | +| `/host.installation.v1.InstallationService/ListHeld` | unary | `—` | `auth` | exposure=AUTHENTICATED; tenant=NONE; impersonation=FORBIDDEN | — | — | — | OPTIONAL / STANDARD_READ | CONFIDENTIAL → CONFIDENTIAL | List only the signed-in member's current installation roles, including team grants. | | `/host.installation.v1.InstallationService/ListRoleGrants` | unary | `—` | `auth` | exposure=AUTHENTICATED; tenant=NONE; impersonation=FORBIDDEN | — | — | — | OPTIONAL / STANDARD_READ | CONFIDENTIAL → CONFIDENTIAL | List who holds which role on one installation. | | `/host.installation.v1.InstallationService/Review` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN; impersonation=FORBIDDEN | — | — | — | OPTIONAL / STANDARD_READ | CONFIDENTIAL → CONFIDENTIAL | Read the exact authority offer an administrator consents to, without recording consent. | | `/host.installation.v1.InstallationService/RevokeRole` | unary | `—` | `auth` | exposure=AUTHENTICATED; tenant=NONE; impersonation=FORBIDDEN | — | — | SUCCESS: saas.authority.role_revoked | FORBIDDEN / STANDARD_WRITE | CONFIDENTIAL → CONFIDENTIAL | Revoke one role grant; its subject's capabilities through the installation end. | @@ -307,7 +308,7 @@ The compact tier is retained for compatibility. Guards, resource bindings, audit ## Tier totals -- `auth`: 50 +- `auth`: 51 - `internal`: 55 - `mfa`: 5 - `org_admin`: 68 diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index c45572cd8..9fc1f61c5 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -7,11 +7,11 @@ require ( buf.build/go/protovalidate v1.4.0 connectrpc.com/connect v1.21.0 connectrpc.com/otelconnect v0.9.0 - github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027132425-c3568f113db1 - github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027132425-c3568f113db1 - github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027132425-c3568f113db1 - github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027132425-c3568f113db1 - github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027132425-c3568f113db1 + github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027160913-bfe4876a8b96 + github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027160913-bfe4876a8b96 + github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027160913-bfe4876a8b96 + github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027160913-bfe4876a8b96 + github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027160913-bfe4876a8b96 github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2 github.com/codefly-dev/interface-cache/go/cache v0.4.1 diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index 93aa53660..a892604a7 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -62,18 +62,18 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027132425-c3568f113db1 h1:y8J/hDtidFnBvfdRoApLx5qsbWxtpxXjAO9OyhuXXT4= -github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027132425-c3568f113db1/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg= -github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027132425-c3568f113db1 h1:nszx0AoJd9w08MwUWr380g1fVEJ4V8KiDQQ+hTfGYes= -github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027132425-c3568f113db1/go.mod h1:kTjeuNl03XSCkH3UhGKuHAKamkIzNsC2RPFu/g0fqSk= -github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027132425-c3568f113db1 h1:3KYJZ1TSC7nFyPls9PM6zXgt6QcnsQcPjRm2s+QwRSg= -github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027132425-c3568f113db1/go.mod h1:or3dd9bqacXWMjke/n81l/QxRaLwq2lq/hL4V2qtAZo= -github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027132425-c3568f113db1 h1:5Lm7ZEss7Y/pJS6owYdYNNqfVR+cNv31dc2HWtW3x+I= -github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027132425-c3568f113db1/go.mod h1:XsOFnO2MQdr37vM3ULa0Uwi8ZWptyVx7hRSUyIdaZ1Q= +github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027160913-bfe4876a8b96 h1:N8Gb3M8Ep9Mrqlhs/3+J1R4DZSb+aO2XvOqKODIOsms= +github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg= +github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027160913-bfe4876a8b96 h1:F3Pluk6x4lw3sjuTWBpFBTWCx8d7QgLkxNiibNRLAVU= +github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:kTjeuNl03XSCkH3UhGKuHAKamkIzNsC2RPFu/g0fqSk= +github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027160913-bfe4876a8b96 h1:JAwG62/l+wluGw3hok6FFs0qUUjVnnoOH2AYFnjkUXA= +github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:or3dd9bqacXWMjke/n81l/QxRaLwq2lq/hL4V2qtAZo= +github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027160913-bfe4876a8b96 h1:gd28HWTEPfBU5z9UIw6GeeCo1nK0wEc268wOhccrIGg= +github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:XsOFnO2MQdr37vM3ULa0Uwi8ZWptyVx7hRSUyIdaZ1Q= github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027063450-22b86ccbf2a5 h1:pHywzlszDDjNJIOg52YhmDCpW+7N4c/Ftma/qx5HxxE= github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027063450-22b86ccbf2a5/go.mod h1:9WF8dd98HYX9g/49uhrTgaTbWXo3hBvYzS3UVWYiZHw= -github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027132425-c3568f113db1 h1:xjWJja3adt61lpYvwtz3VYekrxU4r1sHvOmW9YQ0K0Q= -github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027132425-c3568f113db1/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As= +github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027160913-bfe4876a8b96 h1:Tf208FcTIlJHfjuhJJF/bshqRwye5aL5wflvbiR3Z3U= +github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As= github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc h1:olDnVJ1A1Retn0z9A59YMwDRmDLgkV2y1lbl+HzNrj8= github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc/go.mod h1:VWPYY3FH4te7SzdN2xl9odNpWidJ1Grl/Hjs1SNoEF8= github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2 h1:I1wL0ANXhTdQuEAnxXF+s4+rKILlBu/QHILq4Skyi3I= diff --git a/module/services/accounts/code/pkg/adapters/connect_handlers_host_interface.go b/module/services/accounts/code/pkg/adapters/connect_handlers_host_interface.go index 1c672b012..ef069be17 100644 --- a/module/services/accounts/code/pkg/adapters/connect_handlers_host_interface.go +++ b/module/services/accounts/code/pkg/adapters/connect_handlers_host_interface.go @@ -96,6 +96,10 @@ func (h *hostInstallationConnectHandler) List(ctx context.Context, req *connect. return unary(ctx, req, h.server().List) } +func (h *hostInstallationConnectHandler) ListHeld(ctx context.Context, req *connect.Request[installationv1.ListHeldRequest]) (*connect.Response[installationv1.ListHeldResponse], error) { + return unary(ctx, req, h.server().ListHeld) +} + type hostSessionConnectHandler struct{ svc *business.Service } func (h *hostSessionConnectHandler) SignIn(ctx context.Context, req *connect.Request[sessionv1.SignInRequest]) (*connect.Response[sessionv1.Session], error) { diff --git a/module/services/accounts/code/pkg/adapters/host_interface.go b/module/services/accounts/code/pkg/adapters/host_interface.go index db83bf604..f2149fb49 100644 --- a/module/services/accounts/code/pkg/adapters/host_interface.go +++ b/module/services/accounts/code/pkg/adapters/host_interface.go @@ -557,6 +557,28 @@ func (s *HostInstallationServer) List(ctx context.Context, req *installationv1.L return out, nil } +// ListHeld reads the signed-in member's current installation roles through +// the shared Authority core, including team grants and their live revocation. +func (s *HostInstallationServer) ListHeld(ctx context.Context, req *installationv1.ListHeldRequest) (*installationv1.ListHeldResponse, error) { + core, err := authorityCore() + if err != nil { + return nil, err + } + caller, err := signedInPerson(ctx, "list held installations") + if err != nil { + return nil, err + } + held, err := core.HeldInstallations(ctx, caller, req.GetOrganizationId()) + if err != nil { + return nil, coreStatus(err) + } + out := &installationv1.ListHeldResponse{} + for _, h := range held { + out.Installations = append(out.Installations, &installationv1.HeldInstallation{Installation: installationToWire(core, ctx, h.Installation), Roles: h.Roles}) + } + return out, nil +} + // installationToWire is an installation as the interface states it, with the // roles its solution declares. func installationToWire(core *authority.Service, ctx context.Context, i domain.Installation) *installationv1.Installation { diff --git a/module/services/accounts/code/pkg/adapters/impersonation_method_restriction_test.go b/module/services/accounts/code/pkg/adapters/impersonation_method_restriction_test.go index d113b16fe..99aea596b 100644 --- a/module/services/accounts/code/pkg/adapters/impersonation_method_restriction_test.go +++ b/module/services/accounts/code/pkg/adapters/impersonation_method_restriction_test.go @@ -99,7 +99,8 @@ func TestRestrictedProceduresAreDeniedOnlyWhileImpersonating(t *testing.T) { withoutCentralEnforcement(t) restricted := restrictedProcedures(t) - require.Len(t, restricted, 68, "the declared restriction set changed; confirm it against AUTHZ_MATRIX.md") // +11: Issue, Delegate, RevokeDelegation, GetDelegation, Install, Review, GrantRole, RevokeRole, ListRoleGrants, List, Current of the implemented interfaces; Check and ListResources are public, authenticated by the token they check + require.Contains(t, restricted, "/host.installation.v1.InstallationService/ListHeld") + require.Len(t, restricted, 69, "the declared restriction set changed; confirm it against AUTHZ_MATRIX.md") // +12: Issue, Delegate, RevokeDelegation, GetDelegation, Install, Review, GrantRole, RevokeRole, ListRoleGrants, List, ListHeld, Current of the implemented interfaces; Check and ListResources are public, authenticated by the token they check for _, procedure := range restricted { t.Run(procedure, func(t *testing.T) { diff --git a/module/services/accounts/code/pkg/business/interface_policies.go b/module/services/accounts/code/pkg/business/interface_policies.go index 377f715ed..fec1704b9 100644 --- a/module/services/accounts/code/pkg/business/interface_policies.go +++ b/module/services/accounts/code/pkg/business/interface_policies.go @@ -229,7 +229,9 @@ func InterfaceMethodPolicies() map[string]*policyv1.MethodPolicy { "/host.installation.v1.InstallationService/GrantRole": roleChange(EventAuthorityRoleGranted), "/host.installation.v1.InstallationService/RevokeRole": roleChange(EventAuthorityRoleRevoked), "/host.installation.v1.InstallationService/ListRoleGrants": read(policyv1.Exposure_EXPOSURE_AUTHENTICATED, policyv1.TenantRequirement_TENANT_REQUIREMENT_NONE, policyv1.Sensitivity_SENSITIVITY_CONFIDENTIAL), - "/host.installation.v1.InstallationService/List": read(policyv1.Exposure_EXPOSURE_AUTHENTICATED, policyv1.TenantRequirement_TENANT_REQUIREMENT_ORG_ADMIN, policyv1.Sensitivity_SENSITIVITY_CONFIDENTIAL), // an administrator's read; a member's view is the entitlement listing + "/host.installation.v1.InstallationService/List": read(policyv1.Exposure_EXPOSURE_AUTHENTICATED, policyv1.TenantRequirement_TENANT_REQUIREMENT_ORG_ADMIN, policyv1.Sensitivity_SENSITIVITY_CONFIDENTIAL), // an administrator's read; a member's own holdings are ListHeld + // The core restricts this read to the signed-in member's own holdings. + "/host.installation.v1.InstallationService/ListHeld": read(policyv1.Exposure_EXPOSURE_AUTHENTICATED, policyv1.TenantRequirement_TENANT_REQUIREMENT_NONE, policyv1.Sensitivity_SENSITIVITY_CONFIDENTIAL), // Current is the caller's own session: authenticated by the bearer it // describes, organisation-less like the session itself may be. "/host.session.v1.SessionService/Current": read(policyv1.Exposure_EXPOSURE_AUTHENTICATED, policyv1.TenantRequirement_TENANT_REQUIREMENT_NONE, policyv1.Sensitivity_SENSITIVITY_CONFIDENTIAL), @@ -314,6 +316,7 @@ var interfaceDescriptions = map[string]string{ "InstallationService/GrantRole": "Grant a person or a team one of an installed solution's roles.", "InstallationService/RevokeRole": "Revoke one role grant; its subject's capabilities through the installation end.", "InstallationService/ListRoleGrants": "List who holds which role on one installation.", + "InstallationService/ListHeld": "List only the signed-in member's current installation roles, including team grants.", "InstallationService/List": "List what one organisation has installed.", "AuthorityService/ListResources": "Answer which resources of one kind a checked capability covers for one action, from its owner's current grants.", "AuthorityService/Delegate": "Record a signed-in person's grant to a module, within what they hold.", diff --git a/module/services/accounts/code/pkg/cataloggen/authz_methods_test.go b/module/services/accounts/code/pkg/cataloggen/authz_methods_test.go index 40ee4bcc4..b3b80169a 100644 --- a/module/services/accounts/code/pkg/cataloggen/authz_methods_test.go +++ b/module/services/accounts/code/pkg/cataloggen/authz_methods_test.go @@ -184,5 +184,6 @@ func TestImpersonationRestrictionsMatchTheDescriptorsTheInterceptorReads(t *test } require.Equal(t, catalogued, enforced) - require.Len(t, enforced, 68) // 57 of the API package + Issue, Delegate, RevokeDelegation, GetDelegation, Install, Review, GrantRole, RevokeRole, ListRoleGrants, List, Current of the implemented interfaces; Check and ListResources are public, authenticated by the token they check + require.Contains(t, enforced, "/host.installation.v1.InstallationService/ListHeld") + require.Len(t, enforced, 69) // 57 of the API package + Issue, Delegate, RevokeDelegation, GetDelegation, Install, Review, GrantRole, RevokeRole, ListRoleGrants, List, ListHeld, Current of the implemented interfaces; Check and ListResources are public, authenticated by the token they check } diff --git a/module/services/accounts/code/pkg/gen/host/installation/v1/installation.pb.go b/module/services/accounts/code/pkg/gen/host/installation/v1/installation.pb.go index 9bc9cbdd4..d7a3b8b8d 100644 --- a/module/services/accounts/code/pkg/gen/host/installation/v1/installation.pb.go +++ b/module/services/accounts/code/pkg/gen/host/installation/v1/installation.pb.go @@ -1633,6 +1633,155 @@ func (x *OfferTool) GetWrite() bool { return false } +// ListHeldRequest names the organisation the person reads at: the one their +// session was opened at. +type ListHeldRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The organisation. + OrganizationId string `protobuf:"bytes,1,opt,name=organization_id,json=organizationId,proto3" json:"organization_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListHeldRequest) Reset() { + *x = ListHeldRequest{} + mi := &file_host_installation_v1_installation_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListHeldRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListHeldRequest) ProtoMessage() {} + +func (x *ListHeldRequest) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListHeldRequest.ProtoReflect.Descriptor instead. +func (*ListHeldRequest) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{24} +} + +func (x *ListHeldRequest) GetOrganizationId() string { + if x != nil { + return x.OrganizationId + } + return "" +} + +// ListHeldResponse is every installation the person holds a role on, sorted +// by installation id. +type ListHeldResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The installations. + Installations []*HeldInstallation `protobuf:"bytes,1,rep,name=installations,proto3" json:"installations,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListHeldResponse) Reset() { + *x = ListHeldResponse{} + mi := &file_host_installation_v1_installation_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListHeldResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListHeldResponse) ProtoMessage() {} + +func (x *ListHeldResponse) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListHeldResponse.ProtoReflect.Descriptor instead. +func (*ListHeldResponse) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{25} +} + +func (x *ListHeldResponse) GetInstallations() []*HeldInstallation { + if x != nil { + return x.Installations + } + return nil +} + +// HeldInstallation is one installation the person may use. +type HeldInstallation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The installation. + Installation *Installation `protobuf:"bytes,1,opt,name=installation,proto3" json:"installation,omitempty"` + // The roles the person holds on it, directly or through a team, sorted. + Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HeldInstallation) Reset() { + *x = HeldInstallation{} + mi := &file_host_installation_v1_installation_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HeldInstallation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeldInstallation) ProtoMessage() {} + +func (x *HeldInstallation) ProtoReflect() protoreflect.Message { + mi := &file_host_installation_v1_installation_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeldInstallation.ProtoReflect.Descriptor instead. +func (*HeldInstallation) Descriptor() ([]byte, []int) { + return file_host_installation_v1_installation_proto_rawDescGZIP(), []int{26} +} + +func (x *HeldInstallation) GetInstallation() *Installation { + if x != nil { + return x.Installation + } + return nil +} + +func (x *HeldInstallation) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + var File_host_installation_v1_installation_proto protoreflect.FileDescriptor const file_host_installation_v1_installation_proto_rawDesc = "" + @@ -1746,7 +1895,14 @@ const file_host_installation_v1_installation_proto_rawDesc = "" + "\tOfferTool\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + "\abinding\x18\x02 \x01(\tR\abinding\x12\x14\n" + - "\x05write\x18\x03 \x01(\bR\x05write2\x98\x05\n" + + "\x05write\x18\x03 \x01(\bR\x05write\":\n" + + "\x0fListHeldRequest\x12'\n" + + "\x0forganization_id\x18\x01 \x01(\tR\x0eorganizationId\"`\n" + + "\x10ListHeldResponse\x12L\n" + + "\rinstallations\x18\x01 \x03(\v2&.host.installation.v1.HeldInstallationR\rinstallations\"p\n" + + "\x10HeldInstallation\x12F\n" + + "\finstallation\x18\x01 \x01(\v2\".host.installation.v1.InstallationR\finstallation\x12\x14\n" + + "\x05roles\x18\x02 \x03(\tR\x05roles2\xf3\x05\n" + "\x13InstallationService\x12f\n" + "\aCurrent\x120.host.installation.v1.CurrentInstallationRequest\x1a).host.installation.v1.CurrentInstallation\x12S\n" + "\aInstall\x12$.host.installation.v1.InstallRequest\x1a\".host.installation.v1.Installation\x12T\n" + @@ -1755,7 +1911,8 @@ const file_host_installation_v1_installation_proto_rawDesc = "" + "\n" + "RevokeRole\x12'.host.installation.v1.RevokeRoleRequest\x1a\".host.installation.v1.Installation\x12k\n" + "\x0eListRoleGrants\x12+.host.installation.v1.ListRoleGrantsRequest\x1a,.host.installation.v1.ListRoleGrantsResponse\x12M\n" + - "\x04List\x12!.host.installation.v1.ListRequest\x1a\".host.installation.v1.ListResponse2p\n" + + "\x04List\x12!.host.installation.v1.ListRequest\x1a\".host.installation.v1.ListResponse\x12Y\n" + + "\bListHeld\x12%.host.installation.v1.ListHeldRequest\x1a&.host.installation.v1.ListHeldResponse2p\n" + "\x12InstallableService\x12Z\n" + "\aInstall\x12(.host.installation.v1.InstallationNotice\x1a%.host.installation.v1.InstallationAck2\xc8\x01\n" + "\fOfferService\x12_\n" + @@ -1777,7 +1934,7 @@ func file_host_installation_v1_installation_proto_rawDescGZIP() []byte { } var file_host_installation_v1_installation_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_host_installation_v1_installation_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_host_installation_v1_installation_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_host_installation_v1_installation_proto_goTypes = []any{ (Installation_Status)(0), // 0: host.installation.v1.Installation.Status (*CurrentInstallationRequest)(nil), // 1: host.installation.v1.CurrentInstallationRequest @@ -1804,6 +1961,9 @@ var file_host_installation_v1_installation_proto_goTypes = []any{ (*OfferScope)(nil), // 22: host.installation.v1.OfferScope (*OfferCapability)(nil), // 23: host.installation.v1.OfferCapability (*OfferTool)(nil), // 24: host.installation.v1.OfferTool + (*ListHeldRequest)(nil), // 25: host.installation.v1.ListHeldRequest + (*ListHeldResponse)(nil), // 26: host.installation.v1.ListHeldResponse + (*HeldInstallation)(nil), // 27: host.installation.v1.HeldInstallation } var file_host_installation_v1_installation_proto_depIdxs = []int32{ 5, // 0: host.installation.v1.InstallRequest.approval:type_name -> host.installation.v1.ConsentApproval @@ -1821,31 +1981,35 @@ var file_host_installation_v1_installation_proto_depIdxs = []int32{ 23, // 12: host.installation.v1.OfferRole.capabilities:type_name -> host.installation.v1.OfferCapability 22, // 13: host.installation.v1.OfferCapability.requires:type_name -> host.installation.v1.OfferScope 24, // 14: host.installation.v1.OfferCapability.tools:type_name -> host.installation.v1.OfferTool - 1, // 15: host.installation.v1.InstallationService.Current:input_type -> host.installation.v1.CurrentInstallationRequest - 3, // 16: host.installation.v1.InstallationService.Install:input_type -> host.installation.v1.InstallRequest - 4, // 17: host.installation.v1.InstallationService.Review:input_type -> host.installation.v1.ReviewRequest - 6, // 18: host.installation.v1.InstallationService.GrantRole:input_type -> host.installation.v1.GrantRoleRequest - 7, // 19: host.installation.v1.InstallationService.RevokeRole:input_type -> host.installation.v1.RevokeRoleRequest - 8, // 20: host.installation.v1.InstallationService.ListRoleGrants:input_type -> host.installation.v1.ListRoleGrantsRequest - 11, // 21: host.installation.v1.InstallationService.List:input_type -> host.installation.v1.ListRequest - 14, // 22: host.installation.v1.InstallableService.Install:input_type -> host.installation.v1.InstallationNotice - 16, // 23: host.installation.v1.OfferService.ListOffers:input_type -> host.installation.v1.ListOffersRequest - 4, // 24: host.installation.v1.OfferService.ReviewOffer:input_type -> host.installation.v1.ReviewRequest - 2, // 25: host.installation.v1.InstallationService.Current:output_type -> host.installation.v1.CurrentInstallation - 13, // 26: host.installation.v1.InstallationService.Install:output_type -> host.installation.v1.Installation - 5, // 27: host.installation.v1.InstallationService.Review:output_type -> host.installation.v1.ConsentApproval - 13, // 28: host.installation.v1.InstallationService.GrantRole:output_type -> host.installation.v1.Installation - 13, // 29: host.installation.v1.InstallationService.RevokeRole:output_type -> host.installation.v1.Installation - 9, // 30: host.installation.v1.InstallationService.ListRoleGrants:output_type -> host.installation.v1.ListRoleGrantsResponse - 12, // 31: host.installation.v1.InstallationService.List:output_type -> host.installation.v1.ListResponse - 15, // 32: host.installation.v1.InstallableService.Install:output_type -> host.installation.v1.InstallationAck - 17, // 33: host.installation.v1.OfferService.ListOffers:output_type -> host.installation.v1.ListOffersResponse - 19, // 34: host.installation.v1.OfferService.ReviewOffer:output_type -> host.installation.v1.ReviewedOffer - 25, // [25:35] is the sub-list for method output_type - 15, // [15:25] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 27, // 15: host.installation.v1.ListHeldResponse.installations:type_name -> host.installation.v1.HeldInstallation + 13, // 16: host.installation.v1.HeldInstallation.installation:type_name -> host.installation.v1.Installation + 1, // 17: host.installation.v1.InstallationService.Current:input_type -> host.installation.v1.CurrentInstallationRequest + 3, // 18: host.installation.v1.InstallationService.Install:input_type -> host.installation.v1.InstallRequest + 4, // 19: host.installation.v1.InstallationService.Review:input_type -> host.installation.v1.ReviewRequest + 6, // 20: host.installation.v1.InstallationService.GrantRole:input_type -> host.installation.v1.GrantRoleRequest + 7, // 21: host.installation.v1.InstallationService.RevokeRole:input_type -> host.installation.v1.RevokeRoleRequest + 8, // 22: host.installation.v1.InstallationService.ListRoleGrants:input_type -> host.installation.v1.ListRoleGrantsRequest + 11, // 23: host.installation.v1.InstallationService.List:input_type -> host.installation.v1.ListRequest + 25, // 24: host.installation.v1.InstallationService.ListHeld:input_type -> host.installation.v1.ListHeldRequest + 14, // 25: host.installation.v1.InstallableService.Install:input_type -> host.installation.v1.InstallationNotice + 16, // 26: host.installation.v1.OfferService.ListOffers:input_type -> host.installation.v1.ListOffersRequest + 4, // 27: host.installation.v1.OfferService.ReviewOffer:input_type -> host.installation.v1.ReviewRequest + 2, // 28: host.installation.v1.InstallationService.Current:output_type -> host.installation.v1.CurrentInstallation + 13, // 29: host.installation.v1.InstallationService.Install:output_type -> host.installation.v1.Installation + 5, // 30: host.installation.v1.InstallationService.Review:output_type -> host.installation.v1.ConsentApproval + 13, // 31: host.installation.v1.InstallationService.GrantRole:output_type -> host.installation.v1.Installation + 13, // 32: host.installation.v1.InstallationService.RevokeRole:output_type -> host.installation.v1.Installation + 9, // 33: host.installation.v1.InstallationService.ListRoleGrants:output_type -> host.installation.v1.ListRoleGrantsResponse + 12, // 34: host.installation.v1.InstallationService.List:output_type -> host.installation.v1.ListResponse + 26, // 35: host.installation.v1.InstallationService.ListHeld:output_type -> host.installation.v1.ListHeldResponse + 15, // 36: host.installation.v1.InstallableService.Install:output_type -> host.installation.v1.InstallationAck + 17, // 37: host.installation.v1.OfferService.ListOffers:output_type -> host.installation.v1.ListOffersResponse + 19, // 38: host.installation.v1.OfferService.ReviewOffer:output_type -> host.installation.v1.ReviewedOffer + 28, // [28:39] is the sub-list for method output_type + 17, // [17:28] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_host_installation_v1_installation_proto_init() } @@ -1859,7 +2023,7 @@ func file_host_installation_v1_installation_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_host_installation_v1_installation_proto_rawDesc), len(file_host_installation_v1_installation_proto_rawDesc)), NumEnums: 1, - NumMessages: 24, + NumMessages: 27, NumExtensions: 0, NumServices: 3, }, diff --git a/module/services/accounts/code/pkg/gen/host/installation/v1/installation_grpc.pb.go b/module/services/accounts/code/pkg/gen/host/installation/v1/installation_grpc.pb.go index 0fdefdf19..492488672 100644 --- a/module/services/accounts/code/pkg/gen/host/installation/v1/installation_grpc.pb.go +++ b/module/services/accounts/code/pkg/gen/host/installation/v1/installation_grpc.pb.go @@ -33,6 +33,7 @@ const ( InstallationService_RevokeRole_FullMethodName = "/host.installation.v1.InstallationService/RevokeRole" InstallationService_ListRoleGrants_FullMethodName = "/host.installation.v1.InstallationService/ListRoleGrants" InstallationService_List_FullMethodName = "/host.installation.v1.InstallationService/List" + InstallationService_ListHeld_FullMethodName = "/host.installation.v1.InstallationService/ListHeld" ) // InstallationServiceClient is the client API for InstallationService service. @@ -61,10 +62,16 @@ type InstallationServiceClient interface { // ListRoleGrants answers who holds which role on the installation; the same // callers as GrantRole. ListRoleGrants(ctx context.Context, in *ListRoleGrantsRequest, opts ...grpc.CallOption) (*ListRoleGrantsResponse, error) - // List answers what one organisation has installed: an administrator's read. A - // member's view, which installed solutions they may use, is their navigation and - // belongs to the entitlement read, not here. + // List answers what one organisation has installed: an administrator's read. + // A member's view, which installed solutions they may use, is ListHeld. List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) + // ListHeld answers the signed-in person which installations of their + // organisation they may use: each active installation on which they hold a + // role, granted to them or to a team of theirs, with the roles they hold. + // It is their navigation and answers their own holdings alone, never who + // else holds what. A member holding nothing reads an empty list; a caller + // acting at another organisation is refused naming it. + ListHeld(ctx context.Context, in *ListHeldRequest, opts ...grpc.CallOption) (*ListHeldResponse, error) } type installationServiceClient struct { @@ -145,6 +152,16 @@ func (c *installationServiceClient) List(ctx context.Context, in *ListRequest, o return out, nil } +func (c *installationServiceClient) ListHeld(ctx context.Context, in *ListHeldRequest, opts ...grpc.CallOption) (*ListHeldResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListHeldResponse) + err := c.cc.Invoke(ctx, InstallationService_ListHeld_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // InstallationServiceServer is the server API for InstallationService service. // All implementations must embed UnimplementedInstallationServiceServer // for forward compatibility. @@ -171,10 +188,16 @@ type InstallationServiceServer interface { // ListRoleGrants answers who holds which role on the installation; the same // callers as GrantRole. ListRoleGrants(context.Context, *ListRoleGrantsRequest) (*ListRoleGrantsResponse, error) - // List answers what one organisation has installed: an administrator's read. A - // member's view, which installed solutions they may use, is their navigation and - // belongs to the entitlement read, not here. + // List answers what one organisation has installed: an administrator's read. + // A member's view, which installed solutions they may use, is ListHeld. List(context.Context, *ListRequest) (*ListResponse, error) + // ListHeld answers the signed-in person which installations of their + // organisation they may use: each active installation on which they hold a + // role, granted to them or to a team of theirs, with the roles they hold. + // It is their navigation and answers their own holdings alone, never who + // else holds what. A member holding nothing reads an empty list; a caller + // acting at another organisation is refused naming it. + ListHeld(context.Context, *ListHeldRequest) (*ListHeldResponse, error) mustEmbedUnimplementedInstallationServiceServer() } @@ -206,6 +229,9 @@ func (UnimplementedInstallationServiceServer) ListRoleGrants(context.Context, *L func (UnimplementedInstallationServiceServer) List(context.Context, *ListRequest) (*ListResponse, error) { return nil, status.Error(codes.Unimplemented, "method List not implemented") } +func (UnimplementedInstallationServiceServer) ListHeld(context.Context, *ListHeldRequest) (*ListHeldResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListHeld not implemented") +} func (UnimplementedInstallationServiceServer) mustEmbedUnimplementedInstallationServiceServer() {} func (UnimplementedInstallationServiceServer) testEmbeddedByValue() {} @@ -353,6 +379,24 @@ func _InstallationService_List_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _InstallationService_ListHeld_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListHeldRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InstallationServiceServer).ListHeld(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: InstallationService_ListHeld_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InstallationServiceServer).ListHeld(ctx, req.(*ListHeldRequest)) + } + return interceptor(ctx, in, info, handler) +} + // InstallationService_ServiceDesc is the grpc.ServiceDesc for InstallationService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -388,6 +432,10 @@ var InstallationService_ServiceDesc = grpc.ServiceDesc{ MethodName: "List", Handler: _InstallationService_List_Handler, }, + { + MethodName: "ListHeld", + Handler: _InstallationService_ListHeld_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "host/installation/v1/installation.proto", diff --git a/module/services/accounts/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go b/module/services/accounts/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go index f5cb5ade7..42d72ee8e 100644 --- a/module/services/accounts/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go +++ b/module/services/accounts/code/pkg/gen/host/installation/v1/installationv1connect/installation.connect.go @@ -64,6 +64,9 @@ const ( // InstallationServiceListProcedure is the fully-qualified name of the InstallationService's List // RPC. InstallationServiceListProcedure = "/host.installation.v1.InstallationService/List" + // InstallationServiceListHeldProcedure is the fully-qualified name of the InstallationService's + // ListHeld RPC. + InstallationServiceListHeldProcedure = "/host.installation.v1.InstallationService/ListHeld" // InstallableServiceInstallProcedure is the fully-qualified name of the InstallableService's // Install RPC. InstallableServiceInstallProcedure = "/host.installation.v1.InstallableService/Install" @@ -96,10 +99,16 @@ type InstallationServiceClient interface { // ListRoleGrants answers who holds which role on the installation; the same // callers as GrantRole. ListRoleGrants(context.Context, *connect.Request[v1.ListRoleGrantsRequest]) (*connect.Response[v1.ListRoleGrantsResponse], error) - // List answers what one organisation has installed: an administrator's read. A - // member's view, which installed solutions they may use, is their navigation and - // belongs to the entitlement read, not here. + // List answers what one organisation has installed: an administrator's read. + // A member's view, which installed solutions they may use, is ListHeld. List(context.Context, *connect.Request[v1.ListRequest]) (*connect.Response[v1.ListResponse], error) + // ListHeld answers the signed-in person which installations of their + // organisation they may use: each active installation on which they hold a + // role, granted to them or to a team of theirs, with the roles they hold. + // It is their navigation and answers their own holdings alone, never who + // else holds what. A member holding nothing reads an empty list; a caller + // acting at another organisation is refused naming it. + ListHeld(context.Context, *connect.Request[v1.ListHeldRequest]) (*connect.Response[v1.ListHeldResponse], error) } // NewInstallationServiceClient constructs a client for the host.installation.v1.InstallationService @@ -155,6 +164,12 @@ func NewInstallationServiceClient(httpClient connect.HTTPClient, baseURL string, connect.WithSchema(installationServiceMethods.ByName("List")), connect.WithClientOptions(opts...), ), + listHeld: connect.NewClient[v1.ListHeldRequest, v1.ListHeldResponse]( + httpClient, + baseURL+InstallationServiceListHeldProcedure, + connect.WithSchema(installationServiceMethods.ByName("ListHeld")), + connect.WithClientOptions(opts...), + ), } } @@ -167,6 +182,7 @@ type installationServiceClient struct { revokeRole *connect.Client[v1.RevokeRoleRequest, v1.Installation] listRoleGrants *connect.Client[v1.ListRoleGrantsRequest, v1.ListRoleGrantsResponse] list *connect.Client[v1.ListRequest, v1.ListResponse] + listHeld *connect.Client[v1.ListHeldRequest, v1.ListHeldResponse] } // Current calls host.installation.v1.InstallationService.Current. @@ -204,6 +220,11 @@ func (c *installationServiceClient) List(ctx context.Context, req *connect.Reque return c.list.CallUnary(ctx, req) } +// ListHeld calls host.installation.v1.InstallationService.ListHeld. +func (c *installationServiceClient) ListHeld(ctx context.Context, req *connect.Request[v1.ListHeldRequest]) (*connect.Response[v1.ListHeldResponse], error) { + return c.listHeld.CallUnary(ctx, req) +} + // InstallationServiceHandler is an implementation of the host.installation.v1.InstallationService // service. type InstallationServiceHandler interface { @@ -227,10 +248,16 @@ type InstallationServiceHandler interface { // ListRoleGrants answers who holds which role on the installation; the same // callers as GrantRole. ListRoleGrants(context.Context, *connect.Request[v1.ListRoleGrantsRequest]) (*connect.Response[v1.ListRoleGrantsResponse], error) - // List answers what one organisation has installed: an administrator's read. A - // member's view, which installed solutions they may use, is their navigation and - // belongs to the entitlement read, not here. + // List answers what one organisation has installed: an administrator's read. + // A member's view, which installed solutions they may use, is ListHeld. List(context.Context, *connect.Request[v1.ListRequest]) (*connect.Response[v1.ListResponse], error) + // ListHeld answers the signed-in person which installations of their + // organisation they may use: each active installation on which they hold a + // role, granted to them or to a team of theirs, with the roles they hold. + // It is their navigation and answers their own holdings alone, never who + // else holds what. A member holding nothing reads an empty list; a caller + // acting at another organisation is refused naming it. + ListHeld(context.Context, *connect.Request[v1.ListHeldRequest]) (*connect.Response[v1.ListHeldResponse], error) } // NewInstallationServiceHandler builds an HTTP handler from the service implementation. It returns @@ -282,6 +309,12 @@ func NewInstallationServiceHandler(svc InstallationServiceHandler, opts ...conne connect.WithSchema(installationServiceMethods.ByName("List")), connect.WithHandlerOptions(opts...), ) + installationServiceListHeldHandler := connect.NewUnaryHandler( + InstallationServiceListHeldProcedure, + svc.ListHeld, + connect.WithSchema(installationServiceMethods.ByName("ListHeld")), + connect.WithHandlerOptions(opts...), + ) return "/host.installation.v1.InstallationService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case InstallationServiceCurrentProcedure: @@ -298,6 +331,8 @@ func NewInstallationServiceHandler(svc InstallationServiceHandler, opts ...conne installationServiceListRoleGrantsHandler.ServeHTTP(w, r) case InstallationServiceListProcedure: installationServiceListHandler.ServeHTTP(w, r) + case InstallationServiceListHeldProcedure: + installationServiceListHeldHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -335,6 +370,10 @@ func (UnimplementedInstallationServiceHandler) List(context.Context, *connect.Re return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.installation.v1.InstallationService.List is not implemented")) } +func (UnimplementedInstallationServiceHandler) ListHeld(context.Context, *connect.Request[v1.ListHeldRequest]) (*connect.Response[v1.ListHeldResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("host.installation.v1.InstallationService.ListHeld is not implemented")) +} + // InstallableServiceClient is a client for the host.installation.v1.InstallableService service. type InstallableServiceClient interface { // Install sets up the solution for one installation; it must converge. diff --git a/module/services/accounts/generated/api-contract-surfaces.json b/module/services/accounts/generated/api-contract-surfaces.json index 69272354e..e8f4a7823 100644 --- a/module/services/accounts/generated/api-contract-surfaces.json +++ b/module/services/accounts/generated/api-contract-surfaces.json @@ -88,6 +88,7 @@ "/host.installation.v1.InstallationService/GrantRole", "/host.installation.v1.InstallationService/Install", "/host.installation.v1.InstallationService/List", + "/host.installation.v1.InstallationService/ListHeld", "/host.installation.v1.InstallationService/ListRoleGrants", "/host.installation.v1.InstallationService/Review", "/host.installation.v1.InstallationService/RevokeRole" diff --git a/module/services/accounts/generated/authz-methods.json b/module/services/accounts/generated/authz-methods.json index 0fdfbb7d7..ba2429346 100644 --- a/module/services/accounts/generated/authz-methods.json +++ b/module/services/accounts/generated/authz-methods.json @@ -732,6 +732,43 @@ "policy_sha256": "b387739192840b24f771349cb2cded72d5f7eabd9d56c433b5b5937e6f4e58f0", "rate_limit_backend_failure_mode": "RATE_LIMIT_BACKEND_FAILURE_MODE_FAIL_OPEN" }, + { + "procedure": "/host.installation.v1.InstallationService/ListHeld", + "full_service": "host.installation.v1.InstallationService", + "method": "ListHeld", + "input_type": "host.installation.v1.ListHeldRequest", + "output_type": "host.installation.v1.ListHeldResponse", + "client_streaming": false, + "server_streaming": false, + "policy": { + "exposure": "EXPOSURE_AUTHENTICATED", + "tenant": "TENANT_REQUIREMENT_NONE", + "permissions": [], + "scopes": [], + "resource_bindings": [], + "mfa": "MFA_REQUIREMENT_NONE", + "audit": { + "events": [], + "emission": "AUDIT_EMISSION_NONE" + }, + "idempotency": "IDEMPOTENCY_REQUIREMENT_OPTIONAL", + "rate_limit": "RATE_LIMIT_CLASS_STANDARD_READ", + "request_sensitivity": "SENSITIVITY_CONFIDENTIAL", + "response_sensitivity": "SENSITIVITY_CONFIDENTIAL", + "platform_role": "PLATFORM_ROLE_REQUIREMENT_NONE", + "authentication_factor_attempt": false, + "conditions": [], + "impersonation": "IMPERSONATION_REQUIREMENT_FORBIDDEN" + }, + "policy_tier": "auth", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "source_proto": "host/installation/v1/installation.proto", + "policy_sha256": "79921072468c5f99274839371a7ac834d857df6e5f0708942165c4b31084de26", + "rate_limit_backend_failure_mode": "RATE_LIMIT_BACKEND_FAILURE_MODE_FAIL_OPEN" + }, { "procedure": "/host.installation.v1.InstallationService/ListRoleGrants", "full_service": "host.installation.v1.InstallationService", diff --git a/module/services/accounts/generated/gateway-routes.json b/module/services/accounts/generated/gateway-routes.json index faa25c69e..492d2c5e9 100644 --- a/module/services/accounts/generated/gateway-routes.json +++ b/module/services/accounts/generated/gateway-routes.json @@ -3681,6 +3681,22 @@ "rewrite_path": "", "remove_after": "" }, + { + "protocol": "GATEWAY_PROTOCOL_CONNECT", + "method": "POST", + "path": "/host.installation.v1.InstallationService/ListHeld", + "match": "GATEWAY_MATCH_EXACT", + "procedure": "/host.installation.v1.InstallationService/ListHeld", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "upstream_endpoint": "connect", + "exposure": "EXPOSURE_AUTHENTICATED", + "source": "GATEWAY_ROUTE_SOURCE_DESCRIPTOR", + "rewrite_path": "", + "remove_after": "" + }, { "protocol": "GATEWAY_PROTOCOL_CONNECT", "method": "POST", diff --git a/module/services/accounts/generated/service-catalog.json b/module/services/accounts/generated/service-catalog.json index f3faf12e5..6aa0ddf69 100644 --- a/module/services/accounts/generated/service-catalog.json +++ b/module/services/accounts/generated/service-catalog.json @@ -48,6 +48,7 @@ "/host.installation.v1.InstallationService/GrantRole", "/host.installation.v1.InstallationService/Install", "/host.installation.v1.InstallationService/List", + "/host.installation.v1.InstallationService/ListHeld", "/host.installation.v1.InstallationService/ListRoleGrants", "/host.installation.v1.InstallationService/Review", "/host.installation.v1.InstallationService/RevokeRole" @@ -1408,6 +1409,49 @@ "service": "accounts" } }, + { + "procedure": "/host.installation.v1.InstallationService/ListHeld", + "service": "InstallationService", + "full_service": "host.installation.v1.InstallationService", + "method": "ListHeld", + "input_type": "host.installation.v1.ListHeldRequest", + "output_type": "host.installation.v1.ListHeldResponse", + "client_streaming": false, + "server_streaming": false, + "deprecated": false, + "source_proto": "host/installation/v1/installation.proto", + "protocols": [ + "PROTOCOL_GRPC", + "PROTOCOL_CONNECT" + ], + "http_bindings": [], + "policy": { + "exposure": "EXPOSURE_AUTHENTICATED", + "tenant": "TENANT_REQUIREMENT_NONE", + "permissions": [], + "scopes": [], + "resource_bindings": [], + "mfa": "MFA_REQUIREMENT_NONE", + "audit": { + "events": [], + "emission": "AUDIT_EMISSION_NONE" + }, + "idempotency": "IDEMPOTENCY_REQUIREMENT_OPTIONAL", + "rate_limit": "RATE_LIMIT_CLASS_STANDARD_READ", + "request_sensitivity": "SENSITIVITY_CONFIDENTIAL", + "response_sensitivity": "SENSITIVITY_CONFIDENTIAL", + "platform_role": "PLATFORM_ROLE_REQUIREMENT_NONE", + "authentication_factor_attempt": false, + "conditions": [], + "impersonation": "IMPERSONATION_REQUIREMENT_FORBIDDEN" + }, + "policy_tier": "auth", + "description": "List only the signed-in member's current installation roles, including team grants.", + "owner": { + "module": "saas-starter", + "service": "accounts" + } + }, { "procedure": "/host.installation.v1.InstallationService/ListRoleGrants", "service": "InstallationService", diff --git a/module/services/accounts/proto/host/DIGESTS.sha256 b/module/services/accounts/proto/host/DIGESTS.sha256 index 1d31bafed..4fcaadff4 100644 --- a/module/services/accounts/proto/host/DIGESTS.sha256 +++ b/module/services/accounts/proto/host/DIGESTS.sha256 @@ -2,5 +2,5 @@ 5f743acb2e21e23eb916092313f54b0737742fe73c77d3f43208987a4aabf4c4 authority/v1/authority.proto f1ea4a9113527f8ed972660c3d2134a39135e96cf212b780ec04d14321ddcab1 authority/v1/work_context.proto 65353da82976f3f00d0a87792b93a723adbfc9deec48a5f7e337ff9526be53f5 composition/v1/composition.proto -d69c70528987ef59180b0ac683ac3c30de9b18077dacc3eece1342465953314e installation/v1/installation.proto +5de7bd8b5f68d5bdf081dab59352473a04c13c6bb5cecae0ec32beb6f5a45305 installation/v1/installation.proto 5d1e2f8fb2e7c3a7b8e027d8de23e26e53fa21212831e23fac492372ac53f28b session/v1/session.proto diff --git a/module/services/accounts/proto/host/installation/v1/installation.proto b/module/services/accounts/proto/host/installation/v1/installation.proto index c1ebefd92..f3f3f1397 100644 --- a/module/services/accounts/proto/host/installation/v1/installation.proto +++ b/module/services/accounts/proto/host/installation/v1/installation.proto @@ -30,10 +30,16 @@ service InstallationService { // ListRoleGrants answers who holds which role on the installation; the same // callers as GrantRole. rpc ListRoleGrants(ListRoleGrantsRequest) returns (ListRoleGrantsResponse); - // List answers what one organisation has installed: an administrator's read. A - // member's view, which installed solutions they may use, is their navigation and - // belongs to the entitlement read, not here. + // List answers what one organisation has installed: an administrator's read. + // A member's view, which installed solutions they may use, is ListHeld. rpc List(ListRequest) returns (ListResponse); + // ListHeld answers the signed-in person which installations of their + // organisation they may use: each active installation on which they hold a + // role, granted to them or to a team of theirs, with the roles they hold. + // It is their navigation and answers their own holdings alone, never who + // else holds what. A member holding nothing reads an empty list; a caller + // acting at another organisation is refused naming it. + rpc ListHeld(ListHeldRequest) returns (ListHeldResponse); } message CurrentInstallationRequest { @@ -309,3 +315,25 @@ message OfferTool { // True when the tool changes data. bool write = 3; } + +// ListHeldRequest names the organisation the person reads at: the one their +// session was opened at. +message ListHeldRequest { + // The organisation. + string organization_id = 1; +} + +// ListHeldResponse is every installation the person holds a role on, sorted +// by installation id. +message ListHeldResponse { + // The installations. + repeated HeldInstallation installations = 1; +} + +// HeldInstallation is one installation the person may use. +message HeldInstallation { + // The installation. + Installation installation = 1; + // The roles the person holds on it, directly or through a team, sorted. + repeated string roles = 2; +} diff --git a/module/services/auth-gateway/code/authz_catalog_gen.go b/module/services/auth-gateway/code/authz_catalog_gen.go index d7971ce6e..ca7cfda7c 100644 --- a/module/services/auth-gateway/code/authz_catalog_gen.go +++ b/module/services/auth-gateway/code/authz_catalog_gen.go @@ -30,6 +30,7 @@ var generatedAuthorizationByProcedure = map[string]generatedAuthorizationMetadat "/host.installation.v1.InstallationService/GrantRole": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "fc01eebe90648f5e096a7d4bbbd8f2aeb3d755bbd808c962352ed78936b12a4a"}, "/host.installation.v1.InstallationService/Install": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "9848918817951cb839354fccd74257a45731b1ff0ae86aaa3af89dadc0f32a1b"}, "/host.installation.v1.InstallationService/List": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardRead, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "b387739192840b24f771349cb2cded72d5f7eabd9d56c433b5b5937e6f4e58f0"}, + "/host.installation.v1.InstallationService/ListHeld": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardRead, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "79921072468c5f99274839371a7ac834d857df6e5f0708942165c4b31084de26"}, "/host.installation.v1.InstallationService/ListRoleGrants": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardRead, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "79921072468c5f99274839371a7ac834d857df6e5f0708942165c4b31084de26"}, "/host.installation.v1.InstallationService/Review": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardRead, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "b387739192840b24f771349cb2cded72d5f7eabd9d56c433b5b5937e6f4e58f0"}, "/host.installation.v1.InstallationService/RevokeRole": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "912f1c45666c1a2c3089918be4723973ca4fadf2dc78e5e80253d93499e74c92"}, diff --git a/module/services/auth-gateway/code/routing_catalog_gen.go b/module/services/auth-gateway/code/routing_catalog_gen.go index 7d41dc208..16b42d3dc 100644 --- a/module/services/auth-gateway/code/routing_catalog_gen.go +++ b/module/services/auth-gateway/code/routing_catalog_gen.go @@ -236,6 +236,7 @@ func generatedCatalogConnectRoutes() []*RouteEntry { {Service: "accounts_connect", Method: "POST", Path: "/host.installation.v1.InstallationService/GrantRole", UpstreamPath: "", Procedure: "/host.installation.v1.InstallationService/GrantRole"}, {Service: "accounts_connect", Method: "POST", Path: "/host.installation.v1.InstallationService/Install", UpstreamPath: "", Procedure: "/host.installation.v1.InstallationService/Install"}, {Service: "accounts_connect", Method: "POST", Path: "/host.installation.v1.InstallationService/List", UpstreamPath: "", Procedure: "/host.installation.v1.InstallationService/List"}, + {Service: "accounts_connect", Method: "POST", Path: "/host.installation.v1.InstallationService/ListHeld", UpstreamPath: "", Procedure: "/host.installation.v1.InstallationService/ListHeld"}, {Service: "accounts_connect", Method: "POST", Path: "/host.installation.v1.InstallationService/ListRoleGrants", UpstreamPath: "", Procedure: "/host.installation.v1.InstallationService/ListRoleGrants"}, {Service: "accounts_connect", Method: "POST", Path: "/host.installation.v1.InstallationService/Review", UpstreamPath: "", Procedure: "/host.installation.v1.InstallationService/Review"}, {Service: "accounts_connect", Method: "POST", Path: "/host.installation.v1.InstallationService/RevokeRole", UpstreamPath: "", Procedure: "/host.installation.v1.InstallationService/RevokeRole"}, diff --git a/module/services/frontend/code/package-lock.json b/module/services/frontend/code/package-lock.json index f843f8fbc..b979da7db 100644 --- a/module/services/frontend/code/package-lock.json +++ b/module/services/frontend/code/package-lock.json @@ -15,7 +15,7 @@ "@bufbuild/protobuf": "^2.11.0", "@codefly-dev/saas-plugin-contract": "2.4.4", "@codefly-dev/saas-plugin-react": "0.4.5", - "@codefly-dev/saas-sdk": "0.3.40", + "@codefly-dev/saas-sdk": "0.3.41", "@codefly-dev/saas-ui": "0.13.0", "@codefly-dev/ui": "0.13.0", "@codefly/saas-plugin-manifest": "0.1.2", @@ -15742,7 +15742,7 @@ }, "packages/saas-sdk": { "name": "@codefly-dev/saas-sdk", - "version": "0.3.40", + "version": "0.3.41", "license": "Apache-2.0", "dependencies": { "@bufbuild/protobuf": "^2.11.0", diff --git a/module/services/frontend/code/package.json b/module/services/frontend/code/package.json index 4dd0ffcb3..5c4fce115 100644 --- a/module/services/frontend/code/package.json +++ b/module/services/frontend/code/package.json @@ -39,7 +39,7 @@ "@bufbuild/protobuf": "^2.11.0", "@codefly-dev/saas-plugin-contract": "2.4.4", "@codefly-dev/saas-plugin-react": "0.4.5", - "@codefly-dev/saas-sdk": "0.3.40", + "@codefly-dev/saas-sdk": "0.3.41", "@codefly-dev/saas-ui": "0.13.0", "@codefly-dev/ui": "0.13.0", "@codefly/saas-plugin-manifest": "0.1.2", diff --git a/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml b/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml index 5cb12c298..b4a18ee20 100644 --- a/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml +++ b/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml @@ -12,7 +12,7 @@ sources: module: saas-starter service: accounts endpoint: connect - contract-digest: sha256:743b63bad9a546f105e994c754d845865e189465e6c72816e5785efe38ab7831 + contract-digest: sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b services: - AccessibleScopeService - AuditService diff --git a/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/installation/v1/installation_pb.ts b/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/installation/v1/installation_pb.ts index 3ef395d61..7349ddf76 100644 --- a/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/installation/v1/installation_pb.ts +++ b/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/installation/v1/installation_pb.ts @@ -16,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file host/installation/v1/installation.proto. */ export const file_host_installation_v1_installation: GenFile = /*@__PURE__*/ - fileDesc("Cidob3N0L2luc3RhbGxhdGlvbi92MS9pbnN0YWxsYXRpb24ucHJvdG8SFGhvc3QuaW5zdGFsbGF0aW9uLnYxIkwKGkN1cnJlbnRJbnN0YWxsYXRpb25SZXF1ZXN0EhQKDHBhcmVudF90b2tlbhgBIAEoCRIYChBhY3Rvcl9jcmVkZW50aWFsGAIgASgJIm8KE0N1cnJlbnRJbnN0YWxsYXRpb24SCgoCaWQYASABKAkSFwoPb3JnYW5pemF0aW9uX2lkGAIgASgJEg4KBnRhcmdldBgDIAEoCRIQCghyZXZpc2lvbhgEIAEoBBIRCgluYW1lc3BhY2UYBSABKAkimQEKDkluc3RhbGxSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIOCgZ0YXJnZXQYAiABKAkSDAoEcm9sZRgDIAEoCRIXCg9vd25lcl9wZXJzb25faWQYBCABKAkSNwoIYXBwcm92YWwYBSABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwiOAoNUmV2aWV3UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkSDgoGdGFyZ2V0GAIgASgJInkKD0NvbnNlbnRBcHByb3ZhbBIRCgl0YXJnZXRfaWQYASABKAkSEgoKZ2VuZXJhdGlvbhgCIAEoBBIZChFleHBlY3RlZF9yZXZpc2lvbhgDIAEoBBIUCgxvcGVyYXRpb25faWQYBCABKAkSDgoGZGlnZXN0GAUgASgJInYKEEdyYW50Um9sZVJlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCRIMCgRyb2xlGAMgASgJEhEKCXBlcnNvbl9pZBgEIAEoCRIPCgd0ZWFtX2lkGAUgASgJIncKEVJldm9rZVJvbGVSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIXCg9pbnN0YWxsYXRpb25faWQYAiABKAkSDAoEcm9sZRgDIAEoCRIRCglwZXJzb25faWQYBCABKAkSDwoHdGVhbV9pZBgFIAEoCSJJChVMaXN0Um9sZUdyYW50c1JlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCSJJChZMaXN0Um9sZUdyYW50c1Jlc3BvbnNlEi8KBmdyYW50cxgBIAMoCzIfLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJvbGVHcmFudCI9CglSb2xlR3JhbnQSDAoEcm9sZRgBIAEoCRIRCglwZXJzb25faWQYAiABKAkSDwoHdGVhbV9pZBgDIAEoCSImCgtMaXN0UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkiSQoMTGlzdFJlc3BvbnNlEjkKDWluc3RhbGxhdGlvbnMYASADKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24i3QIKDEluc3RhbGxhdGlvbhIKCgJpZBgBIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAiABKAkSDgoGdGFyZ2V0GAMgASgJEjkKBnN0YXR1cxgEIAEoDjIpLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbi5TdGF0dXMSEAoIcmV2aXNpb24YBSABKAQSDAoEcm9sZRgGIAEoCRIRCgluYW1lc3BhY2UYCSABKAkSFAoMcHJpbmNpcGFsX2lkGAogASgJEg0KBXJvbGVzGAsgAygJIlsKBlN0YXR1cxIWChJTVEFUVVNfVU5TUEVDSUZJRUQQABISCg5TVEFUVVNfUEVORElORxABEhEKDVNUQVRVU19BQ1RJVkUQAhISCg5TVEFUVVNfUkVWT0tFRBADSgQIBxAISgQICBAJUgh0ZWFtX2lkc1ISYWdlbnRfcHJpbmNpcGFsX2lkIk4KEkluc3RhbGxhdGlvbk5vdGljZRI4CgxpbnN0YWxsYXRpb24YASABKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24iEQoPSW5zdGFsbGF0aW9uQWNrIiwKEUxpc3RPZmZlcnNSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCSJBChJMaXN0T2ZmZXJzUmVzcG9uc2USKwoGb2ZmZXJzGAEgAygLMhsuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXIiKwoFT2ZmZXISDgoGdGFyZ2V0GAEgASgJEhIKCmdlbmVyYXRpb24YAiABKAQijwIKDVJldmlld2VkT2ZmZXISNwoIYXBwcm92YWwYASABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwSDgoGdGFyZ2V0GAIgASgJEi4KBXJvbGVzGAMgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJSb2xlEjgKDGluc3RhbGxhdGlvbhgEIAEoCzIiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhIzCgdjZWlsaW5nGAUgAygLMiIuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJCaW5kaW5nEhYKDnRvb2xzX2NvbXBvc2VkGAYgASgIImEKDE9mZmVyQmluZGluZxIPCgdiaW5kaW5nGAEgASgJEg4KBm1vZHVsZRgCIAEoCRIwCgZzY29wZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlIogBCglPZmZlclJvbGUSDAoEbmFtZRgBIAEoCRIwCgZzY29wZXMYAiADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEjsKDGNhcGFiaWxpdGllcxgDIAMoCzIlLmhvc3QuaW5zdGFsbGF0aW9uLnYxLk9mZmVyQ2FwYWJpbGl0eSJhCgpPZmZlclNjb3BlEhUKDXJlc291cmNlX2tpbmQYASABKAkSDwoHYWN0aW9ucxgCIAMoCRIVCg1hdHRhY2hlZF9vbmx5GAMgASgIEhQKDHJlc291cmNlX2lkcxgEIAMoCSKYAQoPT2ZmZXJDYXBhYmlsaXR5Eg4KBm1vZHVsZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSMgoIcmVxdWlyZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEi4KBXRvb2xzGAQgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJUb29sIjkKCU9mZmVyVG9vbBIMCgRuYW1lGAEgASgJEg8KB2JpbmRpbmcYAiABKAkSDQoFd3JpdGUYAyABKAgymAUKE0luc3RhbGxhdGlvblNlcnZpY2USZgoHQ3VycmVudBIwLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkN1cnJlbnRJbnN0YWxsYXRpb25SZXF1ZXN0GikuaG9zdC5pbnN0YWxsYXRpb24udjEuQ3VycmVudEluc3RhbGxhdGlvbhJTCgdJbnN0YWxsEiQuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbFJlcXVlc3QaIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24SVAoGUmV2aWV3EiMuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2aWV3UmVxdWVzdBolLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkNvbnNlbnRBcHByb3ZhbBJXCglHcmFudFJvbGUSJi5ob3N0Lmluc3RhbGxhdGlvbi52MS5HcmFudFJvbGVSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uElkKClJldm9rZVJvbGUSJy5ob3N0Lmluc3RhbGxhdGlvbi52MS5SZXZva2VSb2xlUmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhJrCg5MaXN0Um9sZUdyYW50cxIrLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSb2xlR3JhbnRzUmVxdWVzdBosLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSb2xlR3JhbnRzUmVzcG9uc2USTQoETGlzdBIhLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdFJlc3BvbnNlMnAKEkluc3RhbGxhYmxlU2VydmljZRJaCgdJbnN0YWxsEiguaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uTm90aWNlGiUuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uQWNrMsgBCgxPZmZlclNlcnZpY2USXwoKTGlzdE9mZmVycxInLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RPZmZlcnNSZXF1ZXN0GiguaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdE9mZmVyc1Jlc3BvbnNlElcKC1Jldmlld09mZmVyEiMuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2aWV3UmVxdWVzdBojLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJldmlld2VkT2ZmZXJCNVozZ2F0ZXdheS9wa2cvZ2VuL2hvc3QvaW5zdGFsbGF0aW9uL3YxO2luc3RhbGxhdGlvbnYxYgZwcm90bzM"); + fileDesc("Cidob3N0L2luc3RhbGxhdGlvbi92MS9pbnN0YWxsYXRpb24ucHJvdG8SFGhvc3QuaW5zdGFsbGF0aW9uLnYxIkwKGkN1cnJlbnRJbnN0YWxsYXRpb25SZXF1ZXN0EhQKDHBhcmVudF90b2tlbhgBIAEoCRIYChBhY3Rvcl9jcmVkZW50aWFsGAIgASgJIm8KE0N1cnJlbnRJbnN0YWxsYXRpb24SCgoCaWQYASABKAkSFwoPb3JnYW5pemF0aW9uX2lkGAIgASgJEg4KBnRhcmdldBgDIAEoCRIQCghyZXZpc2lvbhgEIAEoBBIRCgluYW1lc3BhY2UYBSABKAkimQEKDkluc3RhbGxSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIOCgZ0YXJnZXQYAiABKAkSDAoEcm9sZRgDIAEoCRIXCg9vd25lcl9wZXJzb25faWQYBCABKAkSNwoIYXBwcm92YWwYBSABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwiOAoNUmV2aWV3UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkSDgoGdGFyZ2V0GAIgASgJInkKD0NvbnNlbnRBcHByb3ZhbBIRCgl0YXJnZXRfaWQYASABKAkSEgoKZ2VuZXJhdGlvbhgCIAEoBBIZChFleHBlY3RlZF9yZXZpc2lvbhgDIAEoBBIUCgxvcGVyYXRpb25faWQYBCABKAkSDgoGZGlnZXN0GAUgASgJInYKEEdyYW50Um9sZVJlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCRIMCgRyb2xlGAMgASgJEhEKCXBlcnNvbl9pZBgEIAEoCRIPCgd0ZWFtX2lkGAUgASgJIncKEVJldm9rZVJvbGVSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIXCg9pbnN0YWxsYXRpb25faWQYAiABKAkSDAoEcm9sZRgDIAEoCRIRCglwZXJzb25faWQYBCABKAkSDwoHdGVhbV9pZBgFIAEoCSJJChVMaXN0Um9sZUdyYW50c1JlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCSJJChZMaXN0Um9sZUdyYW50c1Jlc3BvbnNlEi8KBmdyYW50cxgBIAMoCzIfLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJvbGVHcmFudCI9CglSb2xlR3JhbnQSDAoEcm9sZRgBIAEoCRIRCglwZXJzb25faWQYAiABKAkSDwoHdGVhbV9pZBgDIAEoCSImCgtMaXN0UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkiSQoMTGlzdFJlc3BvbnNlEjkKDWluc3RhbGxhdGlvbnMYASADKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24i3QIKDEluc3RhbGxhdGlvbhIKCgJpZBgBIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAiABKAkSDgoGdGFyZ2V0GAMgASgJEjkKBnN0YXR1cxgEIAEoDjIpLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbi5TdGF0dXMSEAoIcmV2aXNpb24YBSABKAQSDAoEcm9sZRgGIAEoCRIRCgluYW1lc3BhY2UYCSABKAkSFAoMcHJpbmNpcGFsX2lkGAogASgJEg0KBXJvbGVzGAsgAygJIlsKBlN0YXR1cxIWChJTVEFUVVNfVU5TUEVDSUZJRUQQABISCg5TVEFUVVNfUEVORElORxABEhEKDVNUQVRVU19BQ1RJVkUQAhISCg5TVEFUVVNfUkVWT0tFRBADSgQIBxAISgQICBAJUgh0ZWFtX2lkc1ISYWdlbnRfcHJpbmNpcGFsX2lkIk4KEkluc3RhbGxhdGlvbk5vdGljZRI4CgxpbnN0YWxsYXRpb24YASABKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24iEQoPSW5zdGFsbGF0aW9uQWNrIiwKEUxpc3RPZmZlcnNSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCSJBChJMaXN0T2ZmZXJzUmVzcG9uc2USKwoGb2ZmZXJzGAEgAygLMhsuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXIiKwoFT2ZmZXISDgoGdGFyZ2V0GAEgASgJEhIKCmdlbmVyYXRpb24YAiABKAQijwIKDVJldmlld2VkT2ZmZXISNwoIYXBwcm92YWwYASABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwSDgoGdGFyZ2V0GAIgASgJEi4KBXJvbGVzGAMgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJSb2xlEjgKDGluc3RhbGxhdGlvbhgEIAEoCzIiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhIzCgdjZWlsaW5nGAUgAygLMiIuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJCaW5kaW5nEhYKDnRvb2xzX2NvbXBvc2VkGAYgASgIImEKDE9mZmVyQmluZGluZxIPCgdiaW5kaW5nGAEgASgJEg4KBm1vZHVsZRgCIAEoCRIwCgZzY29wZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlIogBCglPZmZlclJvbGUSDAoEbmFtZRgBIAEoCRIwCgZzY29wZXMYAiADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEjsKDGNhcGFiaWxpdGllcxgDIAMoCzIlLmhvc3QuaW5zdGFsbGF0aW9uLnYxLk9mZmVyQ2FwYWJpbGl0eSJhCgpPZmZlclNjb3BlEhUKDXJlc291cmNlX2tpbmQYASABKAkSDwoHYWN0aW9ucxgCIAMoCRIVCg1hdHRhY2hlZF9vbmx5GAMgASgIEhQKDHJlc291cmNlX2lkcxgEIAMoCSKYAQoPT2ZmZXJDYXBhYmlsaXR5Eg4KBm1vZHVsZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSMgoIcmVxdWlyZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEi4KBXRvb2xzGAQgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJUb29sIjkKCU9mZmVyVG9vbBIMCgRuYW1lGAEgASgJEg8KB2JpbmRpbmcYAiABKAkSDQoFd3JpdGUYAyABKAgiKgoPTGlzdEhlbGRSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCSJRChBMaXN0SGVsZFJlc3BvbnNlEj0KDWluc3RhbGxhdGlvbnMYASADKAsyJi5ob3N0Lmluc3RhbGxhdGlvbi52MS5IZWxkSW5zdGFsbGF0aW9uIlsKEEhlbGRJbnN0YWxsYXRpb24SOAoMaW5zdGFsbGF0aW9uGAEgASgLMiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uEg0KBXJvbGVzGAIgAygJMvMFChNJbnN0YWxsYXRpb25TZXJ2aWNlEmYKB0N1cnJlbnQSMC5ob3N0Lmluc3RhbGxhdGlvbi52MS5DdXJyZW50SW5zdGFsbGF0aW9uUmVxdWVzdBopLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkN1cnJlbnRJbnN0YWxsYXRpb24SUwoHSW5zdGFsbBIkLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uElQKBlJldmlldxIjLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJldmlld1JlcXVlc3QaJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwSVwoJR3JhbnRSb2xlEiYuaG9zdC5pbnN0YWxsYXRpb24udjEuR3JhbnRSb2xlUmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhJZCgpSZXZva2VSb2xlEicuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2b2tlUm9sZVJlcXVlc3QaIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24SawoOTGlzdFJvbGVHcmFudHMSKy5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0Um9sZUdyYW50c1JlcXVlc3QaLC5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0Um9sZUdyYW50c1Jlc3BvbnNlEk0KBExpc3QSIS5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0UmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSZXNwb25zZRJZCghMaXN0SGVsZBIlLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RIZWxkUmVxdWVzdBomLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RIZWxkUmVzcG9uc2UycAoSSW5zdGFsbGFibGVTZXJ2aWNlEloKB0luc3RhbGwSKC5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25Ob3RpY2UaJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25BY2syyAEKDE9mZmVyU2VydmljZRJfCgpMaXN0T2ZmZXJzEicuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdE9mZmVyc1JlcXVlc3QaKC5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0T2ZmZXJzUmVzcG9uc2USVwoLUmV2aWV3T2ZmZXISIy5ob3N0Lmluc3RhbGxhdGlvbi52MS5SZXZpZXdSZXF1ZXN0GiMuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2aWV3ZWRPZmZlckI1WjNnYXRld2F5L3BrZy9nZW4vaG9zdC9pbnN0YWxsYXRpb24vdjE7aW5zdGFsbGF0aW9udjFiBnByb3RvMw"); /** * @generated from message host.installation.v1.CurrentInstallationRequest @@ -884,6 +884,78 @@ export type OfferTool = Message<"host.installation.v1.OfferTool"> & { export const OfferToolSchema: GenMessage = /*@__PURE__*/ messageDesc(file_host_installation_v1_installation, 23); +/** + * ListHeldRequest names the organisation the person reads at: the one their + * session was opened at. + * + * @generated from message host.installation.v1.ListHeldRequest + */ +export type ListHeldRequest = Message<"host.installation.v1.ListHeldRequest"> & { + /** + * The organisation. + * + * @generated from field: string organization_id = 1; + */ + organizationId: string; +}; + +/** + * Describes the message host.installation.v1.ListHeldRequest. + * Use `create(ListHeldRequestSchema)` to create a new message. + */ +export const ListHeldRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 24); + +/** + * ListHeldResponse is every installation the person holds a role on, sorted + * by installation id. + * + * @generated from message host.installation.v1.ListHeldResponse + */ +export type ListHeldResponse = Message<"host.installation.v1.ListHeldResponse"> & { + /** + * The installations. + * + * @generated from field: repeated host.installation.v1.HeldInstallation installations = 1; + */ + installations: HeldInstallation[]; +}; + +/** + * Describes the message host.installation.v1.ListHeldResponse. + * Use `create(ListHeldResponseSchema)` to create a new message. + */ +export const ListHeldResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 25); + +/** + * HeldInstallation is one installation the person may use. + * + * @generated from message host.installation.v1.HeldInstallation + */ +export type HeldInstallation = Message<"host.installation.v1.HeldInstallation"> & { + /** + * The installation. + * + * @generated from field: host.installation.v1.Installation installation = 1; + */ + installation?: Installation; + + /** + * The roles the person holds on it, directly or through a team, sorted. + * + * @generated from field: repeated string roles = 2; + */ + roles: string[]; +}; + +/** + * Describes the message host.installation.v1.HeldInstallation. + * Use `create(HeldInstallationSchema)` to create a new message. + */ +export const HeldInstallationSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 26); + /** * InstallationService is implemented by the host and by the local authority. * @@ -959,9 +1031,8 @@ export const InstallationService: GenService<{ output: typeof ListRoleGrantsResponseSchema; }, /** - * List answers what one organisation has installed: an administrator's read. A - * member's view, which installed solutions they may use, is their navigation and - * belongs to the entitlement read, not here. + * List answers what one organisation has installed: an administrator's read. + * A member's view, which installed solutions they may use, is ListHeld. * * @generated from rpc host.installation.v1.InstallationService.List */ @@ -970,6 +1041,21 @@ export const InstallationService: GenService<{ input: typeof ListRequestSchema; output: typeof ListResponseSchema; }, + /** + * ListHeld answers the signed-in person which installations of their + * organisation they may use: each active installation on which they hold a + * role, granted to them or to a team of theirs, with the roles they hold. + * It is their navigation and answers their own holdings alone, never who + * else holds what. A member holding nothing reads an empty list; a caller + * acting at another organisation is refused naming it. + * + * @generated from rpc host.installation.v1.InstallationService.ListHeld + */ + listHeld: { + methodKind: "unary"; + input: typeof ListHeldRequestSchema; + output: typeof ListHeldResponseSchema; + }, }> = /*@__PURE__*/ serviceDesc(file_host_installation_v1_installation, 0); diff --git a/module/services/frontend/code/packages/saas-sdk/package.json b/module/services/frontend/code/packages/saas-sdk/package.json index 1a723c22a..15472f8a5 100644 --- a/module/services/frontend/code/packages/saas-sdk/package.json +++ b/module/services/frontend/code/packages/saas-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@codefly-dev/saas-sdk", - "version": "0.3.40", + "version": "0.3.41", "description": "Typed Connect client, gateway-bound facades, and audit data-graph tooling for the saas-starter public API.", "repository": { "type": "git", diff --git a/module/services/frontend/code/src/gen/host/installation/v1/installation_pb.ts b/module/services/frontend/code/src/gen/host/installation/v1/installation_pb.ts index f2604fb4c..16e21e465 100644 --- a/module/services/frontend/code/src/gen/host/installation/v1/installation_pb.ts +++ b/module/services/frontend/code/src/gen/host/installation/v1/installation_pb.ts @@ -16,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file host/installation/v1/installation.proto. */ export const file_host_installation_v1_installation: GenFile = /*@__PURE__*/ - fileDesc("Cidob3N0L2luc3RhbGxhdGlvbi92MS9pbnN0YWxsYXRpb24ucHJvdG8SFGhvc3QuaW5zdGFsbGF0aW9uLnYxIkwKGkN1cnJlbnRJbnN0YWxsYXRpb25SZXF1ZXN0EhQKDHBhcmVudF90b2tlbhgBIAEoCRIYChBhY3Rvcl9jcmVkZW50aWFsGAIgASgJIm8KE0N1cnJlbnRJbnN0YWxsYXRpb24SCgoCaWQYASABKAkSFwoPb3JnYW5pemF0aW9uX2lkGAIgASgJEg4KBnRhcmdldBgDIAEoCRIQCghyZXZpc2lvbhgEIAEoBBIRCgluYW1lc3BhY2UYBSABKAkimQEKDkluc3RhbGxSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIOCgZ0YXJnZXQYAiABKAkSDAoEcm9sZRgDIAEoCRIXCg9vd25lcl9wZXJzb25faWQYBCABKAkSNwoIYXBwcm92YWwYBSABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwiOAoNUmV2aWV3UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkSDgoGdGFyZ2V0GAIgASgJInkKD0NvbnNlbnRBcHByb3ZhbBIRCgl0YXJnZXRfaWQYASABKAkSEgoKZ2VuZXJhdGlvbhgCIAEoBBIZChFleHBlY3RlZF9yZXZpc2lvbhgDIAEoBBIUCgxvcGVyYXRpb25faWQYBCABKAkSDgoGZGlnZXN0GAUgASgJInYKEEdyYW50Um9sZVJlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCRIMCgRyb2xlGAMgASgJEhEKCXBlcnNvbl9pZBgEIAEoCRIPCgd0ZWFtX2lkGAUgASgJIncKEVJldm9rZVJvbGVSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIXCg9pbnN0YWxsYXRpb25faWQYAiABKAkSDAoEcm9sZRgDIAEoCRIRCglwZXJzb25faWQYBCABKAkSDwoHdGVhbV9pZBgFIAEoCSJJChVMaXN0Um9sZUdyYW50c1JlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCSJJChZMaXN0Um9sZUdyYW50c1Jlc3BvbnNlEi8KBmdyYW50cxgBIAMoCzIfLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJvbGVHcmFudCI9CglSb2xlR3JhbnQSDAoEcm9sZRgBIAEoCRIRCglwZXJzb25faWQYAiABKAkSDwoHdGVhbV9pZBgDIAEoCSImCgtMaXN0UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkiSQoMTGlzdFJlc3BvbnNlEjkKDWluc3RhbGxhdGlvbnMYASADKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24i3QIKDEluc3RhbGxhdGlvbhIKCgJpZBgBIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAiABKAkSDgoGdGFyZ2V0GAMgASgJEjkKBnN0YXR1cxgEIAEoDjIpLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbi5TdGF0dXMSEAoIcmV2aXNpb24YBSABKAQSDAoEcm9sZRgGIAEoCRIRCgluYW1lc3BhY2UYCSABKAkSFAoMcHJpbmNpcGFsX2lkGAogASgJEg0KBXJvbGVzGAsgAygJIlsKBlN0YXR1cxIWChJTVEFUVVNfVU5TUEVDSUZJRUQQABISCg5TVEFUVVNfUEVORElORxABEhEKDVNUQVRVU19BQ1RJVkUQAhISCg5TVEFUVVNfUkVWT0tFRBADSgQIBxAISgQICBAJUgh0ZWFtX2lkc1ISYWdlbnRfcHJpbmNpcGFsX2lkIk4KEkluc3RhbGxhdGlvbk5vdGljZRI4CgxpbnN0YWxsYXRpb24YASABKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24iEQoPSW5zdGFsbGF0aW9uQWNrIiwKEUxpc3RPZmZlcnNSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCSJBChJMaXN0T2ZmZXJzUmVzcG9uc2USKwoGb2ZmZXJzGAEgAygLMhsuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXIiKwoFT2ZmZXISDgoGdGFyZ2V0GAEgASgJEhIKCmdlbmVyYXRpb24YAiABKAQijwIKDVJldmlld2VkT2ZmZXISNwoIYXBwcm92YWwYASABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwSDgoGdGFyZ2V0GAIgASgJEi4KBXJvbGVzGAMgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJSb2xlEjgKDGluc3RhbGxhdGlvbhgEIAEoCzIiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhIzCgdjZWlsaW5nGAUgAygLMiIuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJCaW5kaW5nEhYKDnRvb2xzX2NvbXBvc2VkGAYgASgIImEKDE9mZmVyQmluZGluZxIPCgdiaW5kaW5nGAEgASgJEg4KBm1vZHVsZRgCIAEoCRIwCgZzY29wZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlIogBCglPZmZlclJvbGUSDAoEbmFtZRgBIAEoCRIwCgZzY29wZXMYAiADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEjsKDGNhcGFiaWxpdGllcxgDIAMoCzIlLmhvc3QuaW5zdGFsbGF0aW9uLnYxLk9mZmVyQ2FwYWJpbGl0eSJhCgpPZmZlclNjb3BlEhUKDXJlc291cmNlX2tpbmQYASABKAkSDwoHYWN0aW9ucxgCIAMoCRIVCg1hdHRhY2hlZF9vbmx5GAMgASgIEhQKDHJlc291cmNlX2lkcxgEIAMoCSKYAQoPT2ZmZXJDYXBhYmlsaXR5Eg4KBm1vZHVsZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSMgoIcmVxdWlyZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEi4KBXRvb2xzGAQgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJUb29sIjkKCU9mZmVyVG9vbBIMCgRuYW1lGAEgASgJEg8KB2JpbmRpbmcYAiABKAkSDQoFd3JpdGUYAyABKAgymAUKE0luc3RhbGxhdGlvblNlcnZpY2USZgoHQ3VycmVudBIwLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkN1cnJlbnRJbnN0YWxsYXRpb25SZXF1ZXN0GikuaG9zdC5pbnN0YWxsYXRpb24udjEuQ3VycmVudEluc3RhbGxhdGlvbhJTCgdJbnN0YWxsEiQuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbFJlcXVlc3QaIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24SVAoGUmV2aWV3EiMuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2aWV3UmVxdWVzdBolLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkNvbnNlbnRBcHByb3ZhbBJXCglHcmFudFJvbGUSJi5ob3N0Lmluc3RhbGxhdGlvbi52MS5HcmFudFJvbGVSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uElkKClJldm9rZVJvbGUSJy5ob3N0Lmluc3RhbGxhdGlvbi52MS5SZXZva2VSb2xlUmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhJrCg5MaXN0Um9sZUdyYW50cxIrLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSb2xlR3JhbnRzUmVxdWVzdBosLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSb2xlR3JhbnRzUmVzcG9uc2USTQoETGlzdBIhLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdFJlc3BvbnNlMnAKEkluc3RhbGxhYmxlU2VydmljZRJaCgdJbnN0YWxsEiguaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uTm90aWNlGiUuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uQWNrMsgBCgxPZmZlclNlcnZpY2USXwoKTGlzdE9mZmVycxInLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RPZmZlcnNSZXF1ZXN0GiguaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdE9mZmVyc1Jlc3BvbnNlElcKC1Jldmlld09mZmVyEiMuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2aWV3UmVxdWVzdBojLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJldmlld2VkT2ZmZXJC1QEKGGNvbS5ob3N0Lmluc3RhbGxhdGlvbi52MUIRSW5zdGFsbGF0aW9uUHJvdG9QAVo0YWNjb3VudHMvcGtnL2dlbi9ob3N0L2luc3RhbGxhdGlvbi92MTtpbnN0YWxsYXRpb252MaICA0hJWKoCFEhvc3QuSW5zdGFsbGF0aW9uLlYxygIUSG9zdFxJbnN0YWxsYXRpb25cVjHiAiBIb3N0XEluc3RhbGxhdGlvblxWMVxHUEJNZXRhZGF0YeoCFkhvc3Q6Okluc3RhbGxhdGlvbjo6VjFiBnByb3RvMw"); + fileDesc("Cidob3N0L2luc3RhbGxhdGlvbi92MS9pbnN0YWxsYXRpb24ucHJvdG8SFGhvc3QuaW5zdGFsbGF0aW9uLnYxIkwKGkN1cnJlbnRJbnN0YWxsYXRpb25SZXF1ZXN0EhQKDHBhcmVudF90b2tlbhgBIAEoCRIYChBhY3Rvcl9jcmVkZW50aWFsGAIgASgJIm8KE0N1cnJlbnRJbnN0YWxsYXRpb24SCgoCaWQYASABKAkSFwoPb3JnYW5pemF0aW9uX2lkGAIgASgJEg4KBnRhcmdldBgDIAEoCRIQCghyZXZpc2lvbhgEIAEoBBIRCgluYW1lc3BhY2UYBSABKAkimQEKDkluc3RhbGxSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIOCgZ0YXJnZXQYAiABKAkSDAoEcm9sZRgDIAEoCRIXCg9vd25lcl9wZXJzb25faWQYBCABKAkSNwoIYXBwcm92YWwYBSABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwiOAoNUmV2aWV3UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkSDgoGdGFyZ2V0GAIgASgJInkKD0NvbnNlbnRBcHByb3ZhbBIRCgl0YXJnZXRfaWQYASABKAkSEgoKZ2VuZXJhdGlvbhgCIAEoBBIZChFleHBlY3RlZF9yZXZpc2lvbhgDIAEoBBIUCgxvcGVyYXRpb25faWQYBCABKAkSDgoGZGlnZXN0GAUgASgJInYKEEdyYW50Um9sZVJlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCRIMCgRyb2xlGAMgASgJEhEKCXBlcnNvbl9pZBgEIAEoCRIPCgd0ZWFtX2lkGAUgASgJIncKEVJldm9rZVJvbGVSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCRIXCg9pbnN0YWxsYXRpb25faWQYAiABKAkSDAoEcm9sZRgDIAEoCRIRCglwZXJzb25faWQYBCABKAkSDwoHdGVhbV9pZBgFIAEoCSJJChVMaXN0Um9sZUdyYW50c1JlcXVlc3QSFwoPb3JnYW5pemF0aW9uX2lkGAEgASgJEhcKD2luc3RhbGxhdGlvbl9pZBgCIAEoCSJJChZMaXN0Um9sZUdyYW50c1Jlc3BvbnNlEi8KBmdyYW50cxgBIAMoCzIfLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJvbGVHcmFudCI9CglSb2xlR3JhbnQSDAoEcm9sZRgBIAEoCRIRCglwZXJzb25faWQYAiABKAkSDwoHdGVhbV9pZBgDIAEoCSImCgtMaXN0UmVxdWVzdBIXCg9vcmdhbml6YXRpb25faWQYASABKAkiSQoMTGlzdFJlc3BvbnNlEjkKDWluc3RhbGxhdGlvbnMYASADKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24i3QIKDEluc3RhbGxhdGlvbhIKCgJpZBgBIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAiABKAkSDgoGdGFyZ2V0GAMgASgJEjkKBnN0YXR1cxgEIAEoDjIpLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbi5TdGF0dXMSEAoIcmV2aXNpb24YBSABKAQSDAoEcm9sZRgGIAEoCRIRCgluYW1lc3BhY2UYCSABKAkSFAoMcHJpbmNpcGFsX2lkGAogASgJEg0KBXJvbGVzGAsgAygJIlsKBlN0YXR1cxIWChJTVEFUVVNfVU5TUEVDSUZJRUQQABISCg5TVEFUVVNfUEVORElORxABEhEKDVNUQVRVU19BQ1RJVkUQAhISCg5TVEFUVVNfUkVWT0tFRBADSgQIBxAISgQICBAJUgh0ZWFtX2lkc1ISYWdlbnRfcHJpbmNpcGFsX2lkIk4KEkluc3RhbGxhdGlvbk5vdGljZRI4CgxpbnN0YWxsYXRpb24YASABKAsyIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24iEQoPSW5zdGFsbGF0aW9uQWNrIiwKEUxpc3RPZmZlcnNSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCSJBChJMaXN0T2ZmZXJzUmVzcG9uc2USKwoGb2ZmZXJzGAEgAygLMhsuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXIiKwoFT2ZmZXISDgoGdGFyZ2V0GAEgASgJEhIKCmdlbmVyYXRpb24YAiABKAQijwIKDVJldmlld2VkT2ZmZXISNwoIYXBwcm92YWwYASABKAsyJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwSDgoGdGFyZ2V0GAIgASgJEi4KBXJvbGVzGAMgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJSb2xlEjgKDGluc3RhbGxhdGlvbhgEIAEoCzIiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhIzCgdjZWlsaW5nGAUgAygLMiIuaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJCaW5kaW5nEhYKDnRvb2xzX2NvbXBvc2VkGAYgASgIImEKDE9mZmVyQmluZGluZxIPCgdiaW5kaW5nGAEgASgJEg4KBm1vZHVsZRgCIAEoCRIwCgZzY29wZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlIogBCglPZmZlclJvbGUSDAoEbmFtZRgBIAEoCRIwCgZzY29wZXMYAiADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEjsKDGNhcGFiaWxpdGllcxgDIAMoCzIlLmhvc3QuaW5zdGFsbGF0aW9uLnYxLk9mZmVyQ2FwYWJpbGl0eSJhCgpPZmZlclNjb3BlEhUKDXJlc291cmNlX2tpbmQYASABKAkSDwoHYWN0aW9ucxgCIAMoCRIVCg1hdHRhY2hlZF9vbmx5GAMgASgIEhQKDHJlc291cmNlX2lkcxgEIAMoCSKYAQoPT2ZmZXJDYXBhYmlsaXR5Eg4KBm1vZHVsZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSMgoIcmVxdWlyZXMYAyADKAsyIC5ob3N0Lmluc3RhbGxhdGlvbi52MS5PZmZlclNjb3BlEi4KBXRvb2xzGAQgAygLMh8uaG9zdC5pbnN0YWxsYXRpb24udjEuT2ZmZXJUb29sIjkKCU9mZmVyVG9vbBIMCgRuYW1lGAEgASgJEg8KB2JpbmRpbmcYAiABKAkSDQoFd3JpdGUYAyABKAgiKgoPTGlzdEhlbGRSZXF1ZXN0EhcKD29yZ2FuaXphdGlvbl9pZBgBIAEoCSJRChBMaXN0SGVsZFJlc3BvbnNlEj0KDWluc3RhbGxhdGlvbnMYASADKAsyJi5ob3N0Lmluc3RhbGxhdGlvbi52MS5IZWxkSW5zdGFsbGF0aW9uIlsKEEhlbGRJbnN0YWxsYXRpb24SOAoMaW5zdGFsbGF0aW9uGAEgASgLMiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uEg0KBXJvbGVzGAIgAygJMvMFChNJbnN0YWxsYXRpb25TZXJ2aWNlEmYKB0N1cnJlbnQSMC5ob3N0Lmluc3RhbGxhdGlvbi52MS5DdXJyZW50SW5zdGFsbGF0aW9uUmVxdWVzdBopLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkN1cnJlbnRJbnN0YWxsYXRpb24SUwoHSW5zdGFsbBIkLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxSZXF1ZXN0GiIuaG9zdC5pbnN0YWxsYXRpb24udjEuSW5zdGFsbGF0aW9uElQKBlJldmlldxIjLmhvc3QuaW5zdGFsbGF0aW9uLnYxLlJldmlld1JlcXVlc3QaJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5Db25zZW50QXBwcm92YWwSVwoJR3JhbnRSb2xlEiYuaG9zdC5pbnN0YWxsYXRpb24udjEuR3JhbnRSb2xlUmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkluc3RhbGxhdGlvbhJZCgpSZXZva2VSb2xlEicuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2b2tlUm9sZVJlcXVlc3QaIi5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb24SawoOTGlzdFJvbGVHcmFudHMSKy5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0Um9sZUdyYW50c1JlcXVlc3QaLC5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0Um9sZUdyYW50c1Jlc3BvbnNlEk0KBExpc3QSIS5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0UmVxdWVzdBoiLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RSZXNwb25zZRJZCghMaXN0SGVsZBIlLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RIZWxkUmVxdWVzdBomLmhvc3QuaW5zdGFsbGF0aW9uLnYxLkxpc3RIZWxkUmVzcG9uc2UycAoSSW5zdGFsbGFibGVTZXJ2aWNlEloKB0luc3RhbGwSKC5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25Ob3RpY2UaJS5ob3N0Lmluc3RhbGxhdGlvbi52MS5JbnN0YWxsYXRpb25BY2syyAEKDE9mZmVyU2VydmljZRJfCgpMaXN0T2ZmZXJzEicuaG9zdC5pbnN0YWxsYXRpb24udjEuTGlzdE9mZmVyc1JlcXVlc3QaKC5ob3N0Lmluc3RhbGxhdGlvbi52MS5MaXN0T2ZmZXJzUmVzcG9uc2USVwoLUmV2aWV3T2ZmZXISIy5ob3N0Lmluc3RhbGxhdGlvbi52MS5SZXZpZXdSZXF1ZXN0GiMuaG9zdC5pbnN0YWxsYXRpb24udjEuUmV2aWV3ZWRPZmZlckLVAQoYY29tLmhvc3QuaW5zdGFsbGF0aW9uLnYxQhFJbnN0YWxsYXRpb25Qcm90b1ABWjRhY2NvdW50cy9wa2cvZ2VuL2hvc3QvaW5zdGFsbGF0aW9uL3YxO2luc3RhbGxhdGlvbnYxogIDSElYqgIUSG9zdC5JbnN0YWxsYXRpb24uVjHKAhRIb3N0XEluc3RhbGxhdGlvblxWMeICIEhvc3RcSW5zdGFsbGF0aW9uXFYxXEdQQk1ldGFkYXRh6gIWSG9zdDo6SW5zdGFsbGF0aW9uOjpWMWIGcHJvdG8z"); /** * @generated from message host.installation.v1.CurrentInstallationRequest @@ -884,6 +884,78 @@ export type OfferTool = Message<"host.installation.v1.OfferTool"> & { export const OfferToolSchema: GenMessage = /*@__PURE__*/ messageDesc(file_host_installation_v1_installation, 23); +/** + * ListHeldRequest names the organisation the person reads at: the one their + * session was opened at. + * + * @generated from message host.installation.v1.ListHeldRequest + */ +export type ListHeldRequest = Message<"host.installation.v1.ListHeldRequest"> & { + /** + * The organisation. + * + * @generated from field: string organization_id = 1; + */ + organizationId: string; +}; + +/** + * Describes the message host.installation.v1.ListHeldRequest. + * Use `create(ListHeldRequestSchema)` to create a new message. + */ +export const ListHeldRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 24); + +/** + * ListHeldResponse is every installation the person holds a role on, sorted + * by installation id. + * + * @generated from message host.installation.v1.ListHeldResponse + */ +export type ListHeldResponse = Message<"host.installation.v1.ListHeldResponse"> & { + /** + * The installations. + * + * @generated from field: repeated host.installation.v1.HeldInstallation installations = 1; + */ + installations: HeldInstallation[]; +}; + +/** + * Describes the message host.installation.v1.ListHeldResponse. + * Use `create(ListHeldResponseSchema)` to create a new message. + */ +export const ListHeldResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 25); + +/** + * HeldInstallation is one installation the person may use. + * + * @generated from message host.installation.v1.HeldInstallation + */ +export type HeldInstallation = Message<"host.installation.v1.HeldInstallation"> & { + /** + * The installation. + * + * @generated from field: host.installation.v1.Installation installation = 1; + */ + installation?: Installation; + + /** + * The roles the person holds on it, directly or through a team, sorted. + * + * @generated from field: repeated string roles = 2; + */ + roles: string[]; +}; + +/** + * Describes the message host.installation.v1.HeldInstallation. + * Use `create(HeldInstallationSchema)` to create a new message. + */ +export const HeldInstallationSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_host_installation_v1_installation, 26); + /** * InstallationService is implemented by the host and by the local authority. * @@ -959,9 +1031,8 @@ export const InstallationService: GenService<{ output: typeof ListRoleGrantsResponseSchema; }, /** - * List answers what one organisation has installed: an administrator's read. A - * member's view, which installed solutions they may use, is their navigation and - * belongs to the entitlement read, not here. + * List answers what one organisation has installed: an administrator's read. + * A member's view, which installed solutions they may use, is ListHeld. * * @generated from rpc host.installation.v1.InstallationService.List */ @@ -970,6 +1041,21 @@ export const InstallationService: GenService<{ input: typeof ListRequestSchema; output: typeof ListResponseSchema; }, + /** + * ListHeld answers the signed-in person which installations of their + * organisation they may use: each active installation on which they hold a + * role, granted to them or to a team of theirs, with the roles they hold. + * It is their navigation and answers their own holdings alone, never who + * else holds what. A member holding nothing reads an empty list; a caller + * acting at another organisation is refused naming it. + * + * @generated from rpc host.installation.v1.InstallationService.ListHeld + */ + listHeld: { + methodKind: "unary"; + input: typeof ListHeldRequestSchema; + output: typeof ListHeldResponseSchema; + }, }> = /*@__PURE__*/ serviceDesc(file_host_installation_v1_installation, 0); From 711e33d6747829325c500dc2482fbb2e87d30626 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:33:04 +0000 Subject: [PATCH 06/11] fix(host): align fixture sessions with current Authority conformance --- .../source-read-sdk/library.codefly.yaml | 2 +- .../api/accounts/authority/contract.binpb | Bin 1303193 -> 1303448 bytes .../authority/proto/host/DIGESTS.sha256 | 2 +- .../proto/host/session/v1/session.proto | 3 +++ .../api/accounts/connect/contract.binpb | Bin 1303193 -> 1303448 bytes .../connect/proto/host/DIGESTS.sha256 | 2 +- .../proto/host/session/v1/session.proto | 3 +++ module/contracts/api/catalog.codefly.json | 4 ++-- module/module.package.codefly.yaml | 4 ++-- module/services/accounts/code/go.mod | 10 ++++----- module/services/accounts/code/go.sum | 20 ++++++++--------- .../accounts/code/pkg/adapters/auth.go | 2 +- .../code/pkg/adapters/host_interface.go | 5 +++++ .../host_interface_conformance_test.go | 10 +++++++++ .../pkg/gen/host/session/v1/session.pb.go | 21 +++++++++++++----- .../generated/openapi-raw/api.swagger.json | 4 ++++ .../accounts/proto/host/DIGESTS.sha256 | 2 +- .../proto/host/session/v1/session.proto | 3 +++ .../saas-sdk/generated/library.codefly.yaml | 2 +- .../src/gen/host/session/v1/session_pb.ts | 10 ++++++++- .../src/gen/host/session/v1/session_pb.ts | 10 ++++++++- 21 files changed, 87 insertions(+), 32 deletions(-) diff --git a/libraries/source-read-sdk/library.codefly.yaml b/libraries/source-read-sdk/library.codefly.yaml index c41a49ae5..8d8b419a5 100644 --- a/libraries/source-read-sdk/library.codefly.yaml +++ b/libraries/source-read-sdk/library.codefly.yaml @@ -12,7 +12,7 @@ sources: module: saas-starter service: accounts endpoint: authority - contract-digest: sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b + contract-digest: sha256:6eb1f3eb1ba262017ff4bdfafe047cba7b441855affd67f44285804bdda9e4d5 services: - ModuleCapabilitiesService generated-by: diff --git a/module/contracts/api/accounts/authority/contract.binpb b/module/contracts/api/accounts/authority/contract.binpb index 2de38bd1ba61a5ad3d36508530c569f8618caf17..39c17ac8b218182d768235e766f3ae41fc7689ef 100644 GIT binary patch delta 876 zcmX|<&r1|x9L9Iv`SI?&nmBzG`rMc zp>E!K^FOFl9fG|^5FWK3Cvi3Z0U?NPV$aOZba-H%c|Xtde&3gO@2mNC-+bAS8l)jN zl!n^SK2h_4?2n4d0sSmp2;-1`5!!b;EY81CZE(P=uUW41+zK4eopb!n4QuDV^~7%e zJfxRIzsxbsP*SPo=J5}@rAfu+gCkldFFKj35TdDue21jj8;L2Q4c%DwwZtE;+q_-u zN5x}4cTrm4md^vMtvb(wr*)hAoA$2&m2i!P3Zj)!uoAq)>jew3)wh!!i&wmz&wJnR%s%_jdH%k0oVqDXy_BbZ z8oZ*;8T+`XZ=cZ{_pMf+>D^%J3;ku{bx8(_JI?lC{wqBW-1WiJZ*+@=cO>JU^6L8Z zJKdeEhef%DFI@3W=cX-(f5Y5T5gDG z_=-23ZT(_ME9GWbFobV9TIkq72b}U+`sa}9M=dd933RlIbGdUda|?WAcsDb1(L}ZP zaHqPzb8xt~dqY|>I~2+~v~0~@?&ULvmNr^eO$*D+LR2v=dj}8h?>^F}=Txb+#e5!T zh__4S+<9DiF_Uu9#BmhsU+1*E9*<310>fivm-aFwD&=Q*O)o)u$)q!*lMA}Ok^E0{ zq!Sfc9_eJ^YVLfj-o@&_T@%loetAKEYF%O9)JLGJ?E3{kdI+=)AHe8OKPayCguPAx zzE`Q{0|0Zl>$XbPH!)Zk<^Bi(=P CU~}aF diff --git a/module/contracts/api/accounts/authority/proto/host/DIGESTS.sha256 b/module/contracts/api/accounts/authority/proto/host/DIGESTS.sha256 index 4fcaadff4..680171a92 100644 --- a/module/contracts/api/accounts/authority/proto/host/DIGESTS.sha256 +++ b/module/contracts/api/accounts/authority/proto/host/DIGESTS.sha256 @@ -3,4 +3,4 @@ f1ea4a9113527f8ed972660c3d2134a39135e96cf212b780ec04d14321ddcab1 authority/v1/work_context.proto 65353da82976f3f00d0a87792b93a723adbfc9deec48a5f7e337ff9526be53f5 composition/v1/composition.proto 5de7bd8b5f68d5bdf081dab59352473a04c13c6bb5cecae0ec32beb6f5a45305 installation/v1/installation.proto -5d1e2f8fb2e7c3a7b8e027d8de23e26e53fa21212831e23fac492372ac53f28b session/v1/session.proto +8754895a7f8ab7d69994b11cf88be17a575f19699f78975041e17e78008556b1 session/v1/session.proto diff --git a/module/contracts/api/accounts/authority/proto/host/session/v1/session.proto b/module/contracts/api/accounts/authority/proto/host/session/v1/session.proto index bf7894518..6b07d758b 100644 --- a/module/contracts/api/accounts/authority/proto/host/session/v1/session.proto +++ b/module/contracts/api/accounts/authority/proto/host/session/v1/session.proto @@ -54,6 +54,9 @@ message FixturePerson { repeated string team_ids = 5; // The token SignIn takes for them. string fixture_token = 6; + // The organisation's name, as the fixture spells it, for a page that shows + // a person where they sign into; the id above is the identity. + string organization_display_name = 7; } // CurrentRequest asks for the caller's own session; the bearer is the call's. diff --git a/module/contracts/api/accounts/connect/contract.binpb b/module/contracts/api/accounts/connect/contract.binpb index 2de38bd1ba61a5ad3d36508530c569f8618caf17..39c17ac8b218182d768235e766f3ae41fc7689ef 100644 GIT binary patch delta 876 zcmX|<&r1|x9L9Iv`SI?&nmBzG`rMc zp>E!K^FOFl9fG|^5FWK3Cvi3Z0U?NPV$aOZba-H%c|Xtde&3gO@2mNC-+bAS8l)jN zl!n^SK2h_4?2n4d0sSmp2;-1`5!!b;EY81CZE(P=uUW41+zK4eopb!n4QuDV^~7%e zJfxRIzsxbsP*SPo=J5}@rAfu+gCkldFFKj35TdDue21jj8;L2Q4c%DwwZtE;+q_-u zN5x}4cTrm4md^vMtvb(wr*)hAoA$2&m2i!P3Zj)!uoAq)>jew3)wh!!i&wmz&wJnR%s%_jdH%k0oVqDXy_BbZ z8oZ*;8T+`XZ=cZ{_pMf+>D^%J3;ku{bx8(_JI?lC{wqBW-1WiJZ*+@=cO>JU^6L8Z zJKdeEhef%DFI@3W=cX-(f5Y5T5gDG z_=-23ZT(_ME9GWbFobV9TIkq72b}U+`sa}9M=dd933RlIbGdUda|?WAcsDb1(L}ZP zaHqPzb8xt~dqY|>I~2+~v~0~@?&ULvmNr^eO$*D+LR2v=dj}8h?>^F}=Txb+#e5!T zh__4S+<9DiF_Uu9#BmhsU+1*E9*<310>fivm-aFwD&=Q*O)o)u$)q!*lMA}Ok^E0{ zq!Sfc9_eJ^YVLfj-o@&_T@%loetAKEYF%O9)JLGJ?E3{kdI+=)AHe8OKPayCguPAx zzE`Q{0|0Zl>$XbPH!)Zk<^Bi(=P CU~}aF diff --git a/module/contracts/api/accounts/connect/proto/host/DIGESTS.sha256 b/module/contracts/api/accounts/connect/proto/host/DIGESTS.sha256 index 4fcaadff4..680171a92 100644 --- a/module/contracts/api/accounts/connect/proto/host/DIGESTS.sha256 +++ b/module/contracts/api/accounts/connect/proto/host/DIGESTS.sha256 @@ -3,4 +3,4 @@ f1ea4a9113527f8ed972660c3d2134a39135e96cf212b780ec04d14321ddcab1 authority/v1/work_context.proto 65353da82976f3f00d0a87792b93a723adbfc9deec48a5f7e337ff9526be53f5 composition/v1/composition.proto 5de7bd8b5f68d5bdf081dab59352473a04c13c6bb5cecae0ec32beb6f5a45305 installation/v1/installation.proto -5d1e2f8fb2e7c3a7b8e027d8de23e26e53fa21212831e23fac492372ac53f28b session/v1/session.proto +8754895a7f8ab7d69994b11cf88be17a575f19699f78975041e17e78008556b1 session/v1/session.proto diff --git a/module/contracts/api/accounts/connect/proto/host/session/v1/session.proto b/module/contracts/api/accounts/connect/proto/host/session/v1/session.proto index bf7894518..6b07d758b 100644 --- a/module/contracts/api/accounts/connect/proto/host/session/v1/session.proto +++ b/module/contracts/api/accounts/connect/proto/host/session/v1/session.proto @@ -54,6 +54,9 @@ message FixturePerson { repeated string team_ids = 5; // The token SignIn takes for them. string fixture_token = 6; + // The organisation's name, as the fixture spells it, for a page that shows + // a person where they sign into; the id above is the identity. + string organization_display_name = 7; } // CurrentRequest asks for the caller's own session; the bearer is the call's. diff --git a/module/contracts/api/catalog.codefly.json b/module/contracts/api/catalog.codefly.json index ce862e1c6..5a3c82f5a 100644 --- a/module/contracts/api/catalog.codefly.json +++ b/module/contracts/api/catalog.codefly.json @@ -10,7 +10,7 @@ "kind": "protobuf", "package": "saas.accounts.v1", "path": "contracts/api/accounts/authority/contract.binpb", - "digest": "sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b", + "digest": "sha256:6eb1f3eb1ba262017ff4bdfafe047cba7b441855affd67f44285804bdda9e4d5", "services": [ { "name": "ModuleCapabilitiesService", @@ -66,7 +66,7 @@ "kind": "protobuf", "package": "", "path": "contracts/api/accounts/connect/contract.binpb", - "digest": "sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b", + "digest": "sha256:6eb1f3eb1ba262017ff4bdfafe047cba7b441855affd67f44285804bdda9e4d5", "services": [ { "name": "ArtifactService", diff --git a/module/module.package.codefly.yaml b/module/module.package.codefly.yaml index 08fd5562b..f705c3be2 100644 --- a/module/module.package.codefly.yaml +++ b/module/module.package.codefly.yaml @@ -18,12 +18,12 @@ services: kind: protobuf package: saas.accounts.v1 path: contracts/api/accounts/authority/contract.binpb - digest: sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b + digest: sha256:6eb1f3eb1ba262017ff4bdfafe047cba7b441855affd67f44285804bdda9e4d5 - endpoint: connect kind: protobuf package: "" path: contracts/api/accounts/connect/contract.binpb - digest: sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b + digest: sha256:6eb1f3eb1ba262017ff4bdfafe047cba7b441855affd67f44285804bdda9e4d5 - name: auth-gateway endpoints: [grpc, rest] api-contracts: diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index 9fc1f61c5..256381736 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -7,11 +7,11 @@ require ( buf.build/go/protovalidate v1.4.0 connectrpc.com/connect v1.21.0 connectrpc.com/otelconnect v0.9.0 - github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027160913-bfe4876a8b96 - github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027160913-bfe4876a8b96 - github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027160913-bfe4876a8b96 - github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027160913-bfe4876a8b96 - github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027160913-bfe4876a8b96 + github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261028150114-269005645a32 + github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261028150114-269005645a32 + github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261028150114-269005645a32 + github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261028150114-269005645a32 + github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261028150114-269005645a32 github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2 github.com/codefly-dev/interface-cache/go/cache v0.4.1 diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index a892604a7..4a370ec18 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -62,18 +62,18 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027160913-bfe4876a8b96 h1:N8Gb3M8Ep9Mrqlhs/3+J1R4DZSb+aO2XvOqKODIOsms= -github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg= -github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027160913-bfe4876a8b96 h1:F3Pluk6x4lw3sjuTWBpFBTWCx8d7QgLkxNiibNRLAVU= -github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:kTjeuNl03XSCkH3UhGKuHAKamkIzNsC2RPFu/g0fqSk= -github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027160913-bfe4876a8b96 h1:JAwG62/l+wluGw3hok6FFs0qUUjVnnoOH2AYFnjkUXA= -github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:or3dd9bqacXWMjke/n81l/QxRaLwq2lq/hL4V2qtAZo= -github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027160913-bfe4876a8b96 h1:gd28HWTEPfBU5z9UIw6GeeCo1nK0wEc268wOhccrIGg= -github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:XsOFnO2MQdr37vM3ULa0Uwi8ZWptyVx7hRSUyIdaZ1Q= +github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261028150114-269005645a32 h1:ndQ+PvrDgIdji2Z30u9tQh9StU/c6ZOkKMxCyToEKf4= +github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261028150114-269005645a32/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg= +github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261028150114-269005645a32 h1:sPE6yEqf9oeH5oGeMQuwaF+CILGXUds+orxMqo9Rk/I= +github.com/codefly-dev/authority/libraries/authority-core/go v0.0.0-20261028150114-269005645a32/go.mod h1:kTjeuNl03XSCkH3UhGKuHAKamkIzNsC2RPFu/g0fqSk= +github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261028150114-269005645a32 h1:H54hlos+kQHF0Afc7A+s0YpJJLNo/EVEHxP56s/SGZQ= +github.com/codefly-dev/authority/libraries/conformance/go v0.0.0-20261028150114-269005645a32/go.mod h1:or3dd9bqacXWMjke/n81l/QxRaLwq2lq/hL4V2qtAZo= +github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261028150114-269005645a32 h1:NrZGR1ZpSP7ueBNxOWh3otEBYRYl7AXtgm8on8TGWY8= +github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261028150114-269005645a32/go.mod h1:XsOFnO2MQdr37vM3ULa0Uwi8ZWptyVx7hRSUyIdaZ1Q= github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027063450-22b86ccbf2a5 h1:pHywzlszDDjNJIOg52YhmDCpW+7N4c/Ftma/qx5HxxE= github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027063450-22b86ccbf2a5/go.mod h1:9WF8dd98HYX9g/49uhrTgaTbWXo3hBvYzS3UVWYiZHw= -github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027160913-bfe4876a8b96 h1:Tf208FcTIlJHfjuhJJF/bshqRwye5aL5wflvbiR3Z3U= -github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261027160913-bfe4876a8b96/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As= +github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261028150114-269005645a32 h1:b9MmgqQE66C049nHg49hIcrz7IAsFcN9GfOjEEp1QVA= +github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261028150114-269005645a32/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As= github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc h1:olDnVJ1A1Retn0z9A59YMwDRmDLgkV2y1lbl+HzNrj8= github.com/codefly-dev/cli/contracts/deployment v0.0.0-20261006151622-5824cf5342fc/go.mod h1:VWPYY3FH4te7SzdN2xl9odNpWidJ1Grl/Hjs1SNoEF8= github.com/codefly-dev/core v0.19.7-0.20261026092055-b23dc13543b2 h1:I1wL0ANXhTdQuEAnxXF+s4+rKILlBu/QHILq4Skyi3I= diff --git a/module/services/accounts/code/pkg/adapters/auth.go b/module/services/accounts/code/pkg/adapters/auth.go index 1a998dd09..6bfe652f8 100644 --- a/module/services/accounts/code/pkg/adapters/auth.go +++ b/module/services/accounts/code/pkg/adapters/auth.go @@ -230,7 +230,7 @@ func requireOrgAdmin(ctx context.Context, actorID, orgID string) error { if role == gen.OrgRole_ORG_ROLE_ADMIN.String() || role == gen.OrgRole_ORG_ROLE_OWNER.String() { return nil } - return status.Error(codes.PermissionDenied, "requires org admin or owner role") + return status.Error(codes.PermissionDenied, "requires an organization administrator (admin or owner role)") } // subjectOutsideOrg is the one answer every way a subject can fail to belong diff --git a/module/services/accounts/code/pkg/adapters/host_interface.go b/module/services/accounts/code/pkg/adapters/host_interface.go index f2149fb49..dc2d29159 100644 --- a/module/services/accounts/code/pkg/adapters/host_interface.go +++ b/module/services/accounts/code/pkg/adapters/host_interface.go @@ -769,9 +769,14 @@ func (s *HostSessionServer) ListFixturePeople(ctx context.Context, _ *sessionv1. } out := &sessionv1.ListFixturePeopleResponse{} for _, p := range people { + _, organization, err := s.service.SessionDisplayNames(ctx, p.PersonID, p.OrganizationID) + if err != nil { + return nil, status.Errorf(codes.Unavailable, "cannot read fixture display names: %v", err) + } out.People = append(out.People, &sessionv1.FixturePerson{ PersonId: p.PersonID, Email: p.Email, OrganizationId: p.OrganizationID, Roles: p.Roles, TeamIds: p.TeamIDs, FixtureToken: p.FixtureToken, + OrganizationDisplayName: organization, }) } return out, nil diff --git a/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go b/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go index 0a928a1fe..77e22db79 100644 --- a/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go +++ b/module/services/accounts/code/pkg/adapters/host_interface_conformance_test.go @@ -68,6 +68,7 @@ const ( fixtureOrganization = "00000000-0000-7000-8000-0000000000c1" globexOrganization = "00000000-0000-7000-8000-0000000000c2" conformanceAlex = "00000000-0000-7000-8000-0000000000b3" + conformanceAdmin = "00000000-0000-7000-8000-0000000000a2" conformanceIvy = "00000000-0000-7000-8000-0000000000b9" conformanceZoe = "00000000-0000-7000-8000-0000000000b5" ) @@ -394,6 +395,15 @@ func runHostInterfaceFixture(t *testing.T) { // waits until the harness resumes it, and uninstalling revokes it. Lifecycle: &conformance.InstallationLifecycleCase{ Target: "conformance-lifecycle", Credential: credential("conformance-lifecycle"), Kind: "conformance-lifecycle.page", Team: finance, + SetAdministrator: func(ctx context.Context, administrator bool) error { + role := gen.OrgRole_ORG_ROLE_MEMBER + if administrator { + role = gen.OrgRole_ORG_ROLE_ADMIN + } + return svc.ConvergeFixtureOrgMember(ctx, &gen.AddOrgMemberRequest{ + OrgId: fixtureOrganization, UserId: conformanceAdmin, Role: role, + }) + }, PauseInstall: func() func() { release := make(chan struct{}) svc.SetAuthorityInstallStep(func(ctx context.Context, _ domain.Installation) error { diff --git a/module/services/accounts/code/pkg/gen/host/session/v1/session.pb.go b/module/services/accounts/code/pkg/gen/host/session/v1/session.pb.go index 7c83adf2d..311a88f70 100644 --- a/module/services/accounts/code/pkg/gen/host/session/v1/session.pb.go +++ b/module/services/accounts/code/pkg/gen/host/session/v1/session.pb.go @@ -125,9 +125,12 @@ type FixturePerson struct { // The teams they belong to. TeamIds []string `protobuf:"bytes,5,rep,name=team_ids,json=teamIds,proto3" json:"team_ids,omitempty"` // The token SignIn takes for them. - FixtureToken string `protobuf:"bytes,6,opt,name=fixture_token,json=fixtureToken,proto3" json:"fixture_token,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + FixtureToken string `protobuf:"bytes,6,opt,name=fixture_token,json=fixtureToken,proto3" json:"fixture_token,omitempty"` + // The organisation's name, as the fixture spells it, for a page that shows + // a person where they sign into; the id above is the identity. + OrganizationDisplayName string `protobuf:"bytes,7,opt,name=organization_display_name,json=organizationDisplayName,proto3" json:"organization_display_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FixturePerson) Reset() { @@ -202,6 +205,13 @@ func (x *FixturePerson) GetFixtureToken() string { return "" } +func (x *FixturePerson) GetOrganizationDisplayName() string { + if x != nil { + return x.OrganizationDisplayName + } + return "" +} + // CurrentRequest asks for the caller's own session; the bearer is the call's. type CurrentRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -403,14 +413,15 @@ const file_host_session_v1_session_proto_rawDesc = "" + "\x1dhost/session/v1/session.proto\x12\x0fhost.session.v1\x1a\x1cgoogle/api/annotations.proto\"\x1a\n" + "\x18ListFixturePeopleRequest\"S\n" + "\x19ListFixturePeopleResponse\x126\n" + - "\x06people\x18\x01 \x03(\v2\x1e.host.session.v1.FixturePersonR\x06people\"\xc1\x01\n" + + "\x06people\x18\x01 \x03(\v2\x1e.host.session.v1.FixturePersonR\x06people\"\xfd\x01\n" + "\rFixturePerson\x12\x1b\n" + "\tperson_id\x18\x01 \x01(\tR\bpersonId\x12\x14\n" + "\x05email\x18\x02 \x01(\tR\x05email\x12'\n" + "\x0forganization_id\x18\x03 \x01(\tR\x0eorganizationId\x12\x14\n" + "\x05roles\x18\x04 \x03(\tR\x05roles\x12\x19\n" + "\bteam_ids\x18\x05 \x03(\tR\ateamIds\x12#\n" + - "\rfixture_token\x18\x06 \x01(\tR\ffixtureToken\"\x10\n" + + "\rfixture_token\x18\x06 \x01(\tR\ffixtureToken\x12:\n" + + "\x19organization_display_name\x18\a \x01(\tR\x17organizationDisplayName\"\x10\n" + "\x0eCurrentRequest\"4\n" + "\rSignInRequest\x12#\n" + "\rfixture_token\x18\x01 \x01(\tR\ffixtureToken\"\xa3\x02\n" + diff --git a/module/services/accounts/generated/openapi-raw/api.swagger.json b/module/services/accounts/generated/openapi-raw/api.swagger.json index 64cd4108a..8c5fc81cd 100644 --- a/module/services/accounts/generated/openapi-raw/api.swagger.json +++ b/module/services/accounts/generated/openapi-raw/api.swagger.json @@ -8813,6 +8813,10 @@ "fixtureToken": { "type": "string", "description": "The token SignIn takes for them." + }, + "organizationDisplayName": { + "type": "string", + "description": "The organisation's name, as the fixture spells it, for a page that shows\na person where they sign into; the id above is the identity." } }, "description": "FixturePerson is one person a fixture run signs in." diff --git a/module/services/accounts/proto/host/DIGESTS.sha256 b/module/services/accounts/proto/host/DIGESTS.sha256 index 4fcaadff4..680171a92 100644 --- a/module/services/accounts/proto/host/DIGESTS.sha256 +++ b/module/services/accounts/proto/host/DIGESTS.sha256 @@ -3,4 +3,4 @@ f1ea4a9113527f8ed972660c3d2134a39135e96cf212b780ec04d14321ddcab1 authority/v1/work_context.proto 65353da82976f3f00d0a87792b93a723adbfc9deec48a5f7e337ff9526be53f5 composition/v1/composition.proto 5de7bd8b5f68d5bdf081dab59352473a04c13c6bb5cecae0ec32beb6f5a45305 installation/v1/installation.proto -5d1e2f8fb2e7c3a7b8e027d8de23e26e53fa21212831e23fac492372ac53f28b session/v1/session.proto +8754895a7f8ab7d69994b11cf88be17a575f19699f78975041e17e78008556b1 session/v1/session.proto diff --git a/module/services/accounts/proto/host/session/v1/session.proto b/module/services/accounts/proto/host/session/v1/session.proto index bf7894518..6b07d758b 100644 --- a/module/services/accounts/proto/host/session/v1/session.proto +++ b/module/services/accounts/proto/host/session/v1/session.proto @@ -54,6 +54,9 @@ message FixturePerson { repeated string team_ids = 5; // The token SignIn takes for them. string fixture_token = 6; + // The organisation's name, as the fixture spells it, for a page that shows + // a person where they sign into; the id above is the identity. + string organization_display_name = 7; } // CurrentRequest asks for the caller's own session; the bearer is the call's. diff --git a/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml b/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml index b4a18ee20..c84d70da3 100644 --- a/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml +++ b/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml @@ -12,7 +12,7 @@ sources: module: saas-starter service: accounts endpoint: connect - contract-digest: sha256:e9a558b41447ea3b3fa51fde2b0fbb7931cc25ea23dc1ef93616286d5631b00b + contract-digest: sha256:6eb1f3eb1ba262017ff4bdfafe047cba7b441855affd67f44285804bdda9e4d5 services: - AccessibleScopeService - AuditService diff --git a/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/session/v1/session_pb.ts b/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/session/v1/session_pb.ts index c71af3fa8..c1cdec3de 100644 --- a/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/session/v1/session_pb.ts +++ b/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/host/session/v1/session_pb.ts @@ -16,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file host/session/v1/session.proto. */ export const file_host_session_v1_session: GenFile = /*@__PURE__*/ - fileDesc("Ch1ob3N0L3Nlc3Npb24vdjEvc2Vzc2lvbi5wcm90bxIPaG9zdC5zZXNzaW9uLnYxIhoKGExpc3RGaXh0dXJlUGVvcGxlUmVxdWVzdCJLChlMaXN0Rml4dHVyZVBlb3BsZVJlc3BvbnNlEi4KBnBlb3BsZRgBIAMoCzIeLmhvc3Quc2Vzc2lvbi52MS5GaXh0dXJlUGVyc29uIoIBCg1GaXh0dXJlUGVyc29uEhEKCXBlcnNvbl9pZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAyABKAkSDQoFcm9sZXMYBCADKAkSEAoIdGVhbV9pZHMYBSADKAkSFQoNZml4dHVyZV90b2tlbhgGIAEoCSIQCg5DdXJyZW50UmVxdWVzdCImCg1TaWduSW5SZXF1ZXN0EhUKDWZpeHR1cmVfdG9rZW4YASABKAkiugEKB1Nlc3Npb24SEgoKc2Vzc2lvbl9pZBgBIAEoCRIRCglwZXJzb25faWQYAiABKAkSFwoPb3JnYW5pemF0aW9uX2lkGAMgASgJEg0KBXJvbGVzGAQgAygJEhAKCHRlYW1faWRzGAUgAygJEg4KBmJlYXJlchgGIAEoCRIbChNwZXJzb25fZGlzcGxheV9uYW1lGAcgASgJEiEKGW9yZ2FuaXphdGlvbl9kaXNwbGF5X25hbWUYCCABKAky4AIKDlNlc3Npb25TZXJ2aWNlEmIKBlNpZ25JbhIeLmhvc3Quc2Vzc2lvbi52MS5TaWduSW5SZXF1ZXN0GhguaG9zdC5zZXNzaW9uLnYxLlNlc3Npb24iHoLT5JMCGDoBKiITL3YxL3Nlc3Npb246c2lnbi1pbhJZCgdDdXJyZW50Eh8uaG9zdC5zZXNzaW9uLnYxLkN1cnJlbnRSZXF1ZXN0GhguaG9zdC5zZXNzaW9uLnYxLlNlc3Npb24iE4LT5JMCDRILL3YxL3Nlc3Npb24SjgEKEUxpc3RGaXh0dXJlUGVvcGxlEikuaG9zdC5zZXNzaW9uLnYxLkxpc3RGaXh0dXJlUGVvcGxlUmVxdWVzdBoqLmhvc3Quc2Vzc2lvbi52MS5MaXN0Rml4dHVyZVBlb3BsZVJlc3BvbnNlIiKC0+STAhwSGi92MS9zZXNzaW9uL2ZpeHR1cmUtcGVvcGxlQitaKWdhdGV3YXkvcGtnL2dlbi9ob3N0L3Nlc3Npb24vdjE7c2Vzc2lvbnYxYgZwcm90bzM", [file_google_api_annotations]); + fileDesc("Ch1ob3N0L3Nlc3Npb24vdjEvc2Vzc2lvbi5wcm90bxIPaG9zdC5zZXNzaW9uLnYxIhoKGExpc3RGaXh0dXJlUGVvcGxlUmVxdWVzdCJLChlMaXN0Rml4dHVyZVBlb3BsZVJlc3BvbnNlEi4KBnBlb3BsZRgBIAMoCzIeLmhvc3Quc2Vzc2lvbi52MS5GaXh0dXJlUGVyc29uIqUBCg1GaXh0dXJlUGVyc29uEhEKCXBlcnNvbl9pZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAyABKAkSDQoFcm9sZXMYBCADKAkSEAoIdGVhbV9pZHMYBSADKAkSFQoNZml4dHVyZV90b2tlbhgGIAEoCRIhChlvcmdhbml6YXRpb25fZGlzcGxheV9uYW1lGAcgASgJIhAKDkN1cnJlbnRSZXF1ZXN0IiYKDVNpZ25JblJlcXVlc3QSFQoNZml4dHVyZV90b2tlbhgBIAEoCSK6AQoHU2Vzc2lvbhISCgpzZXNzaW9uX2lkGAEgASgJEhEKCXBlcnNvbl9pZBgCIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAyABKAkSDQoFcm9sZXMYBCADKAkSEAoIdGVhbV9pZHMYBSADKAkSDgoGYmVhcmVyGAYgASgJEhsKE3BlcnNvbl9kaXNwbGF5X25hbWUYByABKAkSIQoZb3JnYW5pemF0aW9uX2Rpc3BsYXlfbmFtZRgIIAEoCTLgAgoOU2Vzc2lvblNlcnZpY2USYgoGU2lnbkluEh4uaG9zdC5zZXNzaW9uLnYxLlNpZ25JblJlcXVlc3QaGC5ob3N0LnNlc3Npb24udjEuU2Vzc2lvbiIegtPkkwIYOgEqIhMvdjEvc2Vzc2lvbjpzaWduLWluElkKB0N1cnJlbnQSHy5ob3N0LnNlc3Npb24udjEuQ3VycmVudFJlcXVlc3QaGC5ob3N0LnNlc3Npb24udjEuU2Vzc2lvbiITgtPkkwINEgsvdjEvc2Vzc2lvbhKOAQoRTGlzdEZpeHR1cmVQZW9wbGUSKS5ob3N0LnNlc3Npb24udjEuTGlzdEZpeHR1cmVQZW9wbGVSZXF1ZXN0GiouaG9zdC5zZXNzaW9uLnYxLkxpc3RGaXh0dXJlUGVvcGxlUmVzcG9uc2UiIoLT5JMCHBIaL3YxL3Nlc3Npb24vZml4dHVyZS1wZW9wbGVCK1opZ2F0ZXdheS9wa2cvZ2VuL2hvc3Qvc2Vzc2lvbi92MTtzZXNzaW9udjFiBnByb3RvMw", [file_google_api_annotations]); /** * ListFixturePeopleRequest asks for every fixture person. @@ -101,6 +101,14 @@ export type FixturePerson = Message<"host.session.v1.FixturePerson"> & { * @generated from field: string fixture_token = 6; */ fixtureToken: string; + + /** + * The organisation's name, as the fixture spells it, for a page that shows + * a person where they sign into; the id above is the identity. + * + * @generated from field: string organization_display_name = 7; + */ + organizationDisplayName: string; }; /** diff --git a/module/services/frontend/code/src/gen/host/session/v1/session_pb.ts b/module/services/frontend/code/src/gen/host/session/v1/session_pb.ts index 5ebfd8a55..1da433663 100644 --- a/module/services/frontend/code/src/gen/host/session/v1/session_pb.ts +++ b/module/services/frontend/code/src/gen/host/session/v1/session_pb.ts @@ -16,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file host/session/v1/session.proto. */ export const file_host_session_v1_session: GenFile = /*@__PURE__*/ - fileDesc("Ch1ob3N0L3Nlc3Npb24vdjEvc2Vzc2lvbi5wcm90bxIPaG9zdC5zZXNzaW9uLnYxIhoKGExpc3RGaXh0dXJlUGVvcGxlUmVxdWVzdCJLChlMaXN0Rml4dHVyZVBlb3BsZVJlc3BvbnNlEi4KBnBlb3BsZRgBIAMoCzIeLmhvc3Quc2Vzc2lvbi52MS5GaXh0dXJlUGVyc29uIoIBCg1GaXh0dXJlUGVyc29uEhEKCXBlcnNvbl9pZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAyABKAkSDQoFcm9sZXMYBCADKAkSEAoIdGVhbV9pZHMYBSADKAkSFQoNZml4dHVyZV90b2tlbhgGIAEoCSIQCg5DdXJyZW50UmVxdWVzdCImCg1TaWduSW5SZXF1ZXN0EhUKDWZpeHR1cmVfdG9rZW4YASABKAkiugEKB1Nlc3Npb24SEgoKc2Vzc2lvbl9pZBgBIAEoCRIRCglwZXJzb25faWQYAiABKAkSFwoPb3JnYW5pemF0aW9uX2lkGAMgASgJEg0KBXJvbGVzGAQgAygJEhAKCHRlYW1faWRzGAUgAygJEg4KBmJlYXJlchgGIAEoCRIbChNwZXJzb25fZGlzcGxheV9uYW1lGAcgASgJEiEKGW9yZ2FuaXphdGlvbl9kaXNwbGF5X25hbWUYCCABKAky4AIKDlNlc3Npb25TZXJ2aWNlEmIKBlNpZ25JbhIeLmhvc3Quc2Vzc2lvbi52MS5TaWduSW5SZXF1ZXN0GhguaG9zdC5zZXNzaW9uLnYxLlNlc3Npb24iHoLT5JMCGDoBKiITL3YxL3Nlc3Npb246c2lnbi1pbhJZCgdDdXJyZW50Eh8uaG9zdC5zZXNzaW9uLnYxLkN1cnJlbnRSZXF1ZXN0GhguaG9zdC5zZXNzaW9uLnYxLlNlc3Npb24iE4LT5JMCDRILL3YxL3Nlc3Npb24SjgEKEUxpc3RGaXh0dXJlUGVvcGxlEikuaG9zdC5zZXNzaW9uLnYxLkxpc3RGaXh0dXJlUGVvcGxlUmVxdWVzdBoqLmhvc3Quc2Vzc2lvbi52MS5MaXN0Rml4dHVyZVBlb3BsZVJlc3BvbnNlIiKC0+STAhwSGi92MS9zZXNzaW9uL2ZpeHR1cmUtcGVvcGxlQq0BChNjb20uaG9zdC5zZXNzaW9uLnYxQgxTZXNzaW9uUHJvdG9QAVoqYWNjb3VudHMvcGtnL2dlbi9ob3N0L3Nlc3Npb24vdjE7c2Vzc2lvbnYxogIDSFNYqgIPSG9zdC5TZXNzaW9uLlYxygIPSG9zdFxTZXNzaW9uXFYx4gIbSG9zdFxTZXNzaW9uXFYxXEdQQk1ldGFkYXRh6gIRSG9zdDo6U2Vzc2lvbjo6VjFiBnByb3RvMw", [file_google_api_annotations]); + fileDesc("Ch1ob3N0L3Nlc3Npb24vdjEvc2Vzc2lvbi5wcm90bxIPaG9zdC5zZXNzaW9uLnYxIhoKGExpc3RGaXh0dXJlUGVvcGxlUmVxdWVzdCJLChlMaXN0Rml4dHVyZVBlb3BsZVJlc3BvbnNlEi4KBnBlb3BsZRgBIAMoCzIeLmhvc3Quc2Vzc2lvbi52MS5GaXh0dXJlUGVyc29uIqUBCg1GaXh0dXJlUGVyc29uEhEKCXBlcnNvbl9pZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAyABKAkSDQoFcm9sZXMYBCADKAkSEAoIdGVhbV9pZHMYBSADKAkSFQoNZml4dHVyZV90b2tlbhgGIAEoCRIhChlvcmdhbml6YXRpb25fZGlzcGxheV9uYW1lGAcgASgJIhAKDkN1cnJlbnRSZXF1ZXN0IiYKDVNpZ25JblJlcXVlc3QSFQoNZml4dHVyZV90b2tlbhgBIAEoCSK6AQoHU2Vzc2lvbhISCgpzZXNzaW9uX2lkGAEgASgJEhEKCXBlcnNvbl9pZBgCIAEoCRIXCg9vcmdhbml6YXRpb25faWQYAyABKAkSDQoFcm9sZXMYBCADKAkSEAoIdGVhbV9pZHMYBSADKAkSDgoGYmVhcmVyGAYgASgJEhsKE3BlcnNvbl9kaXNwbGF5X25hbWUYByABKAkSIQoZb3JnYW5pemF0aW9uX2Rpc3BsYXlfbmFtZRgIIAEoCTLgAgoOU2Vzc2lvblNlcnZpY2USYgoGU2lnbkluEh4uaG9zdC5zZXNzaW9uLnYxLlNpZ25JblJlcXVlc3QaGC5ob3N0LnNlc3Npb24udjEuU2Vzc2lvbiIegtPkkwIYOgEqIhMvdjEvc2Vzc2lvbjpzaWduLWluElkKB0N1cnJlbnQSHy5ob3N0LnNlc3Npb24udjEuQ3VycmVudFJlcXVlc3QaGC5ob3N0LnNlc3Npb24udjEuU2Vzc2lvbiITgtPkkwINEgsvdjEvc2Vzc2lvbhKOAQoRTGlzdEZpeHR1cmVQZW9wbGUSKS5ob3N0LnNlc3Npb24udjEuTGlzdEZpeHR1cmVQZW9wbGVSZXF1ZXN0GiouaG9zdC5zZXNzaW9uLnYxLkxpc3RGaXh0dXJlUGVvcGxlUmVzcG9uc2UiIoLT5JMCHBIaL3YxL3Nlc3Npb24vZml4dHVyZS1wZW9wbGVCrQEKE2NvbS5ob3N0LnNlc3Npb24udjFCDFNlc3Npb25Qcm90b1ABWiphY2NvdW50cy9wa2cvZ2VuL2hvc3Qvc2Vzc2lvbi92MTtzZXNzaW9udjGiAgNIU1iqAg9Ib3N0LlNlc3Npb24uVjHKAg9Ib3N0XFNlc3Npb25cVjHiAhtIb3N0XFNlc3Npb25cVjFcR1BCTWV0YWRhdGHqAhFIb3N0OjpTZXNzaW9uOjpWMWIGcHJvdG8z", [file_google_api_annotations]); /** * ListFixturePeopleRequest asks for every fixture person. @@ -101,6 +101,14 @@ export type FixturePerson = Message<"host.session.v1.FixturePerson"> & { * @generated from field: string fixture_token = 6; */ fixtureToken: string; + + /** + * The organisation's name, as the fixture spells it, for a page that shows + * a person where they sign into; the id above is the identity. + * + * @generated from field: string organization_display_name = 7; + */ + organizationDisplayName: string; }; /** From dc873643435ff1bb2994f94cff40ee53c254695a Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:57:32 +0000 Subject: [PATCH 07/11] chore: collapse native protocol and catalog output in review --- .gitattributes | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitattributes b/.gitattributes index 61c3cadbd..bd5990190 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,11 @@ module/services/telemetry/code/pkg/gen/** linguist-generated=true # Build recipes emitted by the pinned Go service agent. module/services/accounts/builder/** linguist-generated=true module/services/auth-gateway/builder/** linguist-generated=true + +# Native protocol and catalog output; authored proto inputs and dependency files stay visible. +/module/services/accounts/code/pkg/gen/** linguist-generated=true +/module/services/accounts/generated/** linguist-generated=true +/module/services/frontend/code/src/gen/** linguist-generated=true +/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/** linguist-generated=true +/module/contracts/api/accounts/authority/proto/host/** linguist-generated=true +/module/contracts/api/accounts/connect/proto/host/** linguist-generated=true From 0b6c071090d957dcbb5288b8044d3767f6c6ef3d Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:38:43 +0000 Subject: [PATCH 08/11] fix(azure): follow registry-scoped frontend download redirects Reuse the Azure SDK blob reader and existing bundle verification. Admit only HTTPS data endpoints of the same registry, strip bearer credentials on redirects, and refuse foreign hosts and credentialed data requests. A regression test fails against the original transport and passes with this fix. --- module/frontendpayload/README.md | 5 ++ module/frontendpayload/azure.go | 47 +++++++++- module/frontendpayload/azure_redirect_test.go | 87 +++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 module/frontendpayload/azure_redirect_test.go diff --git a/module/frontendpayload/README.md b/module/frontendpayload/README.md index b5d258615..552bd096f 100644 --- a/module/frontendpayload/README.md +++ b/module/frontendpayload/README.md @@ -11,6 +11,11 @@ repository's own host (Artifact Registry answers every blob read that way); redirects elsewhere and foreign authentication realms are refused. HTTPS is explicit; plain HTTP requires the mesh Service assertion or a loopback development source. No registry is selected by default. +The Azure SDK reader also follows ACR's dedicated data-download redirect to +`..data.azurecr.io` over HTTPS. Only credential-free GETs +reach that endpoint; authentication and token exchanges remain on the registry +login host. The cell must admit that registry's regional data endpoint as well. + `DirectorySource` reads Codefly's existing native artifact cache, whose files are named by the SHA-256 hex digest. It never writes a cache and is refused when the host reports that it runs in a Pod. Neither adapter is an artifact store: diff --git a/module/frontendpayload/azure.go b/module/frontendpayload/azure.go index 5c18a4eda..e326f7063 100644 --- a/module/frontendpayload/azure.go +++ b/module/frontendpayload/azure.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "strings" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" @@ -38,7 +39,17 @@ func azureRegistrySource(ref registry.Reference, credential azcore.TokenCredenti client, err := azcontainerregistry.NewBlobClient("https://"+ref.Registry, credential, &azcontainerregistry.BlobClientOptions{ClientOptions: azcore.ClientOptions{ Transport: &http.Client{Timeout: 30 * time.Second, - Transport: repositoryTransport{host: ref.Registry, scheme: "https", base: base}}, + Transport: azureRegistryTransport{registry: ref.Registry, base: base}, + CheckRedirect: func(next *http.Request, via []*http.Request) error { + if len(via) > 3 || next.URL.Scheme != "https" || next.URL.User != nil || + (next.URL.Host != ref.Registry && !azureDataEndpoint(ref.Registry, next.URL.Host)) { + return errRedirectRefused + } + if next.URL.Host != ref.Registry { + next.Header.Del("Authorization") + } + return nil + }}, }}) if err != nil { return nil, &readerIdentityError{"could not configure the Azure frontend artifact reader"} @@ -46,6 +57,40 @@ func azureRegistrySource(ref registry.Reference, credential azcore.TokenCredenti return &azureWorkloadSource{client: client, repository: ref.Repository}, nil } +// Private-link ACR downloads redirect to this registry's regional data host. +// Neither arbitrary Azure storage hosts nor another registry are admitted. +func azureDataEndpoint(registry, host string) bool { + name, ok := strings.CutSuffix(registry, ".azurecr.io") + if !ok || strings.ContainsAny(name, ".:") { + return false + } + parts := strings.Split(host, ".") + if len(parts) != 5 || parts[0] != name || parts[2] != "data" || parts[3] != "azurecr" || parts[4] != "io" || parts[1] == "" { + return false + } + for _, ch := range parts[1] { + if !(ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9') { + return false + } + } + return true +} + +type azureRegistryTransport struct { + registry string + base http.RoundTripper +} + +func (t azureRegistryTransport) RoundTrip(r *http.Request) (*http.Response, error) { + if r.URL.Scheme != "https" || r.URL.User != nil { + return nil, errRedirectRefused + } + if r.URL.Host != t.registry && (!azureDataEndpoint(t.registry, r.URL.Host) || r.Method != http.MethodGet || r.Header.Get("Authorization") != "") { + return nil, errRedirectRefused + } + return t.base.RoundTrip(r) +} + func (s *azureWorkloadSource) Open(ctx context.Context, digest solutionhost.PayloadDigest) (io.ReadCloser, error) { response, err := s.client.GetBlob(ctx, s.repository, string(digest), nil) if err != nil { diff --git a/module/frontendpayload/azure_redirect_test.go b/module/frontendpayload/azure_redirect_test.go new file mode 100644 index 000000000..0ae4c6b83 --- /dev/null +++ b/module/frontendpayload/azure_redirect_test.go @@ -0,0 +1,87 @@ +package frontendpayload + +import ( + "context" + "encoding/base64" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "oras.land/oras-go/v2/registry" +) + +type azureRedirectRoundTripper func(*http.Request) (*http.Response, error) + +func (f azureRedirectRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + +func TestAzureBundleDownloadFollowsRegistryDataRedirectWithoutCredentials(t *testing.T) { + data := archiveFor(t, "azure-data-endpoint") + frontend := frontendFor(data) + digest := string(frontend.Payload.Digest) + refresh := "e30." + base64.RawStdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"exp":%d}`, time.Now().Add(time.Hour).Unix()))) + ".sig" + downloads := 0 + transport := azureRedirectRoundTripper(func(r *http.Request) (*http.Response, error) { + response := &http.Response{StatusCode: 200, Header: make(http.Header), Request: r, Body: io.NopCloser(strings.NewReader(""))} + switch { + case r.URL.Host == "example.eastus2.data.azurecr.io": + require.Equal(t, http.MethodGet, r.Method) + require.Empty(t, r.Header.Get("Authorization"), "a registry bearer never reaches the download endpoint") + downloads++ + response.Header.Set("Docker-Content-Digest", digest) + response.Header.Set("Content-Length", fmt.Sprint(len(data))) + response.Body = io.NopCloser(strings.NewReader(string(data))) + response.ContentLength = int64(len(data)) + case r.URL.Path == "/oauth2/exchange": + response.Body = io.NopCloser(strings.NewReader(fmt.Sprintf(`{"refresh_token":%q}`, refresh))) + case r.URL.Path == "/oauth2/token": + response.Body = io.NopCloser(strings.NewReader(`{"access_token":"registry-reader-token"}`)) + case r.Header.Get("Authorization") != "Bearer registry-reader-token": + response.StatusCode = 401 + response.Header.Set("WWW-Authenticate", `Bearer realm="https://example.azurecr.io/oauth2/token",service="example.azurecr.io",scope="repository:frontend-bundles:pull"`) + default: + response.StatusCode = 307 + response.Header.Set("Location", "https://example.eastus2.data.azurecr.io/download?sig=private-download-capability") + } + return response, nil + }) + ref, err := registry.ParseReference("example.azurecr.io/frontend-bundles") + require.NoError(t, err) + source, err := azureRegistrySource(ref, &azureTestCredential{}, transport) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second) + defer cancel() + bundle, err := Prepare(ctx, source, frontend) + require.NoError(t, err) + require.Equal(t, "azure-data-endpoint", readAsset(t, bundle, "deferred.js")) + require.Equal(t, 1, downloads) +} + +func TestAzureDownloadTransportRefusesOtherDestinationsAndCredentials(t *testing.T) { + for _, target := range []struct{ url, method, bearer string }{ + {"https://other.eastus2.data.azurecr.io/blob", "GET", ""}, + {"https://example.eastus2.data.azurecr.io.evil.example/blob", "GET", ""}, + {"https://example..data.azurecr.io/blob", "GET", ""}, + {"http://example.eastus2.data.azurecr.io/blob", "GET", ""}, + {"https://example.eastus2.data.azurecr.io:8443/blob", "GET", ""}, + {"https://example.eastus2.data.azurecr.io/blob", "POST", ""}, + {"https://example.eastus2.data.azurecr.io/blob", "GET", "reader-secret"}, + } { + t.Run(target.url+target.method+target.bearer, func(t *testing.T) { + calls := 0 + transport := azureRegistryTransport{registry: "example.azurecr.io", base: azureRedirectRoundTripper(func(*http.Request) (*http.Response, error) { calls++; return nil, nil })} + req, err := http.NewRequest(target.method, target.url, nil) + require.NoError(t, err) + if target.bearer != "" { + req.Header.Set("Authorization", "Bearer "+target.bearer) + } + _, err = transport.RoundTrip(req) + require.ErrorIs(t, err, errRedirectRefused) + require.Equal(t, 0, calls) + require.NotContains(t, err.Error(), "reader-secret") + }) + } +} From 5737fbb396270ac1d7d3b199ddd0c8e4872e7d41 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:49:46 +0000 Subject: [PATCH 09/11] dev: pin the Azure frontend reader redirect candidate in its consumers --- module/services/accounts/code/go.mod | 2 +- module/services/accounts/code/go.sum | 2 ++ module/services/auth-gateway/code/go.mod | 2 +- module/services/auth-gateway/code/go.sum | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index 256381736..ea156cd58 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -141,4 +141,4 @@ require ( replace github.com/codefly-dev/service-vault/keyservice => github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 // DEVELOPMENT CANDIDATE: frontend registry reader in PR #1234; adopt an owner release before promotion. -replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff +replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index 4a370ec18..3642caee7 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -98,6 +98,8 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff h1:dtG4SGf3deNecwpfQzpFlZOY9fwY86E/xOCVCgqs3BA= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf h1:5HLTb1dvcbrZkmVF8byhqqXvtrUsxoYKXHhS77fGC0c= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 h1:QhfPRUrZ8tp6XFg2/jXvx1fwtNqVHWYOkKeXkb7bZ14= github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7/go.mod h1:15o4NPE329wd93fpl/zFPlj+Dx7bGoQqrDT5+nO7Yso= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= diff --git a/module/services/auth-gateway/code/go.mod b/module/services/auth-gateway/code/go.mod index 798d76ec2..6d69273d4 100644 --- a/module/services/auth-gateway/code/go.mod +++ b/module/services/auth-gateway/code/go.mod @@ -117,4 +117,4 @@ require ( exclude google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 // DEVELOPMENT CANDIDATE: frontend registry reader in PR #1234; adopt an owner release before promotion. -replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff +replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf diff --git a/module/services/auth-gateway/code/go.sum b/module/services/auth-gateway/code/go.sum index 7b503bd1c..427ed2a19 100644 --- a/module/services/auth-gateway/code/go.sum +++ b/module/services/auth-gateway/code/go.sum @@ -70,6 +70,8 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff h1:dtG4SGf3deNecwpfQzpFlZOY9fwY86E/xOCVCgqs3BA= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf h1:5HLTb1dvcbrZkmVF8byhqqXvtrUsxoYKXHhS77fGC0c= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= From f1a7f1d242a724c5cbeebaf74511573f2cfc3423 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:14:34 +0000 Subject: [PATCH 10/11] fix(frontendpayload): verify redirected blobs without a registry digest header Azure storage redirects need not repeat registry-specific digest headers. Retain rejection of an explicitly mismatched header and the existing complete signed-byte digest and size verification. Extend the SDK redirect regression to omit the header and reject tampered payload bytes. --- module/frontendpayload/azure.go | 4 +++- module/frontendpayload/azure_redirect_test.go | 20 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/module/frontendpayload/azure.go b/module/frontendpayload/azure.go index e326f7063..398363d64 100644 --- a/module/frontendpayload/azure.go +++ b/module/frontendpayload/azure.go @@ -100,7 +100,9 @@ func (s *azureWorkloadSource) Open(ctx context.Context, digest solutionhost.Payl } return nil, unavailable(err) } - if response.DockerContentDigest == nil || *response.DockerContentDigest != string(digest) || + // A redirected storage response may omit the registry-specific digest header. + // Prepare verifies the downloaded bytes against the signed digest before serving. + if (response.DockerContentDigest != nil && *response.DockerContentDigest != string(digest)) || response.ContentLength == nil || *response.ContentLength < 0 || *response.ContentLength > MaxBundleBytes { response.BlobData.Close() return nil, errors.New("frontend artifact descriptor differs from the admitted bundle") diff --git a/module/frontendpayload/azure_redirect_test.go b/module/frontendpayload/azure_redirect_test.go index 0ae4c6b83..1648b74de 100644 --- a/module/frontendpayload/azure_redirect_test.go +++ b/module/frontendpayload/azure_redirect_test.go @@ -24,6 +24,8 @@ func TestAzureBundleDownloadFollowsRegistryDataRedirectWithoutCredentials(t *tes digest := string(frontend.Payload.Digest) refresh := "e30." + base64.RawStdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"exp":%d}`, time.Now().Add(time.Hour).Unix()))) + ".sig" downloads := 0 + digestHeader := true + tamper := false transport := azureRedirectRoundTripper(func(r *http.Request) (*http.Response, error) { response := &http.Response{StatusCode: 200, Header: make(http.Header), Request: r, Body: io.NopCloser(strings.NewReader(""))} switch { @@ -31,9 +33,15 @@ func TestAzureBundleDownloadFollowsRegistryDataRedirectWithoutCredentials(t *tes require.Equal(t, http.MethodGet, r.Method) require.Empty(t, r.Header.Get("Authorization"), "a registry bearer never reaches the download endpoint") downloads++ - response.Header.Set("Docker-Content-Digest", digest) + if digestHeader { + response.Header.Set("Docker-Content-Digest", digest) + } response.Header.Set("Content-Length", fmt.Sprint(len(data))) - response.Body = io.NopCloser(strings.NewReader(string(data))) + body := append([]byte(nil), data...) + if tamper { + body[len(body)-1] ^= 1 + } + response.Body = io.NopCloser(strings.NewReader(string(body))) response.ContentLength = int64(len(data)) case r.URL.Path == "/oauth2/exchange": response.Body = io.NopCloser(strings.NewReader(fmt.Sprintf(`{"refresh_token":%q}`, refresh))) @@ -58,6 +66,14 @@ func TestAzureBundleDownloadFollowsRegistryDataRedirectWithoutCredentials(t *tes require.NoError(t, err) require.Equal(t, "azure-data-endpoint", readAsset(t, bundle, "deferred.js")) require.Equal(t, 1, downloads) + digestHeader = false + bundle, err = Prepare(ctx, source, frontend) + require.NoError(t, err, "the blob storage redirect response need not repeat registry-specific digest headers") + require.Equal(t, "azure-data-endpoint", readAsset(t, bundle, "deferred.js")) + tamper = true + _, err = Prepare(ctx, source, frontend) + require.ErrorContains(t, err, "digest mismatch", "signed bytes remain verified without a registry digest header") + require.Equal(t, 3, downloads) } func TestAzureDownloadTransportRefusesOtherDestinationsAndCredentials(t *testing.T) { From 1e5f30a16e093a496c3e0a3b4fb29675d7761e11 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:15:06 +0000 Subject: [PATCH 11/11] dev: pin the complete Azure blob redirect reader in both consumers --- module/services/accounts/code/go.mod | 2 +- module/services/accounts/code/go.sum | 2 ++ module/services/auth-gateway/code/go.mod | 2 +- module/services/auth-gateway/code/go.sum | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/module/services/accounts/code/go.mod b/module/services/accounts/code/go.mod index ea156cd58..e6880e1e6 100644 --- a/module/services/accounts/code/go.mod +++ b/module/services/accounts/code/go.mod @@ -141,4 +141,4 @@ require ( replace github.com/codefly-dev/service-vault/keyservice => github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 // DEVELOPMENT CANDIDATE: frontend registry reader in PR #1234; adopt an owner release before promotion. -replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf +replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028181434-d67666bdc352 diff --git a/module/services/accounts/code/go.sum b/module/services/accounts/code/go.sum index 3642caee7..a20239f49 100644 --- a/module/services/accounts/code/go.sum +++ b/module/services/accounts/code/go.sum @@ -100,6 +100,8 @@ github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-202 github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf h1:5HLTb1dvcbrZkmVF8byhqqXvtrUsxoYKXHhS77fGC0c= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028181434-d67666bdc352 h1:TzMok5bj4nTOK1Vb/U/HVVM0MIgYagu3WuuGbG/xuAI= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028181434-d67666bdc352/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7 h1:QhfPRUrZ8tp6XFg2/jXvx1fwtNqVHWYOkKeXkb7bZ14= github.com/crisfiorini-oss/service-vault/keyservice v0.0.0-20261023210413-a6c7256bfae7/go.mod h1:15o4NPE329wd93fpl/zFPlj+Dx7bGoQqrDT5+nO7Yso= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= diff --git a/module/services/auth-gateway/code/go.mod b/module/services/auth-gateway/code/go.mod index 6d69273d4..0cf19a222 100644 --- a/module/services/auth-gateway/code/go.mod +++ b/module/services/auth-gateway/code/go.mod @@ -117,4 +117,4 @@ require ( exclude google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 // DEVELOPMENT CANDIDATE: frontend registry reader in PR #1234; adopt an owner release before promotion. -replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf +replace github.com/codefly-dev/module-saas-starter/module/frontendpayload => github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028181434-d67666bdc352 diff --git a/module/services/auth-gateway/code/go.sum b/module/services/auth-gateway/code/go.sum index 427ed2a19..0cfa1eff3 100644 --- a/module/services/auth-gateway/code/go.sum +++ b/module/services/auth-gateway/code/go.sum @@ -72,6 +72,8 @@ github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-202 github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261027005009-3e9f20a706ff/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf h1:5HLTb1dvcbrZkmVF8byhqqXvtrUsxoYKXHhS77fGC0c= github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028173843-c2485a8f50cf/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028181434-d67666bdc352 h1:TzMok5bj4nTOK1Vb/U/HVVM0MIgYagu3WuuGbG/xuAI= +github.com/crisfiorini-oss/module-saas-starter/module/frontendpayload v0.0.0-20261028181434-d67666bdc352/go.mod h1:k7Jens34+w8LAGA6J/eM9UMKjYewzYCMlIHX0ePPj6I= github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=