@@ -25,6 +25,9 @@ type RequestParams struct {
25
25
Audit Auditor
26
26
Log slog.Logger
27
27
28
+ // OrganizationID is only provided when possible. If an audit resource extends
29
+ // beyond the org scope, leave this as the nil uuid.
30
+ OrganizationID uuid.UUID
28
31
Request * http.Request
29
32
Action database.AuditAction
30
33
AdditionalFields json.RawMessage
@@ -96,7 +99,7 @@ func ResourceTarget[T Auditable](tgt T) string {
96
99
case database.HealthSettings :
97
100
return "" // no target?
98
101
default :
99
- panic (fmt .Sprintf ("unknown resource %T" , tgt ))
102
+ panic (fmt .Sprintf ("unknown resource %T for ResourceTarget " , tgt ))
100
103
}
101
104
}
102
105
@@ -129,7 +132,7 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
129
132
// Artificial ID for auditing purposes
130
133
return typed .ID
131
134
default :
132
- panic (fmt .Sprintf ("unknown resource %T" , tgt ))
135
+ panic (fmt .Sprintf ("unknown resource %T for ResourceID " , tgt ))
133
136
}
134
137
}
135
138
@@ -160,7 +163,39 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
160
163
case database.HealthSettings :
161
164
return database .ResourceTypeHealthSettings
162
165
default :
163
- panic (fmt .Sprintf ("unknown resource %T" , typed ))
166
+ panic (fmt .Sprintf ("unknown resource %T for ResourceType" , typed ))
167
+ }
168
+ }
169
+
170
+ // ResourceRequiresOrgID will ensure given resources are always audited with an
171
+ // organization ID.
172
+ func ResourceRequiresOrgID [T Auditable ]() bool {
173
+ var tgt T
174
+ switch any (tgt ).(type ) {
175
+ case database.Template , database.TemplateVersion :
176
+ return true
177
+ case database.Workspace , database.WorkspaceBuild :
178
+ return true
179
+ case database.AuditableGroup :
180
+ return true
181
+ case database.User :
182
+ return false
183
+ case database.GitSSHKey :
184
+ return false
185
+ case database.APIKey :
186
+ return false
187
+ case database.License :
188
+ return false
189
+ case database.WorkspaceProxy :
190
+ return false
191
+ case database.AuditOAuthConvertState :
192
+ // The merge state is for the given user
193
+ return false
194
+ case database.HealthSettings :
195
+ // Artificial ID for auditing purposes
196
+ return false
197
+ default :
198
+ panic (fmt .Sprintf ("unknown resource %T for ResourceRequiresOrgID" , tgt ))
164
199
}
165
200
}
166
201
@@ -228,6 +263,11 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
228
263
action = req .Action
229
264
}
230
265
266
+ if ResourceRequiresOrgID [T ]() && p .OrganizationID == uuid .Nil {
267
+ // We panic as this is a developer error and should never happen.
268
+ panic (fmt .Sprintf ("missing required organization ID for resource %s" , either (req .Old , req .New , ResourceType [T ], req .params .Action )))
269
+ }
270
+
231
271
ip := parseIP (p .Request .RemoteAddr )
232
272
auditLog := database.AuditLog {
233
273
ID : uuid .New (),
@@ -243,6 +283,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
243
283
StatusCode : int32 (sw .Status ),
244
284
RequestID : httpmw .RequestID (p .Request ),
245
285
AdditionalFields : p .AdditionalFields ,
286
+ OrganizationID : p .OrganizationID ,
246
287
}
247
288
err := p .Audit .Export (ctx , auditLog )
248
289
if err != nil {
@@ -272,6 +313,11 @@ func BackgroundAudit[T Auditable](ctx context.Context, p *BackgroundAuditParams[
272
313
p .AdditionalFields = json .RawMessage ("{}" )
273
314
}
274
315
316
+ if ResourceRequiresOrgID [T ]() && p .OrganizationID == uuid.Nil {
317
+ // We panic as this is a developer error and should never happen.
318
+ panic (fmt .Sprintf ("missing required organization ID for resource %s" , either (p .Old , p .New , ResourceType [T ], p .Action ))
319
+ }
320
+
275
321
auditLog := database.AuditLog {
276
322
ID : uuid .New (),
277
323
Time : dbtime .Now (),
0 commit comments