Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions internal/webhook/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const (
type InspectSession struct {
UserID string
TokenHash string
Token string
Name string
Description string
Priority string
Expand All @@ -46,18 +45,17 @@ func NewInspectStore() *InspectStore {
}

// Create generates a new inspect session for the given user, replacing any previous one.
func (s *InspectStore) Create(userID, name, description, priority string, topics []string) (*InspectSession, error) {
func (s *InspectStore) Create(userID, name, description, priority string, topics []string) (string, *InspectSession, error) {
rawToken, err := secure.NewInspectToken()
if err != nil {
return nil, err
return "", nil, err
}

tokenHash := secure.Hash(rawToken)

session := &InspectSession{
UserID: userID,
TokenHash: tokenHash,
Token: rawToken,
Name: name,
Description: description,
Priority: priority,
Expand All @@ -72,7 +70,7 @@ func (s *InspectStore) Create(userID, name, description, priority string, topics
s.cleanupLocked()
s.sessions[userID] = session

return session, nil
return rawToken, session, nil
}

// GetByUserID returns the inspect session for the given user, or nil if not found or expired.
Expand Down
6 changes: 3 additions & 3 deletions internal/webhook/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,14 +240,14 @@ func (s *Service) CreateInspectSession(ctx context.Context, userID, name, descri
return nil, ErrInvalidTopicSelection
}

session, err := s.inspectStore.Create(userID, name, description, priority, topics)
rawToken, session, err := s.inspectStore.Create(userID, name, description, priority, topics)
if err != nil {
return nil, err
}

return &InspectSessionResponse{
Token: session.Token,
URL: hookURL + "/" + session.Token,
Token: rawToken,
URL: hookURL + "/" + rawToken,
Status: session.Status,
ExpiresAt: session.ExpiresAt,
}, nil
Expand Down
67 changes: 67 additions & 0 deletions internal/webhook/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"lucor.dev/beebuzz/internal/auth"
"lucor.dev/beebuzz/internal/push"
"lucor.dev/beebuzz/internal/secure"
"lucor.dev/beebuzz/internal/testutil"
"lucor.dev/beebuzz/internal/topic"
)
Expand Down Expand Up @@ -207,6 +208,72 @@ func TestExtractPayload_UnsupportedType(t *testing.T) {
}
}

func TestInspectStoreCreateReturnsRawTokenWithoutPersistingIt(t *testing.T) {
store := NewInspectStore()

rawToken, session, err := store.Create("user-1", "inspect", "desc", push.PriorityNormal, []string{"topic-1"})
if err != nil {
t.Fatalf("Create() error = %v, want nil", err)
}
if rawToken == "" {
t.Fatal("Create() rawToken = empty, want token")
}
if session == nil {
t.Fatal("Create() session = nil, want session")
}
if session.TokenHash != secure.Hash(rawToken) {
t.Fatalf("Create() tokenHash = %q, want hash of raw token", session.TokenHash)
}

stored := store.GetByUserID("user-1")
if stored == nil {
t.Fatal("GetByUserID() = nil, want session")
}
if stored.TokenHash != secure.Hash(rawToken) {
t.Fatalf("GetByUserID() tokenHash = %q, want hash of raw token", stored.TokenHash)
}
}

func TestCreateInspectSessionReturnsRawTokenAndStoresOnlyHash(t *testing.T) {
db := testutil.NewDB(t)
ctx := context.Background()

authRepo := auth.NewRepository(db)
topicRepo := topic.NewRepository(db)
repo := NewRepository(db)
topicSvc := topic.NewService(topicRepo, slog.New(slog.NewTextHandler(io.Discard, nil)))
inspectStore := NewInspectStore()
svc := NewService(repo, inspectStore, noopDispatcher{}, topicSvc, slog.New(slog.NewTextHandler(io.Discard, nil)))

user, _, err := authRepo.GetOrCreateUser(ctx, "inspect-create@example.com")
if err != nil {
t.Fatalf("GetOrCreateUser: %v", err)
}
tp, err := topicRepo.Create(ctx, user.ID, "alerts", "")
if err != nil {
t.Fatalf("topic.Create: %v", err)
}

response, err := svc.CreateInspectSession(ctx, user.ID, "inspect", "desc", push.PriorityNormal, []string{tp.ID}, "https://hook.example.com")
if err != nil {
t.Fatalf("CreateInspectSession() error = %v, want nil", err)
}
if response.Token == "" {
t.Fatal("CreateInspectSession() token = empty, want token")
}
if response.URL != "https://hook.example.com/"+response.Token {
t.Fatalf("CreateInspectSession() url = %q, want token-based url", response.URL)
}

session := inspectStore.GetByUserID(user.ID)
if session == nil {
t.Fatal("GetByUserID() = nil, want session")
}
if session.TokenHash != secure.Hash(response.Token) {
t.Fatalf("stored tokenHash = %q, want hash of response token", session.TokenHash)
}
}

// noopDispatcher is a test adapter that accepts all dispatches without sending.
type noopDispatcher struct{}

Expand Down