InitRequest initializes an audit log for a request. It returns a function that should be deferred, causing the audit log to be committed when the handler returns.
(w http.ResponseWriter, p *RequestParams)
| 425 | // that should be deferred, causing the audit log to be committed when the |
| 426 | // handler returns. |
| 427 | func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request[T], func()) { |
| 428 | sw, ok := w.(*tracing.StatusWriter) |
| 429 | if !ok { |
| 430 | panic("dev error: http.ResponseWriter is not *tracing.StatusWriter") |
| 431 | } |
| 432 | |
| 433 | req := &Request[T]{ |
| 434 | params: p, |
| 435 | } |
| 436 | |
| 437 | return req, func() { |
| 438 | ctx := context.Background() |
| 439 | logCtx := p.Request.Context() |
| 440 | |
| 441 | // If no resources were provided, there's nothing we can audit. |
| 442 | if ResourceID(req.Old) == uuid.Nil && ResourceID(req.New) == uuid.Nil { |
| 443 | // If the request action is a login or logout, we always want to audit it even if |
| 444 | // there is no diff. This is so we can capture events where an API Key is never created |
| 445 | // because a known user fails to login. |
| 446 | if req.params.Action != database.AuditActionLogin && req.params.Action != database.AuditActionLogout { |
| 447 | return |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | diffRaw := []byte("{}") |
| 452 | // Only generate diffs if the request succeeded |
| 453 | // and only if we aren't auditing authentication actions |
| 454 | if sw.Status < 400 && |
| 455 | req.params.Action != database.AuditActionLogin && req.params.Action != database.AuditActionLogout { |
| 456 | diff := Diff(p.Audit, req.Old, req.New) |
| 457 | |
| 458 | var err error |
| 459 | diffRaw, err = json.Marshal(diff) |
| 460 | if err != nil { |
| 461 | p.Log.Warn(logCtx, "marshal diff", slog.Error(err)) |
| 462 | diffRaw = []byte("{}") |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | additionalFieldsRaw := json.RawMessage("{}") |
| 467 | |
| 468 | if p.AdditionalFields != nil { |
| 469 | data, err := json.Marshal(p.AdditionalFields) |
| 470 | if err != nil { |
| 471 | p.Log.Warn(logCtx, "marshal additional fields", slog.Error(err)) |
| 472 | } else { |
| 473 | additionalFieldsRaw = json.RawMessage(data) |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | var userID uuid.UUID |
| 478 | key, ok := httpmw.APIKeyOptional(p.Request) |
| 479 | switch { |
| 480 | case ok: |
| 481 | userID = key.UserID |
| 482 | case req.UserID != uuid.Nil: |
| 483 | userID = req.UserID |
| 484 | default: |
no test coverage detected