Skip to content

Commit

Permalink
impl: bcrypt
Browse files Browse the repository at this point in the history
[skip ci]
  • Loading branch information
adityathebe committed Aug 4, 2023
1 parent 9ade0cf commit fc3faaf
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 53 deletions.
41 changes: 41 additions & 0 deletions agent/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package agent

import (
"fmt"

"github.com/flanksource/incident-commander/api"
"github.com/flanksource/incident-commander/db"
"github.com/flanksource/incident-commander/rbac"
"golang.org/x/crypto/bcrypt"
)

func generateAgent(ctx *api.Context, body api.GenerateAgentRequest) (*api.GeneratedAgent, error) {
username, password, err := genUsernamePassword()
if err != nil {
return nil, fmt.Errorf("failed to generate username and password: %w", err)
}

hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}

id, err := db.CreatePerson(ctx, username, string(hashedPassword))
if err != nil {
return nil, fmt.Errorf("failed to create a new person: %w", err)
}

if _, err := rbac.Enforcer.AddRoleForUser(id.String(), "agent"); err != nil {
return nil, fmt.Errorf("failed to add 'agent' role to the new person: %w", err)
}

if err := db.CreateAgent(ctx, body.Name, &id, body.Properties); err != nil {
return nil, fmt.Errorf("failed to create a new agent: %w", err)
}

return &api.GeneratedAgent{
ID: id.String(),
Username: username,
AccessToken: password,
}, nil
}
60 changes: 15 additions & 45 deletions agent/controllers.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package agent

import (
"crypto/rand"
"encoding/binary"
"encoding/json"
"fmt"
"net/http"

"github.com/flanksource/commons/logger"
crand "github.com/flanksource/commons/rand"
"github.com/flanksource/incident-commander/api"
"github.com/flanksource/incident-commander/db"
"github.com/flanksource/incident-commander/rbac"
"github.com/labstack/echo/v4"
)

// GenerateAgent creates a new person and a new agent and associates them.
func GenerateAgent(c echo.Context) error {
ctx := c.(*api.Context)

Expand All @@ -21,54 +20,25 @@ func GenerateAgent(c echo.Context) error {
return c.JSON(http.StatusBadRequest, api.HTTPError{Error: err.Error()})
}

var (
username = fmt.Sprintf("agent-%s", generateRandomString(8))
password = generateRandomString(32)
)

// TODO: Only if unauthenticated, we need to create a user
id, err := db.CreatePerson(ctx, username, password)
agent, err := generateAgent(ctx, body)
if err != nil {
return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"})
}

if ok, err := rbac.Enforcer.AddRoleForUser(id.String(), "agent"); !ok {
return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"})
} else if err != nil {
return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"})
}

if err := db.CreateAgent(ctx, body.Name, &id, body.Properties); err != nil {
return c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"})
logger.Errorf("failed to generate a new agent: %v", err)
c.JSON(http.StatusInternalServerError, api.HTTPError{Error: err.Error(), Message: "error generating agent"})
}

return c.JSON(http.StatusCreated, api.GeneratedAgent{
ID: id.String(),
Username: username,
Password: password,
})
return c.JSON(http.StatusCreated, agent)
}

// generateRandomString generates a random alphanumeric string of the given length.
func generateRandomString(length int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result := make([]byte, length)
for i := range result {
val, err := generateRandomInt(len(letters))
if err != nil {
panic(err) // Handle error in a way that suits your needs
}
result[i] = letters[val]
func genUsernamePassword() (username, password string, err error) {
username, err = crand.GenerateRandHex(8)
if err != nil {
return "", "", err
}
return string(result)
}

// generateRandomInt generates a random integer up to max.
func generateRandomInt(max int) (int, error) {
var n uint32
err := binary.Read(rand.Reader, binary.LittleEndian, &n)
password, err = crand.GenerateRandHex(32)
if err != nil {
return 0, err
return "", "", err
}
return int(n) % max, nil

return fmt.Sprintf("agent-%s", username), password, nil
}
6 changes: 3 additions & 3 deletions api/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type GenerateAgentRequest struct {
}

type GeneratedAgent struct {
ID string `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
ID string `json:"id"`
Username string `json:"username"`
AccessToken string `json:"access_token"`
}
21 changes: 18 additions & 3 deletions auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/labstack/echo/v4"
client "github.com/ory/client-go"
"github.com/patrickmn/go-cache"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)

Expand Down Expand Up @@ -80,16 +81,30 @@ func (k *kratosMiddleware) Session(next echo.HandlerFunc) echo.HandlerFunc {
}
}

func (k *kratosMiddleware) validateAccessToken(ctx context.Context, password string) (*models.AccessToken, error) {
func (k *kratosMiddleware) validateAccessToken(ctx context.Context, username, password string) (*models.AccessToken, error) {
query := `
SELECT
access_tokens.*
FROM
access_tokens
LEFT JOIN people ON access_tokens.person_id = people.id
WHERE
people.name = ?
AND access_tokens.expires_at > NOW()`

var acessToken models.AccessToken
if err := k.db.Raw("SELECT * FROM access_tokens WHERE value = ? AND expires_at > NOW()", password).First(&acessToken).Error; err != nil {
if err := k.db.Raw(query, username).First(&acessToken).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}

return nil, err
}

if err := bcrypt.CompareHashAndPassword([]byte(acessToken.Value), []byte(password)); err != nil {
return nil, nil
}

return &acessToken, nil
}

Expand All @@ -102,7 +117,7 @@ func (k *kratosMiddleware) validateSession(r *http.Request) (*client.Session, er

if username, password, ok := r.BasicAuth(); ok {
if strings.HasPrefix(username, "agent-") {
accessToken, err := k.validateAccessToken(r.Context(), password)
accessToken, err := k.validateAccessToken(r.Context(), username, password)
if err != nil {
return nil, fmt.Errorf("failed to validate agent: %w", err)
} else if accessToken == nil {
Expand Down
4 changes: 2 additions & 2 deletions db/people.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func UpdateIdentityState(ctx *api.Context, id, state string) error {
return ctx.DB().Table("identities").Where("id = ?", id).Update("state", state).Error
}

func CreatePerson(ctx *api.Context, username, password string) (uuid.UUID, error) {
func CreatePerson(ctx *api.Context, username, hashedPassword string) (uuid.UUID, error) {
tx := ctx.DB().Begin()
defer tx.Rollback()

Expand All @@ -38,7 +38,7 @@ func CreatePerson(ctx *api.Context, username, password string) (uuid.UUID, error
}

accessToken := models.AccessToken{
Value: password, // TODO: bcrypt
Value: hashedPassword,
PersonID: person.ID,
ExpiresAt: time.Now().Add(time.Hour), // TODO: decide on this one
}
Expand Down
1 change: 1 addition & 0 deletions rbac/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func Init(adminUserID string) error {
{RoleAdmin, ObjectDatabase, ActionWrite},
{RoleAdmin, ObjectRBAC, ActionWrite},
{RoleAdmin, ObjectAuth, ActionWrite},
{RoleAdmin, ObjectAgentPush, ActionWrite},
{RoleAdmin, ObjectDatabaseIdentity, ActionRead},
{RoleAdmin, ObjectDatabaseConnection, ActionRead},
{RoleAdmin, ObjectDatabaseConnection, ActionCreate},
Expand Down

0 comments on commit fc3faaf

Please sign in to comment.