Skip to content

Commit 1a61f2f

Browse files
authored
feat: add audit logs for billing account details update and kyc update (#971)
* feat: add audit logs for billing account details update and kyc update * fix: typo in error message * feat: add check to see ensure billing account belongs to specified org * fix: linting * fix: remove checking for billing account before updating it * refactor: remove unnecessary util function * fix: typo in error message
1 parent f3adf9f commit 1a61f2f

File tree

6 files changed

+138
-7
lines changed

6 files changed

+138
-7
lines changed

core/audit/audit.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const (
8080
OrgDisabledEvent EventName = "app.organization.disabled"
8181
OrgMemberCreatedEvent EventName = "app.organization.member.created"
8282
OrgMemberDeletedEvent EventName = "app.organization.member.deleted"
83+
OrgKycUpdatedEvent EventName = "app.organization.kyc.updated"
8384

8485
ProjectCreatedEvent EventName = "app.project.created"
8586
ProjectUpdatedEvent EventName = "app.project.updated"
@@ -88,6 +89,8 @@ const (
8889
ResourceCreatedEvent EventName = "app.resource.created"
8990
ResourceUpdatedEvent EventName = "app.resource.updated"
9091
ResourceDeletedEvent EventName = "app.resource.deleted"
92+
93+
BillingAccountDetailsUpdatedEvent EventName = "app.billing.account.details.updated"
9194
)
9295

9396
var systemEvents = []EventName{

internal/api/v1beta1/billing_customer.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"google.golang.org/grpc/status"
1313

1414
"github.com/raystack/frontier/billing/customer"
15+
"github.com/raystack/frontier/core/audit"
1516
"github.com/raystack/frontier/pkg/metadata"
1617
frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1"
1718
"google.golang.org/protobuf/types/known/timestamppb"
@@ -387,17 +388,27 @@ func (h Handler) UpdateBillingAccountDetails(ctx context.Context,
387388
request *frontierv1beta1.UpdateBillingAccountDetailsRequest) (*frontierv1beta1.UpdateBillingAccountDetailsResponse, error) {
388389
if request.GetDueInDays() < 0 {
389390
return nil, status.Errorf(codes.FailedPrecondition,
390-
"cannot create predated invoices: due in days shoule be greated than 0")
391+
"cannot create predated invoices: due in days should be greater than 0")
391392
}
392393

393-
_, err := h.customerService.UpdateDetails(ctx, request.GetId(), customer.Details{
394+
details, err := h.customerService.UpdateDetails(ctx, request.GetId(), customer.Details{
394395
CreditMin: request.GetCreditMin(),
395396
DueInDays: request.GetDueInDays(),
396397
})
397398
if err != nil {
398399
return nil, err
399400
}
400401

402+
// Add audit log
403+
audit.GetAuditor(ctx, request.GetOrgId()).
404+
LogWithAttrs(audit.BillingAccountDetailsUpdatedEvent, audit.Target{
405+
ID: request.GetId(),
406+
Type: "billing_account",
407+
}, map[string]string{
408+
"credit_min": fmt.Sprintf("%d", details.CreditMin),
409+
"due_in_days": fmt.Sprintf("%d", details.DueInDays),
410+
})
411+
401412
return &frontierv1beta1.UpdateBillingAccountDetailsResponse{}, nil
402413
}
403414

internal/api/v1beta1/billing_customer_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package v1beta1
22

33
import (
44
"context"
5+
"errors"
56
"testing"
67

78
"github.com/google/uuid"
89
"github.com/raystack/frontier/billing/customer"
10+
"github.com/raystack/frontier/core/audit"
911
"github.com/raystack/frontier/core/organization"
1012
"github.com/raystack/frontier/internal/api/v1beta1/mocks"
1113
frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1"
@@ -90,3 +92,78 @@ func TestHandler_GetRequestCustomerID(t *testing.T) {
9092
})
9193
}
9294
}
95+
96+
func TestUpdateBillingAccountDetails(t *testing.T) {
97+
tests := []struct {
98+
name string
99+
request *frontierv1beta1.UpdateBillingAccountDetailsRequest
100+
mockUpdateDetails customer.Details
101+
mockUpdateError error
102+
expectError bool
103+
expectedError error
104+
}{
105+
{
106+
name: "successful billing account details update",
107+
request: &frontierv1beta1.UpdateBillingAccountDetailsRequest{
108+
Id: "billing-account-id",
109+
CreditMin: -100,
110+
DueInDays: 30,
111+
},
112+
mockUpdateDetails: customer.Details{
113+
CreditMin: -100,
114+
DueInDays: 30,
115+
},
116+
mockUpdateError: nil,
117+
expectError: false,
118+
},
119+
{
120+
name: "negative due in days error",
121+
request: &frontierv1beta1.UpdateBillingAccountDetailsRequest{
122+
Id: "billing-account-id",
123+
CreditMin: -100,
124+
DueInDays: -1, // Negative due_in_days not allowed
125+
},
126+
expectError: true,
127+
},
128+
{
129+
name: "update details error",
130+
request: &frontierv1beta1.UpdateBillingAccountDetailsRequest{
131+
Id: "billing-account-id",
132+
CreditMin: -100,
133+
DueInDays: 30,
134+
},
135+
mockUpdateError: errors.New("failed to update details"),
136+
expectError: true,
137+
expectedError: errors.New("failed to update details"),
138+
},
139+
}
140+
141+
for _, tt := range tests {
142+
t.Run(tt.name, func(t *testing.T) {
143+
mockCustomerService := mocks.NewCustomerService(t)
144+
145+
if tt.request.GetDueInDays() >= 0 {
146+
mockCustomerService.EXPECT().UpdateDetails(mock.Anything, tt.request.GetId(), mock.Anything).
147+
Return(tt.mockUpdateDetails, tt.mockUpdateError)
148+
}
149+
150+
handler := Handler{
151+
customerService: mockCustomerService,
152+
}
153+
154+
ctx := context.Background()
155+
ctx = audit.SetContextWithService(ctx, audit.NewService("test", audit.NewNoopRepository(), audit.NewNoopWebhookService()))
156+
157+
_, err := handler.UpdateBillingAccountDetails(ctx, tt.request)
158+
159+
if tt.expectError {
160+
assert.Error(t, err)
161+
if tt.expectedError != nil {
162+
assert.Equal(t, tt.expectedError.Error(), err.Error())
163+
}
164+
} else {
165+
assert.NoError(t, err)
166+
}
167+
})
168+
}
169+
}

internal/api/v1beta1/kyc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package v1beta1
33
import (
44
"context"
55
"errors"
6+
"strconv"
67

8+
"github.com/raystack/frontier/core/audit"
79
"github.com/raystack/frontier/core/kyc"
810
frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1"
911
"google.golang.org/grpc/codes"
@@ -37,6 +39,14 @@ func (h Handler) SetOrganizationKyc(ctx context.Context, request *frontierv1beta
3739
return nil, err
3840
}
3941
}
42+
43+
// Add audit log
44+
audit.GetAuditor(ctx, orgKyc.OrgID).
45+
LogWithAttrs(audit.OrgKycUpdatedEvent, audit.OrgTarget(orgKyc.OrgID), map[string]string{
46+
"status": strconv.FormatBool(orgKyc.Status),
47+
"link": orgKyc.Link,
48+
})
49+
4050
return &frontierv1beta1.SetOrganizationKycResponse{OrganizationKyc: transformOrgKycToPB(orgKyc)}, nil
4151
}
4252

internal/api/v1beta1/kyc_test.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"testing"
77

8+
"github.com/raystack/frontier/core/audit"
89
"github.com/raystack/frontier/core/kyc"
910
"github.com/raystack/frontier/internal/api/v1beta1/mocks"
1011
frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1"
@@ -90,16 +91,37 @@ func TestSetOrganizationKyc(t *testing.T) {
9091

9192
for _, tt := range tests {
9293
t.Run(tt.name, func(t *testing.T) {
93-
h := Handler{orgKycService: tt.mockService}
94-
tt.mockService.On("SetKyc", mock.Anything, mock.Anything).Return(tt.mockResponse, tt.mockError)
95-
resp, err := h.SetOrganizationKyc(context.Background(), tt.request)
94+
// Setup mock behavior
95+
if tt.mockError != nil {
96+
tt.mockService.EXPECT().SetKyc(mock.Anything, mock.Anything).Return(kyc.KYC{}, tt.mockError)
97+
} else {
98+
tt.mockService.EXPECT().SetKyc(mock.Anything, mock.Anything).Return(tt.mockResponse, nil)
99+
}
100+
101+
// Create handler with mock service
102+
handler := Handler{
103+
orgKycService: tt.mockService,
104+
}
96105

106+
// Create context with audit service
107+
ctx := context.Background()
108+
ctx = audit.SetContextWithService(ctx, audit.NewService("test", audit.NewNoopRepository(), audit.NewNoopWebhookService()))
109+
110+
// Call the handler method
111+
response, err := handler.SetOrganizationKyc(ctx, tt.request)
112+
113+
// Verify results
97114
if tt.expectError {
98115
assert.Error(t, err)
99-
assert.Equal(t, tt.expectedError.Error(), err.Error())
116+
if tt.expectedError != nil {
117+
assert.Equal(t, tt.expectedError.Error(), err.Error())
118+
}
100119
} else {
101120
assert.NoError(t, err)
102-
assert.NotNil(t, resp)
121+
assert.NotNil(t, response)
122+
assert.Equal(t, tt.mockResponse.OrgID, response.GetOrganizationKyc().GetOrgId())
123+
assert.Equal(t, tt.mockResponse.Status, response.GetOrganizationKyc().GetStatus())
124+
assert.Equal(t, tt.mockResponse.Link, response.GetOrganizationKyc().GetLink())
103125
}
104126
})
105127
}

pkg/server/interceptors/authorization.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,9 +1044,17 @@ var authorizationValidationMap = map[string]func(ctx context.Context, handler *v
10441044
return handler.IsSuperUser(ctx)
10451045
},
10461046
"/raystack.frontier.v1beta1.AdminService/GetBillingAccountDetails": func(ctx context.Context, handler *v1beta1.Handler, req any) error {
1047+
pbReq := req.(*frontierv1beta1.GetBillingAccountDetailsRequest)
1048+
if err := ensureBillingAccountBelongToOrg(ctx, handler, pbReq.GetOrgId(), pbReq.GetId()); err != nil {
1049+
return err
1050+
}
10471051
return handler.IsSuperUser(ctx)
10481052
},
10491053
"/raystack.frontier.v1beta1.AdminService/UpdateBillingAccountDetails": func(ctx context.Context, handler *v1beta1.Handler, req any) error {
1054+
pbReq := req.(*frontierv1beta1.UpdateBillingAccountDetailsRequest)
1055+
if err := ensureBillingAccountBelongToOrg(ctx, handler, pbReq.GetOrgId(), pbReq.GetId()); err != nil {
1056+
return err
1057+
}
10501058
return handler.IsSuperUser(ctx)
10511059
},
10521060
"/raystack.frontier.v1beta1.AdminService/RevertBillingUsage": func(ctx context.Context, handler *v1beta1.Handler, req any) error {

0 commit comments

Comments
 (0)