Compare commits
2 Commits
c9f3863a25
...
e1fa6c502e
| Author | SHA1 | Date | |
|---|---|---|---|
| e1fa6c502e | |||
| aa20652abc |
52
internal/http/middleware/admin.go
Normal file
52
internal/http/middleware/admin.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
httphelpers "synlotto-website/internal/helpers/http"
|
||||||
|
securityHelpers "synlotto-website/internal/helpers/security"
|
||||||
|
auditlogStorage "synlotto-website/internal/storage/auditlog"
|
||||||
|
|
||||||
|
"synlotto-website/internal/platform/bootstrap"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminOnly() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
app := c.MustGet("app").(*bootstrap.App)
|
||||||
|
sm := app.SessionManager
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
v := sm.Get(ctx, "user_id")
|
||||||
|
var uid int64
|
||||||
|
switch t := v.(type) {
|
||||||
|
case int64:
|
||||||
|
uid = t
|
||||||
|
case int:
|
||||||
|
uid = int64(t)
|
||||||
|
default:
|
||||||
|
c.Redirect(http.StatusSeeOther, "/account/login")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !securityHelpers.IsAdmin(app.DB, int(uid)) {
|
||||||
|
c.String(http.StatusForbidden, "Forbidden")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auditlogStorage.LogAdminAccess(
|
||||||
|
app.DB,
|
||||||
|
uid,
|
||||||
|
c.Request.URL.Path,
|
||||||
|
httphelpers.ClientIP(c.Request),
|
||||||
|
c.Request.UserAgent(),
|
||||||
|
time.Now().UTC(),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,76 +1,55 @@
|
|||||||
package storage
|
package auditlogStorage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
securityHelpers "synlotto-website/internal/helpers/security"
|
|
||||||
|
|
||||||
"synlotto-website/internal/logging"
|
"synlotto-website/internal/logging"
|
||||||
"synlotto-website/internal/platform/bootstrap"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const insertAdminAccessSQL = `
|
||||||
|
INSERT INTO admin_access_log
|
||||||
|
(user_id, path, ip, user_agent, accessed_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
|
const insertLoginSQL = `
|
||||||
|
INSERT INTO audit_login
|
||||||
|
(user_id, username, success, ip, user_agent, timestamp)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
|
|
||||||
const insertRegistrationSQL = `
|
const insertRegistrationSQL = `
|
||||||
INSERT INTO audit_registration
|
INSERT INTO audit_registration
|
||||||
(user_id, username, email, ip, user_agent, timestamp)
|
(user_id, username, email, ip, user_agent, timestamp)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
|
|
||||||
func AdminOnly() gin.HandlerFunc {
|
// LogLoginAttempt stores a login attempt. Pass userID if known; otherwise it's NULL.
|
||||||
return func(c *gin.Context) {
|
func LogLoginAttempt(db *sql.DB, ip, userAgent, username string, success bool, userID ...int64) {
|
||||||
app := c.MustGet("app").(*bootstrap.App)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
sm := app.SessionManager
|
defer cancel()
|
||||||
ctx := c.Request.Context()
|
|
||||||
|
|
||||||
// Require logged in (assumes RequireAuth already ran; this is a safety net)
|
var uid sql.NullInt64
|
||||||
v := sm.Get(ctx, "user_id")
|
if len(userID) > 0 {
|
||||||
var uid int64
|
uid.Valid = true
|
||||||
switch t := v.(type) {
|
uid.Int64 = userID[0]
|
||||||
case int64:
|
|
||||||
uid = t
|
|
||||||
case int:
|
|
||||||
uid = int64(t)
|
|
||||||
default:
|
|
||||||
c.Redirect(http.StatusSeeOther, "/account/login")
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check admin
|
_, err := db.ExecContext(ctx, insertLoginSQL,
|
||||||
if !securityHelpers.IsAdmin(app.DB, int(uid)) {
|
uid,
|
||||||
// Optional: log access attempt here or in a helper
|
username,
|
||||||
c.String(http.StatusForbidden, "Forbidden")
|
success,
|
||||||
c.Abort()
|
ip,
|
||||||
return
|
userAgent,
|
||||||
}
|
time.Now().UTC(),
|
||||||
|
|
||||||
// Optionally record access (moved here from storage)
|
|
||||||
_, _ = app.DB.Exec(`
|
|
||||||
INSERT INTO admin_access_log (user_id, path, ip, user_agent, accessed_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
|
||||||
`, uid, c.Request.URL.Path, c.ClientIP(), c.Request.UserAgent(), time.Now().UTC())
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo has to add in - db *sql.DB to make this work should this not be an import as all functions use it, more importantly no functions in storage just sql?
|
|
||||||
// Handler Call - auditlogStorage.LogLoginAttempt(db, r.RemoteAddr, r.UserAgent(), username, ok)
|
|
||||||
func LogLoginAttempt(db *sql.DB, rIP, rUA, username string, success bool) {
|
|
||||||
_, err := db.Exec(
|
|
||||||
`INSERT INTO audit_login (username, success, ip, user_agent, timestamp)
|
|
||||||
VALUES ($1, $2, $3, $4, $5)`,
|
|
||||||
username, success, rIP, rUA, time.Now().UTC(),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Info("❌ Failed to log login:", err)
|
logging.Info("❌ Failed to log login: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogSignup stores a registration event.
|
||||||
func LogSignup(db *sql.DB, userID int64, username, email, ip, userAgent string) {
|
func LogSignup(db *sql.DB, userID int64, username, email, ip, userAgent string) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -82,3 +61,16 @@ func LogSignup(db *sql.DB, userID int64, username, email, ip, userAgent string)
|
|||||||
logging.Info("❌ Failed to log registration: %v", err)
|
logging.Info("❌ Failed to log registration: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogAdminAccess stores an admin access record.
|
||||||
|
func LogAdminAccess(db *sql.DB, userID int64, path, ip, userAgent string, at time.Time) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := db.ExecContext(ctx, insertAdminAccessSQL,
|
||||||
|
userID, path, ip, userAgent, at,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logging.Info("❌ Failed to log admin access: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user