cmd: make wasmc client, add whoami route
This commit is contained in:
parent
2ac6890f3f
commit
84b5bad077
|
@ -0,0 +1,132 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func getUserAndToken(tokenBody string) (User, Token, error) {
|
||||
var t Token
|
||||
var u User
|
||||
|
||||
err := db.Where("body = ?", tokenBody).First(&t).Error
|
||||
if err != nil {
|
||||
return User{}, Token{}, fmt.Errorf("error when fetching token: %w", err)
|
||||
}
|
||||
|
||||
err = db.Where("id = ?", t.UserID).First(&u).Error
|
||||
if err != nil {
|
||||
return User{}, Token{}, fmt.Errorf("error when fetching user: %w", err)
|
||||
}
|
||||
|
||||
return u, t, nil
|
||||
}
|
||||
|
||||
func logoutUser(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("wasmcloud-token")
|
||||
if err != nil {
|
||||
log.Printf("error getting cookie wasmcloud-token: %v", err)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
u, t, err := getUserAndToken(cookie.Value)
|
||||
if err != nil {
|
||||
http.Error(w, "unknown authentication token", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Delete(&t).Error
|
||||
if err != nil {
|
||||
log.Printf("can't delete token %d for %s: %v", t.ID, u.Username, err)
|
||||
http.Error(w, "internal server error, please contact support", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{Name: "wasmcloud-token", MaxAge: -1})
|
||||
log.Printf("logout for %s", u.Username)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func loginUser(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseMultipartForm(1024)
|
||||
|
||||
for _, val := range []string{"username", "password"} {
|
||||
if r.FormValue(val) == "" {
|
||||
http.Error(w, "missing form data "+val, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uname := r.FormValue("username")
|
||||
pw := r.FormValue("password")
|
||||
|
||||
var u User
|
||||
err := db.Where("username = ?", uname).First(&u).Error
|
||||
if err != nil {
|
||||
log.Printf("can't lookup user %s: %v", uname, err)
|
||||
http.Error(w, "unknown user/password", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(u.CryptedPassword, []byte(pw))
|
||||
if err != nil {
|
||||
log.Printf("wrong password for %s: %v", uname, err)
|
||||
http.Error(w, "unknown user/password", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
t, err := makeToken(u)
|
||||
if err != nil {
|
||||
log.Printf("can't make token: %v", err)
|
||||
http.Error(w, "internal server error, contact support", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("login for %s", u.Username)
|
||||
http.SetCookie(w, t.ToCookie())
|
||||
http.Redirect(w, r, "/control/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseMultipartForm(1024)
|
||||
|
||||
for _, val := range []string{"username", "password", "email"} {
|
||||
if r.FormValue(val) == "" {
|
||||
http.Error(w, "missing form data "+val, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cryptPW, err := bcrypt.GenerateFromPassword([]byte(r.FormValue("password")), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
u := User{
|
||||
Username: r.FormValue("username"),
|
||||
CryptedPassword: cryptPW,
|
||||
Email: r.FormValue("email"),
|
||||
}
|
||||
|
||||
err = db.Save(&u).Error
|
||||
if err != nil {
|
||||
log.Printf("can't save user: %v", err)
|
||||
http.Error(w, "can't save new user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tok, err := makeToken(u)
|
||||
if err != nil {
|
||||
log.Printf("can't make token: %v", err)
|
||||
http.Error(w, "internal server error, contact support", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("created user %s", u.Username)
|
||||
http.SetCookie(w, tok.ToCookie())
|
||||
http.Redirect(w, r, "/control/", http.StatusSeeOther)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
var apiConfig *Config
|
||||
|
||||
func withAPI(req *http.Request) {
|
||||
if apiConfig == nil {
|
||||
var err error
|
||||
apiConfig, err = loadConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
req.AddCookie(&http.Cookie{Name: "wasmcloud-token", Value: apiConfig.Token})
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
func loadConfig() (*Config, error) {
|
||||
var cfg Config
|
||||
fin, err := os.Open(*configLocation)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
defer fin.Close()
|
||||
|
||||
err = json.NewDecoder(fin).Decode(&cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func saveConfig(cfg *Config) error {
|
||||
fout, err := os.Create(*configLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fout.Close()
|
||||
|
||||
return json.NewEncoder(fout).Encode(cfg)
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/manifoldco/promptui"
|
||||
)
|
||||
|
||||
type loginCmd struct {
|
||||
username string
|
||||
}
|
||||
|
||||
func (loginCmd) Name() string { return "login" }
|
||||
func (loginCmd) Synopsis() string { return "logs into wasmcloud" }
|
||||
func (loginCmd) Usage() string {
|
||||
return `wasmc login [options]
|
||||
|
||||
$ wasmc login -username Cadey
|
||||
|
||||
Logs into the wasmcloud API server and saves credentials to the global
|
||||
configuration file. The password is always prompted by standard input.
|
||||
|
||||
Flags:
|
||||
`
|
||||
}
|
||||
|
||||
func (l *loginCmd) SetFlags(fs *flag.FlagSet) {
|
||||
fs.StringVar(&l.username, "username", "", "wasmcloud username")
|
||||
}
|
||||
|
||||
func (l *loginCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
cfg, err := loadConfig()
|
||||
if err != nil {
|
||||
log.Printf("error loading config: %v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
if l.username == "" {
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Username",
|
||||
}
|
||||
|
||||
result, err := prompt.Run()
|
||||
if err != nil {
|
||||
log.Printf("error reading username: %v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
l.username = result
|
||||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Password",
|
||||
Mask: '*',
|
||||
}
|
||||
password, err := prompt.Run()
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
writer.WriteField("username", l.username)
|
||||
writer.WriteField("password", password)
|
||||
writer.Close()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, *apiServer+"/login", body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("can't log into server %s: %v", *apiServer, err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusSeeOther {
|
||||
log.Printf("wanted %d but got %d, see above", http.StatusSeeOther, resp.StatusCode)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
var cookie *http.Cookie
|
||||
for _, ck := range resp.Cookies() {
|
||||
if ck.Name == "wasmcloud-token" {
|
||||
cookie = ck
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if cookie == nil {
|
||||
log.Printf("impossible state? server didn't send us a token")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
cfg.Token = cookie.Value
|
||||
|
||||
log.Printf("success! Logged in as %s, token expires at %s", l.username, cookie.Expires.Format(time.RFC3339))
|
||||
|
||||
saveConfig(cfg)
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
var (
|
||||
apiServer = flag.String("api-server", "http://wasmcloud.kahless.cetacean.club:3002", "default API server")
|
||||
configLocation = flag.String("config", filepath.Join(os.Getenv("HOME"), ".wasmc.json"), "default config location")
|
||||
)
|
||||
|
||||
func main() {
|
||||
subcommands.Register(subcommands.HelpCommand(), "")
|
||||
subcommands.Register(subcommands.FlagsCommand(), "")
|
||||
subcommands.Register(subcommands.CommandsCommand(), "")
|
||||
subcommands.Register(&loginCmd{}, "auth")
|
||||
subcommands.Register(&whoamiCmd{}, "auth")
|
||||
subcommands.ImportantFlag("api-server")
|
||||
subcommands.ImportantFlag("config")
|
||||
|
||||
flag.Parse()
|
||||
ctx := context.Background()
|
||||
os.Exit(int(subcommands.Execute(ctx)))
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
type whoamiCmd struct {
|
||||
json bool
|
||||
}
|
||||
|
||||
func (whoamiCmd) Name() string { return "whoami" }
|
||||
func (whoamiCmd) Synopsis() string { return "show information about currently logged in user" }
|
||||
func (whoamiCmd) Usage() string {
|
||||
return `wasmc whoami [options]
|
||||
|
||||
$ wasmc whoami
|
||||
$ wasmc whoami -json
|
||||
|
||||
Returns information about the currently logged in user.
|
||||
|
||||
Flags:
|
||||
`
|
||||
}
|
||||
|
||||
func (w *whoamiCmd) SetFlags(fs *flag.FlagSet) {
|
||||
fs.BoolVar(&w.json, "json", false, "if set, dump information in json")
|
||||
}
|
||||
|
||||
func (w *whoamiCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
req, err := http.NewRequest(http.MethodGet, *apiServer+"/api/whoami", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
withAPI(req)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("error fetching data: %v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if w.json {
|
||||
io.Copy(os.Stdout, resp.Body)
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
type apiResp struct {
|
||||
ID int `json:"ID"`
|
||||
CreatedAt time.Time `json:"CreatedAt"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt"`
|
||||
Username string `json:"Username"`
|
||||
Email string `json:"Email"`
|
||||
IsAdmin bool `json:"IsAdmin"`
|
||||
CanCreateHandlers bool `json:"CanCreateHandlers"`
|
||||
}
|
||||
|
||||
var result apiResp
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Username: %s\nEmail: %s\nIs Admin: %v\nCan Create Handlers: %v\n", result.Username, result.Email, result.IsAdmin, result.CanCreateHandlers)
|
||||
return subcommands.ExitSuccess
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func apiWhoami(w http.ResponseWriter, r *http.Request, u *User) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(u)
|
||||
}
|
|
@ -52,7 +52,13 @@ func logoutUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func loginUser(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
showLoginForm(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseMultipartForm(1024)
|
||||
defer r.Body.Close()
|
||||
|
||||
for _, val := range []string{"username", "password"} {
|
||||
if r.FormValue(val) == "" {
|
||||
|
@ -92,7 +98,13 @@ func loginUser(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
showRegisterForm(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseMultipartForm(1024)
|
||||
defer r.Body.Close()
|
||||
|
||||
for _, val := range []string{"username", "password", "email"} {
|
||||
if r.FormValue(val) == "" {
|
||||
|
|
|
@ -37,16 +37,16 @@ func main() {
|
|||
rtr := mux.NewRouter()
|
||||
|
||||
// auth
|
||||
rtr.HandleFunc("/register", showRegisterForm).Methods("GET")
|
||||
rtr.HandleFunc("/register", registerUser).Methods("POST")
|
||||
rtr.HandleFunc("/login", showLoginForm).Methods("GET")
|
||||
rtr.HandleFunc("/login", loginUser).Methods("POST")
|
||||
rtr.HandleFunc("/register", registerUser)
|
||||
rtr.HandleFunc("/login", loginUser)
|
||||
rtr.HandleFunc("/logout", logoutUser)
|
||||
|
||||
// pages
|
||||
rtr.HandleFunc("/", unauthenticatedShowAPage("index"))
|
||||
rtr.HandleFunc("/control/", authenticatedShowAPage("controlindex"))
|
||||
|
||||
rtr.HandleFunc("/api/whoami", makeHandler(true, apiWhoami))
|
||||
|
||||
rtr.PathPrefix("/static/").Handler(http.FileServer(http.Dir(".")))
|
||||
|
||||
log.Printf("listening on http://wasmcloud.kahless.cetacean.club:%s", *port)
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
type User struct {
|
||||
gorm.Model
|
||||
Username string `gorm:"unique;not null"`
|
||||
CryptedPassword []byte `gorm:"not null"`
|
||||
CryptedPassword []byte `gorm:"not null" json:"-"`
|
||||
Email string `gorm:"unique;not null"`
|
||||
IsAdmin bool `gorm:"default:false"`
|
||||
CanCreateHandlers bool `gorm:"default:false"`
|
||||
|
|
22
go.sum
22
go.sum
|
@ -5,12 +5,17 @@ cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7h
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -40,6 +45,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
|||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -48,9 +54,13 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
|||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
|
@ -72,6 +82,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
|||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -83,6 +95,14 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8=
|
||||
github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
|
@ -143,6 +163,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/theplant/cldr v0.0.0-20190423050709-9f76f7ce4ee8 h1:di0cR5qqo2DllBMwmP75kZpUX6dAXhsn1O2dshQfMaA=
|
||||
github.com/theplant/cldr v0.0.0-20190423050709-9f76f7ce4ee8/go.mod h1:MIL7SmF8wRAYDn+JexczVRUiJXTCi4VbQavsCKWKwXI=
|
||||
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -178,6 +199,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
|
|
Loading…
Reference in New Issue