erste module drin.
zwischenstand: Architektur aktualisiert, TOFU implementiert,
This commit is contained in:
1 parent
9d5789c5d7
commit
c8df9d7651
41 files changed
+6455
-36
No files matched your search
@@ -0,0 +1,88 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// hashToken: Tokens werden nur als SHA-256 gespeichert. Ein einfacher Hash
|
||||
// reicht, weil Tokens zufällig mit hoher Entropie erzeugt werden.
|
||||
func hashToken(token string) string {
|
||||
sum := sha256.Sum256([]byte(token))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// AddEnrollmentToken hinterlegt einen einmaligen Einladungs-Token für
|
||||
// tenantID. Erzeugt wird der Token vom Aufrufer; gespeichert wird nur sein
|
||||
// Hash.
|
||||
func (s *Store) AddEnrollmentToken(ctx context.Context, token, tenantID string, expiresAt time.Time) error {
|
||||
if token == "" {
|
||||
return invalid("Token fehlt")
|
||||
}
|
||||
if !expiresAt.After(s.now()) {
|
||||
return invalid("Ablaufzeitpunkt %v liegt nicht in der Zukunft", expiresAt)
|
||||
}
|
||||
_, err := s.db.ExecContext(ctx,
|
||||
`INSERT INTO enrollment_tokens (token_hash, tenant_id, expires_at, used) VALUES (?, ?, ?, 0)`,
|
||||
hashToken(token), tenantID, formatTime(expiresAt))
|
||||
return mapConstraint(err, "Enrollment-Token")
|
||||
}
|
||||
|
||||
// Enroll löst token ein und legt in derselben Transaktion den Knoten n an.
|
||||
// n.TenantID wird durch den Mandanten des Tokens ersetzt. Schlägt das Anlegen
|
||||
// fehl, bleibt der Token unverbraucht. Fehler: ErrNotFound (unbekannter
|
||||
// Token), ErrTokenUsed, ErrTokenExpired sowie die Fehler von CreateNode.
|
||||
func (s *Store) Enroll(ctx context.Context, token string, n Node) (Node, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return Node{}, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
hash := hashToken(token)
|
||||
var tenantID string
|
||||
var expiresAt time.Time
|
||||
var used bool
|
||||
err = tx.QueryRowContext(ctx,
|
||||
`SELECT tenant_id, expires_at, used FROM enrollment_tokens WHERE token_hash = ?`, hash).
|
||||
Scan(&tenantID, dbTime{&expiresAt}, &used)
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return Node{}, fmt.Errorf("Enrollment-Token: %w", ErrNotFound)
|
||||
case err != nil:
|
||||
return Node{}, err
|
||||
case used:
|
||||
return Node{}, ErrTokenUsed
|
||||
case !s.now().Before(expiresAt):
|
||||
return Node{}, ErrTokenExpired
|
||||
}
|
||||
|
||||
if _, err := tx.ExecContext(ctx, `UPDATE enrollment_tokens SET used = 1 WHERE token_hash = ?`, hash); err != nil {
|
||||
return Node{}, err
|
||||
}
|
||||
n.TenantID = tenantID
|
||||
node, err := s.insertNode(ctx, tx, n)
|
||||
if err != nil {
|
||||
return Node{}, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return Node{}, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// PurgeEnrollmentTokens löscht verbrauchte und abgelaufene Tokens und
|
||||
// liefert ihre Anzahl.
|
||||
func (s *Store) PurgeEnrollmentTokens(ctx context.Context) (int64, error) {
|
||||
res, err := s.db.ExecContext(ctx,
|
||||
`DELETE FROM enrollment_tokens WHERE used = 1 OR expires_at <= ?`, formatTime(s.now()))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
Reference in new issue
Block a user