1// Copyright 2020 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"errors"
8	"strings"
9
10	"github.com/keybase/client/go/libkb"
11	keybase1 "github.com/keybase/client/go/protocol/keybase1"
12	jsonw "github.com/keybase/go-jsonw"
13)
14
15type WotReactArg struct {
16	Voucher  keybase1.UserVersion
17	Proof    keybase1.SigID
18	Reaction keybase1.WotReactionType
19}
20
21// WotReact is an engine.
22type WotReact struct {
23	arg *WotReactArg
24	libkb.Contextified
25}
26
27// NewWotReact creates a WotReact engine.
28func NewWotReact(g *libkb.GlobalContext, arg *WotReactArg) *WotReact {
29	return &WotReact{
30		arg:          arg,
31		Contextified: libkb.NewContextified(g),
32	}
33}
34
35// Name is the unique engine name.
36func (e *WotReact) Name() string {
37	return "WotReact"
38}
39
40// GetPrereqs returns the engine prereqs.
41func (e *WotReact) Prereqs() Prereqs {
42	return Prereqs{Device: true}
43}
44
45// RequiredUIs returns the required UIs.
46func (e *WotReact) RequiredUIs() []libkb.UIKind {
47	return []libkb.UIKind{}
48}
49
50// SubConsumers returns the other UI consumers for this engine.
51func (e *WotReact) SubConsumers() []libkb.UIConsumer {
52	return nil
53}
54
55// Run starts the engine.
56func (e *WotReact) Run(mctx libkb.MetaContext) error {
57	luArg := libkb.NewLoadUserArgWithMetaContext(mctx).WithUID(e.arg.Voucher.Uid)
58	them, err := libkb.LoadUser(luArg)
59	if err != nil {
60		return err
61	}
62	if them.GetCurrentEldestSeqno() != e.arg.Voucher.EldestSeqno {
63		return errors.New("voucher has reset, make sure you still know them")
64	}
65
66	statement := jsonw.NewDictionary()
67	if err := statement.SetKey("sig_id", jsonw.NewString(string(e.arg.Proof))); err != nil {
68		return err
69	}
70	reactionType := strings.ToLower(keybase1.WotReactionTypeRevMap[e.arg.Reaction])
71	if err := statement.SetKey("reaction", jsonw.NewString(reactionType)); err != nil {
72		return err
73	}
74	expansions, sum, err := libkb.EmbedExpansionObj(statement)
75	if err != nil {
76		return err
77	}
78
79	signingKey, err := mctx.G().ActiveDevice.SigningKey()
80	if err != nil {
81		return err
82	}
83	sigVersion := libkb.KeybaseSignatureV2
84	var inner []byte
85	var sig string
86
87	// ForcePoll is required.
88	err = mctx.G().GetFullSelfer().WithSelfForcePoll(mctx.Ctx(), func(me *libkb.User) error {
89		if me.GetUID() == e.arg.Voucher.Uid {
90			return libkb.InvalidArgumentError{Msg: "can't react to a vouch from yourself"}
91		}
92		proof, err := me.WotReactProof(mctx, signingKey, sigVersion, sum)
93		if err != nil {
94			return err
95		}
96		inner, err = proof.J.Marshal()
97		if err != nil {
98			return err
99		}
100
101		sig, _, _, err = libkb.MakeSig(
102			mctx,
103			signingKey,
104			libkb.LinkTypeWotReact,
105			inner,
106			libkb.SigHasRevokes(false),
107			keybase1.SeqType_PUBLIC,
108			libkb.SigIgnoreIfUnsupported(true),
109			me,
110			sigVersion,
111		)
112
113		return err
114	})
115
116	if err != nil {
117		return err
118	}
119
120	item := libkb.SigMultiItem{
121		Sig:        sig,
122		SigningKID: signingKey.GetKID(),
123		Type:       string(libkb.LinkTypeWotReact),
124		SeqType:    keybase1.SeqType_PUBLIC,
125		SigInner:   string(inner),
126		Version:    sigVersion,
127		Expansions: expansions,
128	}
129
130	payload := make(libkb.JSONPayload)
131	payload["sigs"] = []interface{}{item}
132
133	if _, err := e.G().API.PostJSON(mctx, libkb.APIArg{
134		Endpoint:    "sig/multi",
135		SessionType: libkb.APISessionTypeREQUIRED,
136		JSONPayload: payload,
137	}); err != nil {
138		return err
139	}
140
141	voucheeUsername := mctx.ActiveDevice().Username(mctx).String()
142	mctx.G().NotifyRouter.HandleWebOfTrustChanged(voucheeUsername)
143	return libkb.DismissWotNotifications(mctx, them.GetName(), voucheeUsername)
144}
145