mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-05 11:10:47 -06:00
[server][auth] Record last use info [#45]
This commit is contained in:
@@ -34,6 +34,7 @@ require (
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mileusna/useragent v1.3.5 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
)
|
||||
|
||||
@@ -141,6 +141,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mileusna/useragent v1.3.5 h1:SJM5NzBmh/hO+4LGeATKpaEX9+b4vcGg2qXGLiNGDws=
|
||||
github.com/mileusna/useragent v1.3.5/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||
|
||||
@@ -39,10 +39,15 @@ func Require(c *gin.Context) {
|
||||
|
||||
func getAuth(c *gin.Context) (*auth.Auth, error) {
|
||||
db := db.Get(c.Request.Context())
|
||||
connectionDetails := auth.ConnectionDetails(
|
||||
c.RemoteIP(),
|
||||
c.Request.Header.Get("X-Device"),
|
||||
c.Request.Header.Get("User-Agent"),
|
||||
)
|
||||
if header := c.Request.Header.Get("Authorization"); header == "" {
|
||||
if cookie, err := c.Request.Cookie("api_token"); err == nil {
|
||||
encodedKey := cookie.Value
|
||||
if a, err := auth.ReadAPIToken(db, encodedKey); err == nil {
|
||||
if a, err := auth.ReadAPIToken(db, encodedKey, connectionDetails); err == nil {
|
||||
return a, nil
|
||||
} else {
|
||||
if errors.Is(err, auth.ErrCredentialsInvalid) {
|
||||
@@ -60,9 +65,9 @@ func getAuth(c *gin.Context) (*auth.Auth, error) {
|
||||
var err error
|
||||
|
||||
if keyIDStr == "" {
|
||||
a, err = auth.ReadAPIToken(db, keyStr)
|
||||
a, err = auth.ReadAPIToken(db, keyStr, connectionDetails)
|
||||
} else {
|
||||
a, err = auth.ReadAPIKey(db, keyIDStr, keyStr)
|
||||
a, err = auth.ReadAPIKey(db, keyIDStr, keyStr, connectionDetails)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -75,7 +80,7 @@ func getAuth(c *gin.Context) (*auth.Auth, error) {
|
||||
}
|
||||
}
|
||||
} else if encodedKey, ok := checkAuthHeader(header, "bearer"); ok {
|
||||
if a, err := auth.ReadAPIToken(db, encodedKey); err == nil {
|
||||
if a, err := auth.ReadAPIToken(db, encodedKey, connectionDetails); err == nil {
|
||||
return a, nil
|
||||
} else {
|
||||
if errors.Is(err, auth.ErrCredentialsInvalid) {
|
||||
|
||||
@@ -47,10 +47,15 @@ func (h *handler) HandleRequest(c *gin.Context) {
|
||||
var a *auth.Auth
|
||||
var err error
|
||||
|
||||
connectionDetails := auth.ConnectionDetails(
|
||||
c.RemoteIP(),
|
||||
c.Request.Header.Get("X-Device"),
|
||||
c.Request.Header.Get("User-Agent"),
|
||||
)
|
||||
if keyIDStr == "" {
|
||||
a, err = auth.ReadAPIToken(db, keyStr)
|
||||
a, err = auth.ReadAPIToken(db, keyStr, connectionDetails)
|
||||
} else {
|
||||
a, err = auth.ReadAPIKey(db, keyIDStr, keyStr)
|
||||
a, err = auth.ReadAPIKey(db, keyIDStr, keyStr, connectionDetails)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package auth
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/mileusna/useragent"
|
||||
)
|
||||
|
||||
type APIKey struct {
|
||||
@@ -19,6 +21,31 @@ type APIKey struct {
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
type LastUsed struct {
|
||||
Time int64 `json:"time"`
|
||||
IP string `json:"ip"`
|
||||
Device string `json:"device"`
|
||||
}
|
||||
|
||||
func ConnectionDetails(ip, device, userAgent string) []byte {
|
||||
if device == "" {
|
||||
ua := useragent.Parse(userAgent)
|
||||
device = ua.Name
|
||||
if ua.Device != "" {
|
||||
device += " on " + ua.Device
|
||||
} else if ua.OS != "" {
|
||||
device += " on " + ua.OS
|
||||
}
|
||||
}
|
||||
info := LastUsed{
|
||||
Time: time.Now().Unix(),
|
||||
IP: ip,
|
||||
Device: device,
|
||||
}
|
||||
b, _ := json.Marshal(info)
|
||||
return b
|
||||
}
|
||||
|
||||
func ListAPIKeys(db db.Handler, userID int32, includeExpired bool) ([]APIKey, error) {
|
||||
q := "SELECT id, created, expires, description, scopes FROM api_keys WHERE user_id = $1"
|
||||
if !includeExpired {
|
||||
@@ -63,36 +90,36 @@ func scanAPIKey(row pgx.CollectableRow) (APIKey, error) {
|
||||
return apiKey, err
|
||||
}
|
||||
|
||||
func ReadAPIToken(db db.Handler, encodedKey string) (*Auth, error) {
|
||||
func ReadAPIToken(db db.Handler, encodedKey string, clientInfo []byte) (*Auth, error) {
|
||||
if b, err := b64Encoder.DecodeString(encodedKey); err != nil {
|
||||
return nil, ErrCredentialsInvalid
|
||||
} else if len(b) < 16 {
|
||||
return nil, ErrCredentialsInvalid
|
||||
} else {
|
||||
keyID, _ := uuid.FromBytes(b[:16])
|
||||
return readAPIKey(db, keyID, b[16:])
|
||||
return readAPIKey(db, keyID, b[16:], clientInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func ReadAPIKey(db db.Handler, keyIDStr, keyStr string) (*Auth, error) {
|
||||
func ReadAPIKey(db db.Handler, keyIDStr, keyStr string, clientInfo []byte) (*Auth, error) {
|
||||
if keyID, err := uuid.Parse(keyIDStr); err != nil {
|
||||
return nil, err
|
||||
} else if key, err := b32Encoder.DecodeString(keyStr); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return readAPIKey(db, keyID, key)
|
||||
return readAPIKey(db, keyID, key, clientInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func readAPIKey(db db.Handler, keyID uuid.UUID, key []byte) (auth *Auth, err error) {
|
||||
func readAPIKey(db db.Handler, keyID uuid.UUID, key []byte, clientInfo []byte) (auth *Auth, err error) {
|
||||
const q = `WITH cte(id) AS (
|
||||
UPDATE api_keys SET last_used = NOW()
|
||||
UPDATE api_keys SET last_used = $3
|
||||
WHERE id = $1 AND hash = $2
|
||||
RETURNING id, user_id, expires, scopes
|
||||
) SELECT k.expires, k.user_id, k.scopes, u.permissions, u.home FROM cte k
|
||||
JOIN users u ON k.user_id = u.id`
|
||||
hash := sha256.Sum256(key)
|
||||
row := db.QueryRow(q, keyID, hash[:])
|
||||
row := db.QueryRow(q, keyID, hash[:], clientInfo)
|
||||
|
||||
auth = new(Auth)
|
||||
err = row.Scan(&auth.expires, &auth.userID, &auth.scopes, &auth.userPermissions, &auth.homeID)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ALTER TABLE api_keys ADD COLUMN last_used TIMESTAMPTZ;
|
||||
ALTER TABLE api_keys ADD COLUMN last_used TEXT;
|
||||
|
||||
---- create above / drop below ----
|
||||
|
||||
ALTER TABLE api_keys DROP COLUMN last_used_by;
|
||||
ALTER TABLE api_keys DROP COLUMN last_used;
|
||||
|
||||
Reference in New Issue
Block a user