Skip to content

Commit d591ab0

Browse files
committed
add audit logs
1 parent bc1103b commit d591ab0

9 files changed

Lines changed: 226 additions & 7 deletions

File tree

server/app/app.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func (a *App) registerHandlers() {
118118
userRouter := authRouter.PathPrefix("/user").Subrouter()
119119
invoiceRouter := authRouter.PathPrefix("/invoice").Subrouter()
120120
cardRouter := userRouter.PathPrefix("/card").Subrouter()
121+
logRouter := userRouter.PathPrefix("/log").Subrouter()
121122
notificationRouter := authRouter.PathPrefix("/notification").Subrouter()
122123
vmRouter := authRouter.PathPrefix("/vm").Subrouter()
123124
k8sRouter := authRouter.PathPrefix("/k8s").Subrouter()
@@ -156,6 +157,8 @@ func (a *App) registerHandlers() {
156157
cardRouter.HandleFunc("", WrapFunc(a.ListCardHandler)).Methods("GET", "OPTIONS")
157158
cardRouter.HandleFunc("/default", WrapFunc(a.SetDefaultCardHandler)).Methods("PUT", "OPTIONS")
158159

160+
logRouter.HandleFunc("", WrapFunc(a.ListLogsHandler)).Methods("GET", "OPTIONS")
161+
159162
invoiceRouter.HandleFunc("", WrapFunc(a.ListInvoicesHandler)).Methods("GET", "OPTIONS")
160163
invoiceRouter.HandleFunc("/{id}", WrapFunc(a.GetInvoiceHandler)).Methods("GET", "OPTIONS")
161164
invoiceRouter.HandleFunc("/download/{id}", WrapFunc(a.DownloadInvoiceHandler)).Methods("GET", "OPTIONS")
@@ -204,10 +207,10 @@ func (a *App) registerHandlers() {
204207
voucherRouter.HandleFunc("/all/reset", WrapFunc(a.ResetUsersVoucherBalanceHandler)).Methods("PUT", "OPTIONS")
205208

206209
// middlewares
207-
r.Use(middlewares.LoggingMW)
208210
r.Use(middlewares.EnableCors)
209211

210212
authRouter.Use(middlewares.Authorization(a.db, a.config.Token.Secret, a.config.Token.Timeout))
213+
authRouter.Use(middlewares.AuditLogMiddleware(a.db))
211214
adminRouter.Use(middlewares.AdminAccess(a.db))
212215

213216
// prometheus registration

server/app/audit_handler.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package app
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/codescalers/cloud4students/middlewares"
8+
"github.com/rs/zerolog/log"
9+
"gorm.io/gorm"
10+
)
11+
12+
// Example endpoint: List user's logs
13+
// @Summary List user's logs
14+
// @Description List user's logs
15+
// @Tags Audit
16+
// @Accept json
17+
// @Produce json
18+
// @Security BearerAuth
19+
// @Success 200 {object} []models.AuditLog
20+
// @Failure 400 {object} Response
21+
// @Failure 401 {object} Response
22+
// @Failure 404 {object} Response
23+
// @Failure 500 {object} Response
24+
// @Router /user/log [get]
25+
func (a *App) ListLogsHandler(req *http.Request) (interface{}, Response) {
26+
userID := req.Context().Value(middlewares.UserIDKey("UserID")).(string)
27+
28+
logs, err := a.db.GetUserLogs(userID)
29+
if err == gorm.ErrRecordNotFound || len(logs) == 0 {
30+
return ResponseMsg{
31+
Message: "no logs found",
32+
Data: logs,
33+
}, Ok()
34+
}
35+
if err != nil {
36+
log.Error().Err(err).Send()
37+
return nil, InternalServerError(errors.New(internalServerErrorMsg))
38+
}
39+
40+
return ResponseMsg{
41+
Message: "Logs are found",
42+
Data: logs,
43+
}, Ok()
44+
}

server/docs/docs.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,6 +1937,53 @@ const docTemplate = `{
19371937
}
19381938
}
19391939
},
1940+
"/user/log": {
1941+
"get": {
1942+
"security": [
1943+
{
1944+
"BearerAuth": []
1945+
}
1946+
],
1947+
"description": "List user's logs",
1948+
"consumes": [
1949+
"application/json"
1950+
],
1951+
"produces": [
1952+
"application/json"
1953+
],
1954+
"tags": [
1955+
"Audit"
1956+
],
1957+
"summary": "List user's logs",
1958+
"responses": {
1959+
"200": {
1960+
"description": "OK",
1961+
"schema": {
1962+
"type": "array",
1963+
"items": {
1964+
"$ref": "#/definitions/models.AuditLog"
1965+
}
1966+
}
1967+
},
1968+
"400": {
1969+
"description": "Bad Request",
1970+
"schema": {}
1971+
},
1972+
"401": {
1973+
"description": "Unauthorized",
1974+
"schema": {}
1975+
},
1976+
"404": {
1977+
"description": "Not Found",
1978+
"schema": {}
1979+
},
1980+
"500": {
1981+
"description": "Internal Server Error",
1982+
"schema": {}
1983+
}
1984+
}
1985+
}
1986+
},
19401987
"/user/refresh_token": {
19411988
"post": {
19421989
"security": [
@@ -3095,6 +3142,32 @@ const docTemplate = `{
30953142
"voucherAndBalanceAndCard"
30963143
]
30973144
},
3145+
"models.AuditLog": {
3146+
"type": "object",
3147+
"properties": {
3148+
"id": {
3149+
"type": "integer"
3150+
},
3151+
"method": {
3152+
"type": "string"
3153+
},
3154+
"status_code": {
3155+
"type": "integer"
3156+
},
3157+
"success": {
3158+
"type": "boolean"
3159+
},
3160+
"timestamp": {
3161+
"type": "string"
3162+
},
3163+
"url": {
3164+
"type": "string"
3165+
},
3166+
"user_id": {
3167+
"type": "string"
3168+
}
3169+
}
3170+
},
30983171
"models.Card": {
30993172
"type": "object",
31003173
"required": [

server/docs/swagger.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,23 @@ definitions:
302302
- voucherAndCard
303303
- balanceAndCard
304304
- voucherAndBalanceAndCard
305+
models.AuditLog:
306+
properties:
307+
id:
308+
type: integer
309+
method:
310+
type: string
311+
status_code:
312+
type: integer
313+
success:
314+
type: boolean
315+
timestamp:
316+
type: string
317+
url:
318+
type: string
319+
user_id:
320+
type: string
321+
type: object
305322
models.Card:
306323
properties:
307324
brand:
@@ -1918,6 +1935,37 @@ paths:
19181935
summary: Send code to forget password email for verification
19191936
tags:
19201937
- User
1938+
/user/log:
1939+
get:
1940+
consumes:
1941+
- application/json
1942+
description: List user's logs
1943+
produces:
1944+
- application/json
1945+
responses:
1946+
"200":
1947+
description: OK
1948+
schema:
1949+
items:
1950+
$ref: '#/definitions/models.AuditLog'
1951+
type: array
1952+
"400":
1953+
description: Bad Request
1954+
schema: {}
1955+
"401":
1956+
description: Unauthorized
1957+
schema: {}
1958+
"404":
1959+
description: Not Found
1960+
schema: {}
1961+
"500":
1962+
description: Internal Server Error
1963+
schema: {}
1964+
security:
1965+
- BearerAuth: []
1966+
summary: List user's logs
1967+
tags:
1968+
- Audit
19211969
/user/refresh_token:
19221970
post:
19231971
consumes:

server/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/swaggo/swag v1.16.4
2424
github.com/threefoldtech/tfgrid-sdk-go/grid-client v0.16.0
2525
github.com/threefoldtech/tfgrid-sdk-go/grid-proxy v0.16.0
26+
github.com/urfave/negroni/v3 v3.1.1
2627
golang.org/x/crypto v0.30.0
2728
golang.org/x/text v0.21.0
2829
gopkg.in/validator.v2 v2.0.1

server/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
204204
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
205205
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
206206
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
207+
github.com/urfave/negroni/v3 v3.1.1 h1:6MS4nG9Jk/UuCACaUlNXCbiKa0ywF9LXz5dGu09v8hw=
208+
github.com/urfave/negroni/v3 v3.1.1/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs=
207209
github.com/vedhavyas/go-subkey v1.0.3 h1:iKR33BB/akKmcR2PMlXPBeeODjWLM90EL98OrOGs8CA=
208210
github.com/vedhavyas/go-subkey v1.0.3/go.mod h1:CloUaFQSSTdWnINfBRFjVMkWXZANW+nd8+TI5jYcl6Y=
209211
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=

server/middlewares/logging.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,35 @@ package middlewares
33

44
import (
55
"net/http"
6+
"time"
67

8+
"github.com/codescalers/cloud4students/models"
79
"github.com/rs/zerolog/log"
10+
"github.com/urfave/negroni/v3"
811
)
912

10-
// LoggingMW logs all information of every request
11-
func LoggingMW(h http.Handler) http.Handler {
12-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13-
log.Info().Timestamp().Str("method", r.Method).Str("uri", r.RequestURI).Send()
14-
h.ServeHTTP(w, r)
15-
})
13+
func AuditLogMiddleware(db models.DB) func(http.Handler) http.Handler {
14+
return func(h http.Handler) http.Handler {
15+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16+
userID := r.Context().Value(UserIDKey("UserID")).(string)
17+
18+
lrw := negroni.NewResponseWriter(w)
19+
h.ServeHTTP(lrw, r)
20+
21+
statusCode := lrw.Status()
22+
23+
l := models.AuditLog{
24+
UserID: userID,
25+
Method: r.Method,
26+
URL: r.URL.Path,
27+
Timestamp: time.Now(),
28+
Success: statusCode >= 200 && statusCode < 300,
29+
StatusCode: statusCode,
30+
}
31+
32+
if err := db.CreateAuditLog(&l); err != nil {
33+
log.Error().Err(err).Msg("logging audit failed")
34+
}
35+
})
36+
}
1637
}

server/models/audit_log.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package models
2+
3+
import (
4+
"time"
5+
)
6+
7+
// Struct to represent audit log details
8+
type AuditLog struct {
9+
ID uint `gorm:"primaryKey"`
10+
UserID string `json:"user_id" gorm:"not null"`
11+
Method string `json:"method"`
12+
URL string `json:"url"`
13+
Timestamp time.Time `json:"timestamp"`
14+
Success bool `json:"success"`
15+
StatusCode int `json:"status_code"`
16+
}
17+
18+
func (d *DB) CreateAuditLog(l *AuditLog) error {
19+
return d.db.Create(&l).Error
20+
}
21+
22+
// GetUserLogs gets user logs
23+
func (d *DB) GetUserLogs(userID string) ([]AuditLog, error) {
24+
var res []AuditLog
25+
return res, d.db.Find(&res, "user_id = ?", userID).Error
26+
}

server/models/database.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func (d *DB) Migrate() error {
3232
err := d.db.AutoMigrate(
3333
&User{}, &State{}, &Card{}, &Invoice{}, &VM{}, &K8sCluster{}, &Master{}, &Worker{},
3434
&Voucher{}, &Maintenance{}, &Notification{}, &NextLaunch{}, &DeploymentItem{}, &PaymentDetails{},
35+
AuditLog{},
3536
)
3637
if err != nil {
3738
return err

0 commit comments

Comments
 (0)