@ -1,13 +0,0 @@ | |||
# Proxy Server | |||
This package provides all the functionality for a local http server, providing access to key management functionality (creating, listing, updating, and deleting keys). This is a nice building block for larger apps, and the HTTP handlers here can be embedded in a larger server that does nice things like signing transactions and posting them to a tendermint chain (which requires domain-knowledge of the transactions types and out of scope of this generic app). | |||
## Key Management | |||
We expose a number of methods for safely managing your keychain. If you are embedding this in a larger server, you will typically want to mount all these paths under `/keys`. | |||
* `POST /` - provide a name and passphrase and create a brand new key | |||
* `GET /` - get a list of all available key names, along with their public key and address | |||
* `GET /{name}` - get public key and address for this named key | |||
* `PUT /{name}` - update the passphrase for the given key. requires you to correctly provide the current passphrase, as well as a new one. | |||
* `DELETE /{name}` - permanently delete this private key. requires you to correctly provide the current passphrase |
@ -1,59 +0,0 @@ | |||
/* | |||
package server provides http handlers to construct a server server | |||
for key management, transaction signing, and query validation. | |||
Please read the README and godoc to see how to | |||
configure the server for your application. | |||
*/ | |||
package server | |||
import ( | |||
"encoding/json" | |||
"io/ioutil" | |||
"net/http" | |||
"github.com/tendermint/go-crypto/keys/server/types" | |||
data "github.com/tendermint/go-wire/data" | |||
"github.com/pkg/errors" | |||
) | |||
func readRequest(r *http.Request, o interface{}) error { | |||
defer r.Body.Close() | |||
data, err := ioutil.ReadAll(r.Body) | |||
if err != nil { | |||
return errors.Wrap(err, "Read Request") | |||
} | |||
err = json.Unmarshal(data, o) | |||
if err != nil { | |||
return errors.Wrap(err, "Parse") | |||
} | |||
return validate(o) | |||
} | |||
// most errors are bad input, so 406... do better.... | |||
func writeError(w http.ResponseWriter, err error) { | |||
// fmt.Printf("Error: %+v\n", err) | |||
res := types.ErrorResponse{ | |||
Code: 406, | |||
Error: err.Error(), | |||
} | |||
writeCode(w, &res, 406) | |||
} | |||
func writeCode(w http.ResponseWriter, o interface{}, code int) { | |||
// two space indent to make it easier to read | |||
data, err := data.ToJSON(o) | |||
if err != nil { | |||
writeError(w, err) | |||
} else { | |||
w.Header().Set("Content-Type", "application/json") | |||
w.WriteHeader(code) | |||
w.Write(data) | |||
} | |||
} | |||
func writeSuccess(w http.ResponseWriter, o interface{}) { | |||
writeCode(w, o, 200) | |||
} |
@ -1,128 +0,0 @@ | |||
package server | |||
import ( | |||
"errors" | |||
"net/http" | |||
"github.com/gorilla/mux" | |||
keys "github.com/tendermint/go-crypto/keys" | |||
"github.com/tendermint/go-crypto/keys/server/types" | |||
) | |||
type Keys struct { | |||
manager keys.Manager | |||
algo string | |||
} | |||
func New(manager keys.Manager, algo string) Keys { | |||
return Keys{ | |||
manager: manager, | |||
algo: algo, | |||
} | |||
} | |||
func (k Keys) GenerateKey(w http.ResponseWriter, r *http.Request) { | |||
req := types.CreateKeyRequest{ | |||
Algo: k.algo, // default key type from cli | |||
} | |||
err := readRequest(r, &req) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
key, seed, err := k.manager.Create(req.Name, req.Passphrase, req.Algo) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
res := types.CreateKeyResponse{key, seed} | |||
writeSuccess(w, &res) | |||
} | |||
func (k Keys) GetKey(w http.ResponseWriter, r *http.Request) { | |||
vars := mux.Vars(r) | |||
name := vars["name"] | |||
key, err := k.manager.Get(name) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
writeSuccess(w, &key) | |||
} | |||
func (k Keys) ListKeys(w http.ResponseWriter, r *http.Request) { | |||
keys, err := k.manager.List() | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
writeSuccess(w, keys) | |||
} | |||
func (k Keys) UpdateKey(w http.ResponseWriter, r *http.Request) { | |||
req := types.UpdateKeyRequest{} | |||
err := readRequest(r, &req) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
vars := mux.Vars(r) | |||
name := vars["name"] | |||
if name != req.Name { | |||
writeError(w, errors.New("path and json key names don't match")) | |||
return | |||
} | |||
err = k.manager.Update(req.Name, req.OldPass, req.NewPass) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
key, err := k.manager.Get(req.Name) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
writeSuccess(w, &key) | |||
} | |||
func (k Keys) DeleteKey(w http.ResponseWriter, r *http.Request) { | |||
req := types.DeleteKeyRequest{} | |||
err := readRequest(r, &req) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
vars := mux.Vars(r) | |||
name := vars["name"] | |||
if name != req.Name { | |||
writeError(w, errors.New("path and json key names don't match")) | |||
return | |||
} | |||
err = k.manager.Delete(req.Name, req.Passphrase) | |||
if err != nil { | |||
writeError(w, err) | |||
return | |||
} | |||
// not really an error, but something generic | |||
resp := types.ErrorResponse{ | |||
Success: true, | |||
} | |||
writeSuccess(w, &resp) | |||
} | |||
func (k Keys) Register(r *mux.Router) { | |||
r.HandleFunc("/", k.GenerateKey).Methods("POST") | |||
r.HandleFunc("/", k.ListKeys).Methods("GET") | |||
r.HandleFunc("/{name}", k.GetKey).Methods("GET") | |||
r.HandleFunc("/{name}", k.UpdateKey).Methods("POST", "PUT") | |||
r.HandleFunc("/{name}", k.DeleteKey).Methods("DELETE") | |||
} |
@ -1,193 +0,0 @@ | |||
package server_test | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"net/http" | |||
"net/http/httptest" | |||
"testing" | |||
"github.com/gorilla/mux" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
keys "github.com/tendermint/go-crypto/keys" | |||
"github.com/tendermint/go-crypto/keys/cryptostore" | |||
"github.com/tendermint/go-crypto/keys/server" | |||
"github.com/tendermint/go-crypto/keys/server/types" | |||
"github.com/tendermint/go-crypto/keys/storage/memstorage" | |||
) | |||
func TestKeyServer(t *testing.T) { | |||
assert, require := assert.New(t), require.New(t) | |||
r := setupServer() | |||
// let's abstract this out a bit.... | |||
keys, code, err := listKeys(r) | |||
require.Nil(err) | |||
require.Equal(http.StatusOK, code) | |||
assert.Equal(0, len(keys)) | |||
algo := "ed25519" | |||
n1, n2 := "personal", "business" | |||
p0, p1, p2 := "1234", "over10chars...", "really-secure!@#$" | |||
// this fails for validation | |||
_, code, err = createKey(r, n1, p0, algo) | |||
require.Nil(err, "%+v", err) | |||
require.NotEqual(http.StatusOK, code) | |||
// new password better | |||
key, code, err := createKey(r, n1, p1, algo) | |||
require.Nil(err, "%+v", err) | |||
require.Equal(http.StatusOK, code) | |||
require.Equal(n1, key.Key.Name) | |||
require.NotEmpty(n1, key.Seed) | |||
// the other one works | |||
key2, code, err := createKey(r, n2, p2, algo) | |||
require.Nil(err, "%+v", err) | |||
require.Equal(http.StatusOK, code) | |||
require.Equal(key2.Key.Name, n2) | |||
require.NotEmpty(n2, key.Seed) | |||
// let's abstract this out a bit.... | |||
keys, code, err = listKeys(r) | |||
require.Nil(err) | |||
require.Equal(http.StatusOK, code) | |||
if assert.Equal(2, len(keys)) { | |||
// in alphabetical order | |||
assert.Equal(keys[0].Name, n2) | |||
assert.Equal(keys[1].Name, n1) | |||
} | |||
// get works | |||
k, code, err := getKey(r, n1) | |||
require.Nil(err, "%+v", err) | |||
require.Equal(http.StatusOK, code) | |||
assert.Equal(n1, k.Name) | |||
assert.NotNil(k.Address) | |||
assert.Equal(key.Key.Address, k.Address) | |||
// delete with proper key | |||
_, code, err = deleteKey(r, n1, p1) | |||
require.Nil(err, "%+v", err) | |||
require.Equal(http.StatusOK, code) | |||
// after delete, get and list different | |||
_, code, err = getKey(r, n1) | |||
require.Nil(err, "%+v", err) | |||
require.NotEqual(http.StatusOK, code) | |||
keys, code, err = listKeys(r) | |||
require.Nil(err, "%+v", err) | |||
require.Equal(http.StatusOK, code) | |||
if assert.Equal(1, len(keys)) { | |||
assert.Equal(keys[0].Name, n2) | |||
} | |||
} | |||
func setupServer() http.Handler { | |||
// make the storage with reasonable defaults | |||
cstore := cryptostore.New( | |||
cryptostore.SecretBox, | |||
memstorage.New(), | |||
keys.MustLoadCodec("english"), | |||
) | |||
// build your http server | |||
ks := server.New(cstore, "ed25519") | |||
r := mux.NewRouter() | |||
sk := r.PathPrefix("/keys").Subrouter() | |||
ks.Register(sk) | |||
return r | |||
} | |||
// return data, status code, and error | |||
func listKeys(h http.Handler) (keys.Infos, int, error) { | |||
rr := httptest.NewRecorder() | |||
req, err := http.NewRequest("GET", "/keys/", nil) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
h.ServeHTTP(rr, req) | |||
if http.StatusOK != rr.Code { | |||
return nil, rr.Code, nil | |||
} | |||
data := keys.Infos{} | |||
err = json.Unmarshal(rr.Body.Bytes(), &data) | |||
return data, rr.Code, err | |||
} | |||
func getKey(h http.Handler, name string) (*keys.Info, int, error) { | |||
rr := httptest.NewRecorder() | |||
req, err := http.NewRequest("GET", "/keys/"+name, nil) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
h.ServeHTTP(rr, req) | |||
if http.StatusOK != rr.Code { | |||
return nil, rr.Code, nil | |||
} | |||
data := keys.Info{} | |||
err = json.Unmarshal(rr.Body.Bytes(), &data) | |||
return &data, rr.Code, err | |||
} | |||
func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyResponse, int, error) { | |||
rr := httptest.NewRecorder() | |||
post := types.CreateKeyRequest{ | |||
Name: name, | |||
Passphrase: passphrase, | |||
Algo: algo, | |||
} | |||
var b bytes.Buffer | |||
err := json.NewEncoder(&b).Encode(&post) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
req, err := http.NewRequest("POST", "/keys/", &b) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
h.ServeHTTP(rr, req) | |||
if http.StatusOK != rr.Code { | |||
return nil, rr.Code, nil | |||
} | |||
data := new(types.CreateKeyResponse) | |||
err = json.Unmarshal(rr.Body.Bytes(), data) | |||
return data, rr.Code, err | |||
} | |||
func deleteKey(h http.Handler, name, passphrase string) (*types.ErrorResponse, int, error) { | |||
rr := httptest.NewRecorder() | |||
post := types.DeleteKeyRequest{ | |||
Name: name, | |||
Passphrase: passphrase, | |||
} | |||
var b bytes.Buffer | |||
err := json.NewEncoder(&b).Encode(&post) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
req, err := http.NewRequest("DELETE", "/keys/"+name, &b) | |||
if err != nil { | |||
return nil, 0, err | |||
} | |||
h.ServeHTTP(rr, req) | |||
if http.StatusOK != rr.Code { | |||
return nil, rr.Code, nil | |||
} | |||
data := types.ErrorResponse{} | |||
err = json.Unmarshal(rr.Body.Bytes(), &data) | |||
return &data, rr.Code, err | |||
} |
@ -1,35 +0,0 @@ | |||
package types | |||
import "github.com/tendermint/go-crypto/keys" | |||
// CreateKeyRequest is sent to create a new key | |||
type CreateKeyRequest struct { | |||
Name string `json:"name" validate:"required,min=4,printascii"` | |||
Passphrase string `json:"passphrase" validate:"required,min=10"` | |||
Algo string `json:"algo"` | |||
} | |||
// DeleteKeyRequest to destroy a key permanently (careful!) | |||
type DeleteKeyRequest struct { | |||
Name string `json:"name" validate:"required,min=4,printascii"` | |||
Passphrase string `json:"passphrase" validate:"required,min=10"` | |||
} | |||
// UpdateKeyRequest is sent to update the passphrase for an existing key | |||
type UpdateKeyRequest struct { | |||
Name string `json:"name" validate:"required,min=4,printascii"` | |||
OldPass string `json:"passphrase" validate:"required,min=10"` | |||
NewPass string `json:"new_passphrase" validate:"required,min=10"` | |||
} | |||
// ErrorResponse is returned for 4xx and 5xx errors | |||
type ErrorResponse struct { | |||
Success bool `json:"success"` | |||
Error string `json:"error"` // error message if Success is false | |||
Code int `json:"code"` // error code if Success is false | |||
} | |||
type CreateKeyResponse struct { | |||
Key keys.Info `json:"key"` | |||
Seed string `json:"seed_phrase"` | |||
} |
@ -1,12 +0,0 @@ | |||
package server | |||
import ( | |||
"github.com/pkg/errors" | |||
"gopkg.in/go-playground/validator.v9" | |||
) | |||
var v = validator.New() | |||
func validate(req interface{}) error { | |||
return errors.Wrap(v.Struct(req), "Validate") | |||
} |