mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-01-23 22:43:27 +01:00
Add simple auth (#186)
This commit is contained in:
@@ -2,8 +2,10 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
"github.com/arangodb/go-driver/http"
|
||||
@@ -67,17 +69,25 @@ func New(ctx context.Context, index *index.Index, bus *bus.Bus, hooks *hooks.Hoo
|
||||
name = Name
|
||||
}
|
||||
|
||||
conn, err := http.NewConnection(http.ConnectionConfig{Endpoints: []string{config.Host}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
var client driver.Client
|
||||
for {
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok && time.Until(deadline) < 0 {
|
||||
return nil, context.DeadlineExceeded
|
||||
}
|
||||
|
||||
client, err := driver.NewClient(driver.ClientConfig{
|
||||
Connection: conn,
|
||||
Authentication: driver.BasicAuthentication(config.User, config.Password),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
client, err = getClient(ctx, config)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, errors.New("could not load database, connection timed out")
|
||||
}
|
||||
|
||||
log.Printf("could not connect to database: %s, retrying in 10 seconds\n", err)
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
|
||||
hooks.DatabaseAfterConnect(ctx, client, name)
|
||||
@@ -162,10 +172,31 @@ func New(ctx context.Context, index *index.Index, bus *bus.Bus, hooks *hooks.Hoo
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func getClient(ctx context.Context, config *Config) (driver.Client, error) {
|
||||
conn, err := http.NewConnection(http.ConnectionConfig{Endpoints: []string{config.Host}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := driver.NewClient(driver.ClientConfig{
|
||||
Connection: conn,
|
||||
Authentication: driver.BasicAuthentication(config.User, config.Password),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := client.Version(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func SetupDB(ctx context.Context, client driver.Client, dbName string) (driver.Database, error) {
|
||||
databaseExists, err := client.DatabaseExists(ctx, dbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("could not check if database exists: %w", err)
|
||||
}
|
||||
|
||||
var db driver.Database
|
||||
@@ -175,12 +206,12 @@ func SetupDB(ctx context.Context, client driver.Client, dbName string) (driver.D
|
||||
db, err = client.Database(ctx, dbName)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("could not create database: %w", err)
|
||||
}
|
||||
|
||||
collectionExists, err := db.CollectionExists(ctx, migrations.MigrationCollection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("could not check if collection exists: %w", err)
|
||||
}
|
||||
|
||||
if !collectionExists {
|
||||
|
||||
@@ -60,6 +60,8 @@ func generateMigrations() ([]Migration, error) {
|
||||
&createCollection{ID: "create-dashboard-collection", Name: "dashboards", DataType: "dashboards", Schema: `{"type":"object","properties":{"name":{"type":"string"},"widgets":{"items":{"type":"object","properties":{"aggregation":{"type":"string"},"filter":{"type":"string"},"name":{"type":"string"},"type":{"enum":[ "bar", "line", "pie" ]},"width": { "type": "integer", "minimum": 1, "maximum": 12 }},"required":["name","aggregation", "type", "width"]},"type":"array"}},"required":["name","widgets"]}`},
|
||||
|
||||
&updateDocument[model.Settings]{ID: "update-settings-global-1", Collection: "settings", Key: "global", Document: &model.Settings{ArtifactStates: []*model.Type{{Icon: "mdi-help-circle-outline", ID: "unknown", Name: "Unknown", Color: pointer.String(model.TypeColorInfo)}, {Icon: "mdi-skull", ID: "malicious", Name: "Malicious", Color: pointer.String(model.TypeColorError)}, {Icon: "mdi-check", ID: "clean", Name: "Clean", Color: pointer.String(model.TypeColorSuccess)}}, ArtifactKinds: []*model.Type{{Icon: "mdi-server", ID: "asset", Name: "Asset"}, {Icon: "mdi-bullseye", ID: "ioc", Name: "IOC"}}, Timeformat: "yyyy-MM-dd hh:mm:ss"}},
|
||||
|
||||
&updateSchema{ID: "update-user-simple-login", Name: "users", DataType: "user", Schema: `{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"roles":{"items":{"type":"string"},"type":"array"},"salt":{"type":"string"},"sha256":{"type":"string"},"sha512":{"type":"string"}},"required":["blocked","apikey","roles"],"$id":"#/definitions/User"}`},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
108
database/user.go
108
database/user.go
@@ -3,8 +3,10 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
@@ -32,13 +34,15 @@ func generateKey() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func toUser(user *model.UserForm, sha256 *string) *model.User {
|
||||
func toUser(user *model.UserForm, salt, sha256, sha512 *string) *model.User {
|
||||
roles := []string{}
|
||||
roles = append(roles, role.Strings(role.Explodes(user.Roles))...)
|
||||
u := &model.User{
|
||||
Blocked: user.Blocked,
|
||||
Roles: roles,
|
||||
Salt: salt,
|
||||
Sha256: sha256,
|
||||
Sha512: sha512,
|
||||
Apikey: user.Apikey,
|
||||
}
|
||||
|
||||
@@ -87,21 +91,21 @@ func (db *Database) UserGetOrCreate(ctx context.Context, newUser *model.UserForm
|
||||
}
|
||||
|
||||
func (db *Database) UserCreate(ctx context.Context, newUser *model.UserForm) (*model.NewUserResponse, error) {
|
||||
var key string
|
||||
var hash *string
|
||||
var key, salt, sha256Hash, sha512Hash *string
|
||||
if newUser.Apikey {
|
||||
key = generateKey()
|
||||
hash = pointer.String(fmt.Sprintf("%x", sha256.Sum256([]byte(key))))
|
||||
key, sha256Hash = generateAPIKey()
|
||||
} else {
|
||||
salt, sha512Hash = hashUserPassword(newUser)
|
||||
}
|
||||
|
||||
var doc model.User
|
||||
newctx := driver.WithReturnNew(ctx, &doc)
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, hash))
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, salt, sha256Hash, sha512Hash))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toNewUserResponse(meta.Key, &doc, pointer.String(key)), nil
|
||||
return toNewUserResponse(meta.Key, &doc, key), nil
|
||||
}
|
||||
|
||||
func (db *Database) UserCreateSetupAPIKey(ctx context.Context, key string) (*model.UserResponse, error) {
|
||||
@@ -111,11 +115,42 @@ func (db *Database) UserCreateSetupAPIKey(ctx context.Context, key string) (*mod
|
||||
Apikey: true,
|
||||
Blocked: false,
|
||||
}
|
||||
hash := pointer.String(fmt.Sprintf("%x", sha256.Sum256([]byte(key))))
|
||||
sha256Hash := pointer.String(fmt.Sprintf("%x", sha256.Sum256([]byte(key))))
|
||||
|
||||
var doc model.User
|
||||
newctx := driver.WithReturnNew(ctx, &doc)
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, hash))
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, nil, sha256Hash, nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUserResponse(meta.Key, &doc), nil
|
||||
}
|
||||
|
||||
func (db *Database) UserUpdate(ctx context.Context, id string, user *model.UserForm) (*model.UserResponse, error) {
|
||||
var doc model.User
|
||||
_, err := db.userCollection.ReadDocument(ctx, id, &doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if doc.Apikey {
|
||||
return nil, errors.New("cannot update an API key")
|
||||
}
|
||||
|
||||
var salt, sha512Hash *string
|
||||
if user.Password != nil {
|
||||
salt, sha512Hash = hashUserPassword(user)
|
||||
} else {
|
||||
salt = doc.Salt
|
||||
sha512Hash = doc.Sha512
|
||||
}
|
||||
|
||||
ctx = driver.WithReturnNew(ctx, &doc)
|
||||
|
||||
user.ID = id
|
||||
|
||||
meta, err := db.userCollection.ReplaceDocument(ctx, id, toUser(user, salt, nil, sha512Hash))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,12 +197,13 @@ func (db *Database) UserList(ctx context.Context) ([]*model.UserResponse, error)
|
||||
return docs, err
|
||||
}
|
||||
|
||||
func (db *Database) UserByHash(ctx context.Context, sha256 string) (*model.UserResponse, error) {
|
||||
func (db *Database) UserAPIKeyByHash(ctx context.Context, sha256 string) (*model.UserResponse, error) {
|
||||
query := `FOR d in @@collection
|
||||
FILTER d.sha256 == @sha256
|
||||
FILTER d.apikey && d.sha256 == @sha256
|
||||
RETURN d`
|
||||
|
||||
cursor, _, err := db.Query(ctx, query, map[string]any{"@collection": UserCollectionName, "sha256": sha256}, busdb.ReadOperation)
|
||||
vars := map[string]any{"@collection": UserCollectionName, "sha256": sha256}
|
||||
cursor, _, err := db.Query(ctx, query, vars, busdb.ReadOperation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -182,25 +218,41 @@ func (db *Database) UserByHash(ctx context.Context, sha256 string) (*model.UserR
|
||||
return toUserResponse(meta.Key, &doc), err
|
||||
}
|
||||
|
||||
func (db *Database) UserUpdate(ctx context.Context, id string, user *model.UserForm) (*model.UserResponse, error) {
|
||||
func (db *Database) UserByIDAndPassword(ctx context.Context, id, password string) (*model.UserResponse, error) {
|
||||
log.Println("UserByIDAndPassword", id, password)
|
||||
query := `FOR d in @@collection
|
||||
FILTER d._key == @id && !d.apikey && d.sha512 == SHA512(CONCAT(d.salt, @password))
|
||||
RETURN d`
|
||||
|
||||
vars := map[string]any{"@collection": UserCollectionName, "id": id, "password": password}
|
||||
cursor, _, err := db.Query(ctx, query, vars, busdb.ReadOperation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close()
|
||||
|
||||
var doc model.User
|
||||
_, err := db.userCollection.ReadDocument(ctx, id, &doc)
|
||||
meta, err := cursor.ReadDocument(ctx, &doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if doc.Sha256 != nil {
|
||||
return nil, errors.New("cannot update an API key")
|
||||
}
|
||||
|
||||
ctx = driver.WithReturnNew(ctx, &doc)
|
||||
|
||||
user.ID = id
|
||||
|
||||
meta, err := db.userCollection.ReplaceDocument(ctx, id, toUser(user, nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUserResponse(meta.Key, &doc), nil
|
||||
return toUserResponse(meta.Key, &doc), err
|
||||
}
|
||||
|
||||
func generateAPIKey() (key, sha256Hash *string) {
|
||||
newKey := generateKey()
|
||||
sha256Hash = pointer.String(fmt.Sprintf("%x", sha256.Sum256([]byte(newKey))))
|
||||
|
||||
return &newKey, sha256Hash
|
||||
}
|
||||
|
||||
func hashUserPassword(newUser *model.UserForm) (salt, sha512Hash *string) {
|
||||
if newUser.Password != nil {
|
||||
saltKey := generateKey()
|
||||
salt = &saltKey
|
||||
sha512Hash = pointer.String(fmt.Sprintf("%x", sha512.Sum512([]byte(saltKey+*newUser.Password))))
|
||||
}
|
||||
|
||||
return salt, sha512Hash
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user