From d02eb91b11a6f1a3fcdac90238349b51fbeab7b4 Mon Sep 17 00:00:00 2001 From: William Petit Date: Sat, 1 Apr 2023 14:33:19 +0200 Subject: [PATCH] feat(agent): add contactedAt attribute to agent --- internal/auth/agent/authenticator.go | 8 +++ internal/command/api/agent/util.go | 2 +- internal/datastore/agent.go | 17 +++--- internal/datastore/agent_repository.go | 18 ++++-- internal/datastore/sqlite/agent_repository.go | 55 ++++++++++++++----- .../sqlite/0000002_agent_contactedat.down.sql | 1 + .../sqlite/0000002_agent_contactedat.up.sql | 1 + 7 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 migrations/sqlite/0000002_agent_contactedat.down.sql create mode 100644 migrations/sqlite/0000002_agent_contactedat.up.sql diff --git a/internal/auth/agent/authenticator.go b/internal/auth/agent/authenticator.go index 52bf5c0..e771646 100644 --- a/internal/auth/agent/authenticator.go +++ b/internal/auth/agent/authenticator.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "strings" + "time" "forge.cadoles.com/Cadoles/emissary/internal/auth" "forge.cadoles.com/Cadoles/emissary/internal/datastore" @@ -76,6 +77,13 @@ func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth return nil, errors.WithStack(err) } + contactedAt := time.Now() + + agent, err = a.repo.Update(ctx, agent.ID, datastore.WithAgentUpdateContactedAt(contactedAt)) + if err != nil { + return nil, errors.WithStack(err) + } + user := &User{ agent: agent, } diff --git a/internal/command/api/agent/util.go b/internal/command/api/agent/util.go index 78cfeec..80b9ad5 100644 --- a/internal/command/api/agent/util.go +++ b/internal/command/api/agent/util.go @@ -10,7 +10,7 @@ func agentHints(outputMode format.OutputMode) format.Hints { format.NewProp("Label", "Label"), format.NewProp("Thumbprint", "Thumbprint"), format.NewProp("Status", "Status"), - format.NewProp("CreatedAt", "CreatedAt"), + format.NewProp("ContactedAt", "ContactedAt"), format.NewProp("UpdatedAt", "UpdatedAt"), }, } diff --git a/internal/datastore/agent.go b/internal/datastore/agent.go index 7709683..621f226 100644 --- a/internal/datastore/agent.go +++ b/internal/datastore/agent.go @@ -20,14 +20,15 @@ const ( ) type Agent struct { - ID AgentID `json:"id"` - Label string `json:"label"` - Thumbprint string `json:"thumbprint"` - KeySet *SerializableKeySet `json:"keyset,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - Status AgentStatus `json:"status"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID AgentID `json:"id"` + Label string `json:"label"` + Thumbprint string `json:"thumbprint"` + KeySet *SerializableKeySet `json:"keyset,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` + Status AgentStatus `json:"status"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + ContactedAt *time.Time `json:"contactedAt,omitempty"` } type SerializableKeySet struct { diff --git a/internal/datastore/agent_repository.go b/internal/datastore/agent_repository.go index a643d67..640626d 100644 --- a/internal/datastore/agent_repository.go +++ b/internal/datastore/agent_repository.go @@ -2,6 +2,7 @@ package datastore import ( "context" + "time" "github.com/lestrrat-go/jwx/v2/jwk" ) @@ -68,11 +69,12 @@ func WithAgentQueryThumbprints(thumbprints ...string) AgentQueryOptionFunc { type AgentUpdateOptionFunc func(*AgentUpdateOptions) type AgentUpdateOptions struct { - Label *string - Status *AgentStatus - Metadata *map[string]any - KeySet *jwk.Set - Thumbprint *string + Label *string + Status *AgentStatus + ContactedAt *time.Time + Metadata *map[string]any + KeySet *jwk.Set + Thumbprint *string } func WithAgentUpdateStatus(status AgentStatus) AgentUpdateOptionFunc { @@ -104,3 +106,9 @@ func WithAgentUpdateLabel(label string) AgentUpdateOptionFunc { opts.Label = &label } } + +func WithAgentUpdateContactedAt(contactedAt time.Time) AgentUpdateOptionFunc { + return func(opts *AgentUpdateOptions) { + opts.ContactedAt = &contactedAt + } +} diff --git a/internal/datastore/sqlite/agent_repository.go b/internal/datastore/sqlite/agent_repository.go index 9141885..62ef48a 100644 --- a/internal/datastore/sqlite/agent_repository.go +++ b/internal/datastore/sqlite/agent_repository.go @@ -127,7 +127,7 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer count := 0 err := r.withTx(ctx, func(tx *sql.Tx) error { - query := `SELECT id, label, thumbprint, status, created_at, updated_at FROM agents` + query := `SELECT id, label, thumbprint, status, contacted_at, created_at, updated_at FROM agents` limit := 10 if options.Limit != nil { @@ -194,12 +194,16 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer agent := &datastore.Agent{} metadata := JSONMap{} + contactedAt := sql.NullTime{} - if err := rows.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil { + if err := rows.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &agent.Status, &contactedAt, &agent.CreatedAt, &agent.UpdatedAt); err != nil { return errors.WithStack(err) } agent.Metadata = metadata + if contactedAt.Valid { + agent.ContactedAt = &contactedAt.Time + } agents = append(agents, agent) } @@ -315,7 +319,7 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas err := r.withTx(ctx, func(tx *sql.Tx) error { query := ` - SELECT "id", "label", "thumbprint", "keyset", "metadata", "status", "created_at", "updated_at" + SELECT "id", "label", "thumbprint", "keyset", "metadata", "status", "contacted_at", "created_at", "updated_at" FROM agents WHERE id = $1 ` @@ -323,9 +327,10 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas row := r.db.QueryRowContext(ctx, query, id) metadata := JSONMap{} + contactedAt := sql.NullTime{} var rawKeySet []byte - if err := row.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil { + if err := row.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &contactedAt, &agent.CreatedAt, &agent.UpdatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return datastore.ErrNotFound } @@ -334,6 +339,9 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas } agent.Metadata = metadata + if contactedAt.Valid { + agent.ContactedAt = &contactedAt.Time + } keySet := jwk.NewSet() if err := json.Unmarshal(rawKeySet, &keySet); err != nil { @@ -362,15 +370,11 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts err := r.withTx(ctx, func(tx *sql.Tx) error { query := ` - UPDATE agents SET updated_at = $2 + UPDATE agents SET id = $1 ` - now := time.Now().UTC() - - args := []any{ - id, now, - } - index := 3 + args := []any{id} + index := 2 if options.Status != nil { query += fmt.Sprintf(`, status = $%d`, index) @@ -401,23 +405,45 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts index++ } + if options.ContactedAt != nil { + query += fmt.Sprintf(`, contacted_at = $%d`, index) + utc := options.ContactedAt.UTC() + args = append(args, utc) + index++ + } + if options.Metadata != nil { query += fmt.Sprintf(`, metadata = $%d`, index) args = append(args, JSONMap(*options.Metadata)) index++ } + updated := options.Metadata != nil || + options.Status != nil || + options.Label != nil || + options.KeySet != nil || + options.Thumbprint != nil + if updated { + now := time.Now().UTC() + query += fmt.Sprintf(`, updated_at = $%d`, index) + args = append(args, now) + index++ + } + query += ` WHERE id = $1 - RETURNING "id", "label", "thumbprint", "keyset", "metadata", "status", "created_at", "updated_at" + RETURNING "id", "label", "thumbprint", "keyset", "metadata", "status", "contacted_at", "created_at", "updated_at" ` + logger.Debug(ctx, "executing query", logger.F("query", query), logger.F("args", args)) + row := tx.QueryRowContext(ctx, query, args...) metadata := JSONMap{} + contactedAt := sql.NullTime{} var rawKeySet []byte - if err := row.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil { + if err := row.Scan(&agent.ID, &agent.Label, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &contactedAt, &agent.CreatedAt, &agent.UpdatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return datastore.ErrNotFound } @@ -426,6 +452,9 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts } agent.Metadata = metadata + if contactedAt.Valid { + agent.ContactedAt = &contactedAt.Time + } keySet := jwk.NewSet() if err := json.Unmarshal(rawKeySet, &keySet); err != nil { diff --git a/migrations/sqlite/0000002_agent_contactedat.down.sql b/migrations/sqlite/0000002_agent_contactedat.down.sql new file mode 100644 index 0000000..bdc1ffb --- /dev/null +++ b/migrations/sqlite/0000002_agent_contactedat.down.sql @@ -0,0 +1 @@ +ALTER TABLE agents DROP COLUMN contacted_at; \ No newline at end of file diff --git a/migrations/sqlite/0000002_agent_contactedat.up.sql b/migrations/sqlite/0000002_agent_contactedat.up.sql new file mode 100644 index 0000000..c97f76d --- /dev/null +++ b/migrations/sqlite/0000002_agent_contactedat.up.sql @@ -0,0 +1 @@ +ALTER TABLE agents ADD COLUMN contacted_at datetime; \ No newline at end of file