2021-02-25 18:24:24 +00:00
|
|
|
package nosql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
nosqlDB "github.com/smallstep/nosql"
|
|
|
|
"go.step.sm/crypto/jose"
|
|
|
|
)
|
|
|
|
|
|
|
|
// dbAccount represents an ACME account.
|
|
|
|
type dbAccount struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Created time.Time `json:"created"`
|
|
|
|
Deactivated time.Time `json:"deactivated"`
|
|
|
|
Key *jose.JSONWebKey `json:"key"`
|
|
|
|
Contact []string `json:"contact,omitempty"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
}
|
|
|
|
|
2021-02-26 18:12:30 +00:00
|
|
|
func (dba *dbAccount) clone() *dbAccount {
|
|
|
|
nu := *dba
|
|
|
|
return &nu
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) saveDBAccount(nu *dbAccount, old *dbAccount) error {
|
2021-02-25 18:24:24 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
oldB []byte
|
|
|
|
)
|
|
|
|
if old == nil {
|
|
|
|
oldB = nil
|
|
|
|
} else {
|
|
|
|
if oldB, err = json.Marshal(old); err != nil {
|
|
|
|
return ServerInternalErr(errors.Wrap(err, "error marshaling old acme order"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := json.Marshal(*nu)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error marshaling new account object")
|
|
|
|
}
|
|
|
|
// Set the Account
|
|
|
|
_, swapped, err := db.CmpAndSwap(accountTable, []byte(nu.ID), oldB, b)
|
|
|
|
switch {
|
|
|
|
case err != nil:
|
|
|
|
return ServerInternalErr(errors.Wrap(err, "error storing account"))
|
|
|
|
case !swapped:
|
|
|
|
return ServerInternalErr(errors.New("error storing account; " +
|
|
|
|
"value has changed since last read"))
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateAccount imlements the AcmeDB.CreateAccount interface.
|
2021-02-26 18:12:30 +00:00
|
|
|
func (db *DB) CreateAccount(ctx context.Context, acc *types.Account) error {
|
2021-02-25 18:24:24 +00:00
|
|
|
id, err := randID()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dba := &dbAccount{
|
|
|
|
ID: id,
|
|
|
|
Key: acc.Key,
|
|
|
|
Contact: acc.Contact,
|
|
|
|
Status: acc.Valid,
|
|
|
|
Created: clock.Now(),
|
|
|
|
}
|
|
|
|
|
|
|
|
kid, err := keyToID(dba.Key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
kidB := []byte(kid)
|
|
|
|
|
|
|
|
// Set the jwkID -> acme account ID index
|
|
|
|
_, swapped, err := db.db.CmpAndSwap(accountByKeyIDTable, kidB, nil, []byte(a.ID))
|
|
|
|
switch {
|
|
|
|
case err != nil:
|
|
|
|
return ServerInternalErr(errors.Wrap(err, "error setting key-id to account-id index"))
|
|
|
|
case !swapped:
|
|
|
|
return ServerInternalErr(errors.Errorf("key-id to account-id index already exists"))
|
|
|
|
default:
|
2021-02-26 18:12:30 +00:00
|
|
|
if err = db.saveDBAccount(dba, nil); err != nil {
|
2021-02-25 18:24:24 +00:00
|
|
|
db.db.Del(accountByKeyIDTable, kidB)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-26 18:12:30 +00:00
|
|
|
// GetAccount retrieves an ACME account by ID.
|
|
|
|
func (db *DB) GetAccount(ctx context.Context, id string) (*types.Account, error) {
|
|
|
|
|
|
|
|
return &types.Account{
|
|
|
|
Status: dbacc.Status,
|
|
|
|
Contact: dbacc.Contact,
|
|
|
|
Orders: dir.getLink(ctx, OrdersByAccountLink, true, a.ID),
|
|
|
|
Key: dbacc.Key,
|
|
|
|
ID: dbacc.ID,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAccountByKeyID retrieves an ACME account by KeyID (thumbprint of the Account Key -- JWK).
|
|
|
|
func (db *DB) GetAccountByKeyID(ctx context.Context, kid string) (*types.Account, error) {
|
|
|
|
id, err := db.getAccountIDByKeyID(kid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return db.GetAccount(ctx, id)
|
|
|
|
}
|
|
|
|
|
2021-02-25 18:24:24 +00:00
|
|
|
// UpdateAccount imlements the AcmeDB.UpdateAccount interface.
|
2021-02-26 18:12:30 +00:00
|
|
|
func (db *DB) UpdateAccount(ctx context.Context, acc *types.Account) error {
|
|
|
|
kid := "from-context"
|
|
|
|
|
|
|
|
old, err := db.getDBAccountByKeyID(ctx, kid)
|
2021-02-25 18:24:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-02-26 18:12:30 +00:00
|
|
|
nu := old.clone()
|
|
|
|
nu.Contact = acc.contact
|
|
|
|
nu.Status = acc.Status
|
2021-02-25 18:24:24 +00:00
|
|
|
|
|
|
|
// If the status has changed to 'deactivated', then set deactivatedAt timestamp.
|
2021-02-26 18:12:30 +00:00
|
|
|
if acc.Status == types.StatusDeactivated && old.Status != types.Status.Deactivated {
|
|
|
|
nu.Deactivated = clock.Now()
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 18:12:30 +00:00
|
|
|
return db.saveDBAccount(newdba, dba)
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 18:12:30 +00:00
|
|
|
func (db *DB) getAccountIDByKeyID(ctx context.Context, kid string) (string, error) {
|
|
|
|
id, err := db.db.Get(accountByKeyIDTable, []byte(kid))
|
2021-02-25 18:24:24 +00:00
|
|
|
if err != nil {
|
|
|
|
if nosqlDB.IsErrNotFound(err) {
|
2021-02-26 18:12:30 +00:00
|
|
|
return nil, MalformedErr(errors.Wrapf(err, "account with key id %s not found", kid))
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|
2021-02-26 18:12:30 +00:00
|
|
|
return nil, ServerInternalErr(errors.Wrapf(err, "error loading key-account index"))
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|
2021-02-26 18:12:30 +00:00
|
|
|
return string(id), nil
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 18:12:30 +00:00
|
|
|
// getDBAccountByKeyID retrieves Id associated with the given Kid.
|
|
|
|
func (db *DB) getDBAccountByKeyID(ctx context.Context, kid string) (*dbAccount, error) {
|
|
|
|
id, err := db.getAccountIDByKeyID(ctx, kid)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data, err := db.db.Get(accountTable, []byte(id))
|
2021-02-25 18:24:24 +00:00
|
|
|
if err != nil {
|
|
|
|
if nosqlDB.IsErrNotFound(err) {
|
2021-02-26 18:12:30 +00:00
|
|
|
return nil, MalformedErr(errors.Wrapf(err, "account %s not found", id))
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|
2021-02-26 18:12:30 +00:00
|
|
|
return nil, ServerInternalErr(errors.Wrapf(err, "error loading account %s", id))
|
|
|
|
}
|
|
|
|
|
|
|
|
dbacc := new(account)
|
|
|
|
if err = json.Unmarshal(data, dbacc); err != nil {
|
|
|
|
return nil, ServerInternalErr(errors.Wrap(err, "error unmarshaling account"))
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|
2021-02-26 18:12:30 +00:00
|
|
|
return dbacc, nil
|
2021-02-25 18:24:24 +00:00
|
|
|
}
|