Files

299 lines
7.9 KiB
Go
Raw Permalink Normal View History

2016-02-11 11:57:16 -08:00
package vain
2016-02-08 00:15:22 -08:00
2016-02-11 12:12:13 -08:00
import (
"encoding/json"
2016-02-11 12:12:13 -08:00
"fmt"
"net/http"
2016-04-22 23:50:23 -07:00
"net/mail"
"strings"
2016-05-14 21:30:58 -07:00
"time"
2016-04-11 20:43:18 -07:00
2016-04-23 22:16:40 -07:00
"github.com/elazarl/go-bindata-assetfs"
2016-06-24 00:06:43 -07:00
"github.com/prometheus/client_golang/prometheus"
2016-04-23 22:16:40 -07:00
2016-04-11 20:43:18 -07:00
verrors "mcquay.me/vain/errors"
2016-06-24 00:06:43 -07:00
"mcquay.me/vain/metrics"
2016-06-05 22:40:22 -07:00
"mcquay.me/vain/static"
2016-02-11 12:12:13 -08:00
)
2016-02-08 00:15:22 -08:00
2016-04-11 20:43:18 -07:00
const apiPrefix = "/api/v0/"
2016-06-03 13:42:05 -07:00
const emailSubject = "your api token"
2016-04-11 20:43:18 -07:00
var prefix map[string]string
func init() {
prefix = map[string]string{
"pkgs": apiPrefix + "db/",
"register": apiPrefix + "register/",
"confirm": apiPrefix + "confirm/",
"forgot": apiPrefix + "forgot/",
2016-04-23 22:16:40 -07:00
"static": "/_static/",
2016-04-11 20:43:18 -07:00
}
}
2016-06-03 13:42:05 -07:00
// Server serves up the http.
type Server struct {
2016-06-22 22:40:18 -07:00
db Storer
2016-06-03 13:42:05 -07:00
static string
emailTimeout time.Duration
mail Mailer
insecure bool
}
2016-02-15 01:10:14 -08:00
// NewServer populates a server, adds the routes, and returns it for use.
2016-06-22 22:40:18 -07:00
func NewServer(sm *http.ServeMux, store Storer, m Mailer, static string, emailTimeout time.Duration, insecure bool) *Server {
2016-02-15 01:10:14 -08:00
s := &Server{
2016-05-14 21:30:58 -07:00
db: store,
static: static,
emailTimeout: emailTimeout,
2016-06-03 13:42:05 -07:00
mail: m,
insecure: insecure,
2016-02-15 01:10:14 -08:00
}
2016-02-23 22:09:29 -08:00
addRoutes(sm, s)
2016-02-15 01:10:14 -08:00
return s
}
2016-02-11 12:12:13 -08:00
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
2016-06-24 00:06:43 -07:00
defer metrics.Time()()
2016-04-11 20:43:18 -07:00
if req.Method == "GET" {
2016-04-23 22:16:40 -07:00
req.ParseForm()
if _, ok := req.Form["go-get"]; !ok {
route := prefix["static"]
if p, err := s.db.Package(req.Host + req.URL.Path); err == nil {
route = p.Repo
}
http.Redirect(w, req, route, http.StatusTemporaryRedirect)
2016-04-23 22:16:40 -07:00
return
}
2016-06-03 16:44:50 -07:00
if req.URL.Path == "/" {
fmt.Fprintf(w, "<!DOCTYPE html>\n<html><head>\n")
for _, p := range s.db.Pkgs() {
fmt.Fprintf(w, "%s\n", p)
}
fmt.Fprintf(w, "</head>\n<body><p>go tool metadata in head</p></body>\n</html>\n")
} else {
p, err := s.db.Package(req.Host + req.URL.Path)
if err := verrors.ToHTTP(err); err != nil {
2016-06-24 00:06:43 -07:00
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
2016-06-03 16:44:50 -07:00
http.Error(w, err.Message, err.Code)
return
}
fmt.Fprintf(w, "<!DOCTYPE html>\n<html><head>\n%s\n</head>\n<body><p>go tool metadata in head</p></body>\n</html>\n", p)
}
2016-04-11 20:43:18 -07:00
return
}
const prefix = "Bearer "
var tok string
auth := req.Header.Get("Authorization")
if strings.HasPrefix(auth, prefix) {
tok = strings.TrimPrefix(auth, prefix)
}
if tok == "" {
http.Error(w, "missing token", http.StatusUnauthorized)
return
}
ns, err := parseNamespace(req.URL.Path)
if err != nil {
http.Error(w, fmt.Sprintf("could not parse namespace:%v", err), http.StatusBadRequest)
return
}
2016-06-22 23:01:21 -07:00
if err := verrors.ToHTTP(s.db.NSForToken(ns, Token(tok))); err != nil {
2016-06-24 00:06:43 -07:00
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
2016-04-11 20:43:18 -07:00
http.Error(w, err.Message, err.Code)
return
}
switch req.Method {
2016-02-11 12:12:13 -08:00
case "POST":
if req.URL.Path == "/" {
http.Error(w, fmt.Sprintf("invalid path %q", req.URL.Path), http.StatusBadRequest)
return
}
p := Package{}
if err := json.NewDecoder(req.Body).Decode(&p); err != nil {
2016-06-24 00:06:43 -07:00
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest))).Add(1)
2016-02-15 01:10:14 -08:00
http.Error(w, fmt.Sprintf("unable to parse json from body: %v", err), http.StatusBadRequest)
return
}
if p.Repo == "" {
2016-04-11 20:43:18 -07:00
http.Error(w, fmt.Sprintf("invalid repository %q", p.Repo), http.StatusBadRequest)
return
}
2016-02-23 22:09:29 -08:00
if p.Vcs == "" {
p.Vcs = "git"
}
if !valid(p.Vcs) {
http.Error(w, fmt.Sprintf("invalid vcs %q", p.Vcs), http.StatusBadRequest)
return
}
2016-04-11 20:43:18 -07:00
p.Path = fmt.Sprintf("%s/%s", req.Host, strings.Trim(req.URL.Path, "/"))
p.Ns = ns
if !Valid(p.Path, s.db.Pkgs()) {
http.Error(w, fmt.Sprintf("invalid path; prefix already taken %q", req.URL.Path), http.StatusConflict)
return
}
2016-04-11 20:43:18 -07:00
if err := s.db.AddPackage(p); err != nil {
2016-02-14 22:19:41 -08:00
http.Error(w, fmt.Sprintf("unable to add package: %v", err), http.StatusInternalServerError)
return
}
2016-03-01 23:34:31 -08:00
case "DELETE":
p := fmt.Sprintf("%s/%s", req.Host, strings.Trim(req.URL.Path, "/"))
2016-06-22 23:01:21 -07:00
if !s.db.PackageExists(path(p)) {
2016-03-01 23:34:31 -08:00
http.Error(w, fmt.Sprintf("package %q not found", p), http.StatusNotFound)
return
}
2016-04-11 20:43:18 -07:00
2016-06-22 23:01:21 -07:00
if err := s.db.RemovePackage(path(p)); err != nil {
2016-03-01 23:34:31 -08:00
http.Error(w, fmt.Sprintf("unable to delete package: %v", err), http.StatusInternalServerError)
return
}
2016-02-11 12:12:13 -08:00
default:
2016-03-01 23:34:31 -08:00
http.Error(w, fmt.Sprintf("unsupported method %q; accepted: POST, GET, DELETE", req.Method), http.StatusMethodNotAllowed)
2016-02-11 12:12:13 -08:00
}
}
2016-02-23 22:09:29 -08:00
2016-04-11 20:43:18 -07:00
func (s *Server) register(w http.ResponseWriter, req *http.Request) {
2016-06-24 00:06:43 -07:00
defer metrics.Time()()
2016-04-11 20:43:18 -07:00
req.ParseForm()
email, ok := req.Form["email"]
if !ok || len(email) != 1 {
http.Error(w, "must provide one email parameter", http.StatusBadRequest)
return
}
2016-04-22 23:50:23 -07:00
2016-06-03 13:42:05 -07:00
addr, err := mail.ParseAddress(email[0])
if err != nil {
2016-04-22 23:50:23 -07:00
http.Error(w, fmt.Sprintf("invalid email detected: %v", err), http.StatusBadRequest)
return
}
2016-06-22 23:01:21 -07:00
tok, err := s.db.Register(Email(addr.Address))
2016-04-11 20:43:18 -07:00
if err := verrors.ToHTTP(err); err != nil {
2016-06-24 00:06:43 -07:00
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
2016-04-11 20:43:18 -07:00
http.Error(w, err.Message, err.Code)
return
}
2016-06-03 13:42:05 -07:00
2016-04-17 18:26:45 -07:00
proto := "https"
2016-06-03 13:42:05 -07:00
if s.insecure {
2016-04-17 18:26:45 -07:00
proto = "http"
}
2016-05-22 11:41:51 -07:00
resp := struct {
Msg string `json:"msg"`
}{
Msg: "please check your email\n",
}
2016-06-03 13:42:05 -07:00
err = s.mail.Send(
*addr,
"your api string",
fmt.Sprintf("%s://%s/api/v0/confirm/%+v", proto, req.Host, tok),
)
if err != nil {
resp.Msg = fmt.Sprintf("problem sending email: %v", err)
w.WriteHeader(http.StatusInternalServerError)
}
2016-05-22 11:41:51 -07:00
w.Header().Set("Content-type", "application/json")
json.NewEncoder(w).Encode(resp)
2016-04-11 20:43:18 -07:00
}
func (s *Server) confirm(w http.ResponseWriter, req *http.Request) {
2016-06-24 00:06:43 -07:00
defer metrics.Time()()
2016-04-11 20:43:18 -07:00
tok := req.URL.Path[len(prefix["confirm"]):]
tok = strings.TrimRight(tok, "/")
if tok == "" {
http.Error(w, "must provide one email parameter", http.StatusBadRequest)
return
}
2016-06-22 23:01:21 -07:00
ttok, err := s.db.Confirm(Token(tok))
2016-04-11 20:43:18 -07:00
if err := verrors.ToHTTP(err); err != nil {
2016-06-24 00:06:43 -07:00
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
2016-04-11 20:43:18 -07:00
http.Error(w, err.Message, err.Code)
return
}
2016-06-22 23:01:21 -07:00
fmt.Fprintf(w, "new token: %s\n", ttok)
2016-04-11 20:43:18 -07:00
}
func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
2016-06-24 00:06:43 -07:00
defer metrics.Time()()
2016-04-11 20:43:18 -07:00
req.ParseForm()
email, ok := req.Form["email"]
if !ok || len(email) != 1 {
http.Error(w, "must provide one email parameter", http.StatusBadRequest)
return
}
2016-04-22 23:50:23 -07:00
2016-06-03 13:42:05 -07:00
addr, err := mail.ParseAddress(email[0])
if err != nil {
2016-04-22 23:50:23 -07:00
http.Error(w, fmt.Sprintf("invalid email detected: %v", err), http.StatusBadRequest)
return
}
2016-06-22 23:01:21 -07:00
tok, err := s.db.Forgot(Email(addr.Address), s.emailTimeout)
2016-04-11 20:43:18 -07:00
if err := verrors.ToHTTP(err); err != nil {
2016-06-24 00:06:43 -07:00
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
2016-04-11 20:43:18 -07:00
http.Error(w, err.Message, err.Code)
return
}
2016-06-03 13:42:05 -07:00
proto := "https"
if s.insecure {
proto = "http"
}
2016-05-22 11:41:51 -07:00
resp := struct {
Msg string `json:"msg"`
}{
Msg: "please check your email\n",
}
2016-06-03 13:42:05 -07:00
err = s.mail.Send(
*addr,
emailSubject,
fmt.Sprintf("%s://%s/api/v0/confirm/%+v", proto, req.Host, tok),
)
if err != nil {
resp.Msg = fmt.Sprintf("problem sending email: %v", err)
w.WriteHeader(http.StatusInternalServerError)
}
2016-05-22 11:41:51 -07:00
w.Header().Set("Content-type", "application/json")
json.NewEncoder(w).Encode(resp)
2016-04-11 20:43:18 -07:00
}
2016-06-03 16:44:50 -07:00
2016-04-11 20:43:18 -07:00
func (s *Server) pkgs(w http.ResponseWriter, req *http.Request) {
2016-06-24 00:06:43 -07:00
defer metrics.Time()()
2016-02-23 22:09:29 -08:00
w.Header().Set("Content-type", "application/json")
2016-04-11 20:43:18 -07:00
json.NewEncoder(w).Encode(s.db.Pkgs())
2016-02-23 22:09:29 -08:00
}
func addRoutes(sm *http.ServeMux, s *Server) {
sm.Handle("/", s)
2016-06-24 00:06:43 -07:00
sm.Handle("/metrics", prometheus.Handler())
2016-04-11 20:43:18 -07:00
2016-04-23 22:16:40 -07:00
if s.static == "" {
sm.Handle(
prefix["static"],
http.FileServer(
&assetfs.AssetFS{
2016-06-05 22:40:22 -07:00
Asset: static.Asset,
AssetDir: static.AssetDir,
AssetInfo: static.AssetInfo,
2016-04-23 22:16:40 -07:00
},
),
)
} else {
sm.Handle(
prefix["static"],
http.StripPrefix(
prefix["static"],
http.FileServer(http.Dir(s.static)),
),
)
}
2016-04-11 20:43:18 -07:00
sm.HandleFunc(prefix["pkgs"], s.pkgs)
sm.HandleFunc(prefix["register"], s.register)
sm.HandleFunc(prefix["confirm"], s.confirm)
sm.HandleFunc(prefix["forgot"], s.forgot)
2016-02-23 22:09:29 -08:00
}