mirror of https://github.com/usememos/memos
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.
74 lines
3.2 KiB
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")
|
|
}
|