package operator import ( "bytes" "context" "database/sql" "encoding/base64" "net/mail" "time" "unicode/utf8" family "amadan.net/rastrillo/rastrillo/crypto" ) // PersonResolver uses the supplied transaction connection and the credential // role's normal blind index. False means existing-only resolution found nobody. type PersonResolver func(context.Context, *sql.Conn, string, bool, time.Time) (string, bool, error) type OwnerInput struct { InputBase Directory string `json:"directory"` TeamID int64 `json:"team_id"` TeamSQID string `json:"team_sqid"` Address string `json:"address"` ExistingOnly bool `json:"existing_only"` } type Credential struct { engine *Engine directory string signer *family.Keypair resolve PersonResolver } func NewCredential(engine *Engine, directory string, signer *family.Keypair, browserPublicKeys [][]byte, resolve PersonResolver) (*Credential, error) { if engine == nil || engine.verifier.pins.Role != "credential" || !Origin(directory) || directory == engine.verifier.pins.Origin || !signingKey(signer) || resolve == nil || len(browserPublicKeys) == 0 || len(browserPublicKeys) > 8 || bytes.Equal(signer.SignPub(), engine.verifier.key) { return nil, ErrRefused } for _, key := range browserPublicKeys { if _, e := public(base64.RawURLEncoding.EncodeToString(key)); e != nil { return nil, ErrRefused } if bytes.Equal(key, signer.SignPub()) || bytes.Equal(key, engine.verifier.key) { return nil, ErrRefused } } return &Credential{engine, directory, signer, resolve}, nil } func (c *Credential) Execute(ctx context.Context, raw []byte) ([]byte, error) { return c.engine.Execute(ctx, raw, func(ctx context.Context, conn *sql.Conn, r Request) ([]byte, error) { var in OwnerInput if DecodeInput(r, &in) != nil || !in.matches(r) || r.authorization.Action != "credential.owner-seed" || in.Directory != c.directory || !validTeam(in.TeamID, in.TeamSQID) || len(in.Address) == 0 || len(in.Address) > 320 || !utf8.ValidString(in.Address) { return nil, ErrRefused } address, e := mail.ParseAddress(in.Address) if e != nil || address.Address != in.Address || address.Name != "" { return nil, ErrRefused } now := c.engine.now() subject, found, e := c.resolve(ctx, conn, in.Address, in.ExistingOnly, now) if e != nil { return nil, ErrUnavailable } if !found || !PersonRef(subject) { return nil, ErrRefused } receipt, e := SignOwner(c.signer, OwnerReceipt{1, "owner-seed", r.authorization.OperationID, r.authorization.Origin, c.directory, in.TeamID, in.TeamSQID, subject, "owner", now.Unix(), now.Unix() + 300}) if e != nil { return nil, e } result := resultFor(r) result.Receipt = receipt return encode(result) }) }