mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-06 07:12:46 +01:00
234 lines
5.7 KiB
Go
234 lines
5.7 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
|
|
"github.com/arangodb/go-driver"
|
|
"github.com/iancoleman/strcase"
|
|
maut "github.com/jonas-plum/maut/auth"
|
|
|
|
"github.com/SecurityBrewery/catalyst/database/busdb"
|
|
"github.com/SecurityBrewery/catalyst/generated/model"
|
|
"github.com/SecurityBrewery/catalyst/generated/pointer"
|
|
"github.com/SecurityBrewery/catalyst/generated/time"
|
|
)
|
|
|
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
}
|
|
|
|
func generateKey() string {
|
|
b := make([]rune, 32)
|
|
for i := range b {
|
|
b[i] = letters[rand.Intn(len(letters))]
|
|
}
|
|
|
|
return string(b)
|
|
}
|
|
|
|
func toUser(user *model.UserForm, sha256 *string) *model.User {
|
|
u := &model.User{
|
|
Blocked: user.Blocked,
|
|
Roles: user.Roles,
|
|
Sha256: sha256,
|
|
Apikey: user.Apikey,
|
|
}
|
|
|
|
// log.Println(u)
|
|
// b, _ := json.Marshal(u)
|
|
// loader := gojsonschema.NewBytesLoader(b)
|
|
// res, err := model.UserSchema.Validate(loader)
|
|
// if err != nil {
|
|
// log.Println(err)
|
|
// }
|
|
// log.Println(res.Errors())
|
|
|
|
return u
|
|
}
|
|
|
|
func toUserResponse(key string, user *model.User) *model.UserResponse {
|
|
return &model.UserResponse{
|
|
ID: key,
|
|
Roles: user.Roles,
|
|
Blocked: user.Blocked,
|
|
Apikey: user.Apikey,
|
|
}
|
|
}
|
|
|
|
func toNewUserResponse(key string, user *model.User, secret *string) *model.NewUserResponse {
|
|
return &model.NewUserResponse{
|
|
ID: key,
|
|
Roles: user.Roles,
|
|
Secret: secret,
|
|
Blocked: user.Blocked,
|
|
}
|
|
}
|
|
|
|
func (db *Database) UserGetOrCreate(ctx context.Context, newUser *model.UserForm) (*model.UserResponse, error) {
|
|
user, err := db.UserGet(ctx, newUser.ID)
|
|
if err != nil {
|
|
newUser, err := db.UserCreate(ctx, newUser)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &model.UserResponse{ID: newUser.ID, Roles: newUser.Roles, Blocked: newUser.Blocked}, nil
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (db *Database) UserCreate(ctx context.Context, newUser *model.UserForm) (*model.NewUserResponse, error) {
|
|
var key, sha256Hash *string
|
|
if newUser.Apikey {
|
|
key, sha256Hash = generateAPIKey()
|
|
}
|
|
|
|
var doc model.User
|
|
newctx := driver.WithReturnNew(ctx, &doc)
|
|
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, sha256Hash))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return toNewUserResponse(meta.Key, &doc, key), nil
|
|
}
|
|
|
|
func (db *Database) UserCreateSetupAPIKey(ctx context.Context, key string) (*model.UserResponse, error) {
|
|
newUser := &model.UserForm{
|
|
ID: "setup",
|
|
Roles: []string{maut.AdminRole},
|
|
Apikey: true,
|
|
Blocked: false,
|
|
}
|
|
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, sha256Hash))
|
|
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")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (db *Database) UserGet(ctx context.Context, id string) (*model.UserResponse, error) {
|
|
var doc model.User
|
|
meta, err := db.userCollection.ReadDocument(ctx, id, &doc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return toUserResponse(meta.Key, &doc), nil
|
|
}
|
|
|
|
func (db *Database) UserDelete(ctx context.Context, id string) error {
|
|
_, err := db.userCollection.RemoveDocument(ctx, id)
|
|
|
|
return err
|
|
}
|
|
|
|
func (db *Database) UserList(ctx context.Context) ([]*model.UserResponse, error) {
|
|
query := "FOR d IN @@collection RETURN d"
|
|
cursor, _, err := db.Query(ctx, query, map[string]any{"@collection": UserCollectionName}, busdb.ReadOperation)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close()
|
|
var docs []*model.UserResponse
|
|
for {
|
|
var doc model.User
|
|
meta, err := cursor.ReadDocument(ctx, &doc)
|
|
if driver.IsNoMoreDocuments(err) {
|
|
break
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
doc.Sha256 = nil
|
|
docs = append(docs, toUserResponse(meta.Key, &doc))
|
|
}
|
|
|
|
return docs, err
|
|
}
|
|
|
|
func (db *Database) UserAPIKeyByHash(ctx context.Context, sha256 string) (*model.UserResponse, error) {
|
|
query := `FOR d in @@collection
|
|
FILTER d.apikey && d.sha256 == @sha256
|
|
RETURN d`
|
|
|
|
vars := map[string]any{"@collection": UserCollectionName, "sha256": sha256}
|
|
cursor, _, err := db.Query(ctx, query, vars, busdb.ReadOperation)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close()
|
|
|
|
var doc model.User
|
|
meta, err := cursor.ReadDocument(ctx, &doc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return toUserResponse(meta.Key, &doc), err
|
|
}
|
|
|
|
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
|
|
meta, err := cursor.ReadDocument(ctx, &doc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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
|
|
}
|