You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
memos/server/router/api/v1/user_webhook_test.go

74 lines
3.2 KiB
Go

package v1
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
v1pb "github.com/usememos/memos/proto/gen/api/v1"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
// TestConvertUserWebhookFromUserSettingOmitsSigningSecret guards the core security
// invariant of the signing-secret feature: the secret is INPUT_ONLY and must never
// be copied into an API response, even though it is persisted in the user setting.
func TestConvertUserWebhookFromUserSettingOmitsSigningSecret(t *testing.T) {
user := &store.User{Username: "alice"}
stored := &storepb.WebhooksUserSetting_Webhook{
Id: "webhook-id",
Title: "My Webhook",
Url: "https://example.com/postreceive",
SigningSecret: "whsec_super-secret-value",
}
apiWebhook := convertUserWebhookFromUserSetting(stored, user)
require.Equal(t, "My Webhook", apiWebhook.DisplayName)
require.Equal(t, "https://example.com/postreceive", apiWebhook.Url)
require.Empty(t, apiWebhook.SigningSecret, "signing secret must never be returned in API responses")
require.True(t, apiWebhook.SigningSecretSet, "signing_secret_set must be true when secret is configured")
}
// TestUserWebhookSigningSecretLifecycle covers the reveal-later flow: a webhook
// created without a secret is auto-assigned one server-side (exposed only via the
// signing_secret_set flag), the owner can reveal the value, and a non-owner cannot.
func TestUserWebhookSigningSecretLifecycle(t *testing.T) {
svc := newIntegrationService(t)
ctx := context.Background()
// First user initializes the store as host/admin; the test actors are regular users.
_, err := svc.Store.CreateUser(ctx, &store.User{Username: "host", Role: store.RoleAdmin, Email: "host@example.com"})
require.NoError(t, err)
owner, err := svc.Store.CreateUser(ctx, &store.User{Username: "owner", Role: store.RoleUser, Email: "owner@example.com"})
require.NoError(t, err)
other, err := svc.Store.CreateUser(ctx, &store.User{Username: "other", Role: store.RoleUser, Email: "other@example.com"})
require.NoError(t, err)
ownerCtx := userCtx(ctx, owner.ID)
otherCtx := userCtx(ctx, other.ID)
// Create without supplying a secret -> the server generates one.
created, err := svc.CreateUserWebhook(ownerCtx, &v1pb.CreateUserWebhookRequest{
Parent: "users/owner",
Webhook: &v1pb.UserWebhook{DisplayName: "deploy", Url: "https://example.com/postreceive"},
})
require.NoError(t, err)
require.True(t, created.SigningSecretSet, "create must auto-generate a signing secret")
require.Empty(t, created.SigningSecret, "create response must never carry the secret value")
// The owner can reveal the generated value.
revealed, err := svc.GetUserWebhookSigningSecret(ownerCtx, &v1pb.GetUserWebhookSigningSecretRequest{Name: created.Name})
require.NoError(t, err)
require.True(t, strings.HasPrefix(revealed.SigningSecret, "whsec_"), "revealed secret must be in whsec_ form")
// A non-owner, non-admin user cannot reveal it.
_, err = svc.GetUserWebhookSigningSecret(otherCtx, &v1pb.GetUserWebhookSigningSecretRequest{Name: created.Name})
require.Error(t, err)
require.Equal(t, codes.PermissionDenied, status.Code(err), "non-owner must be denied")
}