add support for emails. #2

Closed
sm wants to merge 1 commits from email into master
4 changed files with 125 additions and 33 deletions

View File

@ -23,7 +23,7 @@ func TestAdd(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
if err != nil { if err != nil {
@ -169,7 +169,7 @@ func TestInvalidPath(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
if err != nil { if err != nil {
@ -201,7 +201,7 @@ func TestCannotDuplicateExistingPath(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
@ -247,7 +247,7 @@ func TestCannotAddExistingSubPath(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
@ -295,7 +295,7 @@ func TestMissingRepo(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
@ -328,7 +328,7 @@ func TestBadJson(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
@ -361,7 +361,7 @@ func TestNoAuth(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
u := fmt.Sprintf("%s/foo", ts.URL) u := fmt.Sprintf("%s/foo", ts.URL)
@ -390,7 +390,7 @@ func TestBadVcs(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
@ -421,7 +421,7 @@ func TestUnsupportedMethod(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")
@ -453,7 +453,7 @@ func TestDelete(t *testing.T) {
defer done() defer done()
sm := http.NewServeMux() sm := http.NewServeMux()
NewServer(sm, db, "", window) NewServer(sm, db, nil, "", window, false)
ts := httptest.NewServer(sm) ts := httptest.NewServer(sm)
tok, err := db.addUser("sm@example.org") tok, err := db.addUser("sm@example.org")

View File

@ -56,6 +56,7 @@ const usage = "vaind [init] <dbname>"
type config struct { type config struct {
Port int Port int
Insecure bool
Cert string Cert string
Key string Key string
@ -63,6 +64,11 @@ type config struct {
Static string Static string
EmailTimeout time.Duration `envconfig:"email_timeout"` EmailTimeout time.Duration `envconfig:"email_timeout"`
SMTPHost string `envconfig:"smtp_host"`
SMTPPort int `envconfig:"smtp_port"`
From string
} }
func main() { func main() {
@ -101,6 +107,7 @@ func main() {
c := &config{ c := &config{
Port: 4040, Port: 4040,
EmailTimeout: 5 * time.Minute, EmailTimeout: 5 * time.Minute,
SMTPPort: 25,
} }
if err := envconfig.Process("vain", c); err != nil { if err := envconfig.Process("vain", c); err != nil {
fmt.Fprintf(os.Stderr, "problem processing environment: %v", err) fmt.Fprintf(os.Stderr, "problem processing environment: %v", err)
@ -114,6 +121,13 @@ func main() {
os.Exit(0) os.Exit(0)
} }
} }
m, err := vain.NewEmail(c.From, c.SMTPHost, c.SMTPPort)
if err != nil {
fmt.Fprintf(os.Stderr, "problem initializing mailer: %v", err)
os.Exit(1)
}
hostname := "localhost" hostname := "localhost"
if hn, err := os.Hostname(); err != nil { if hn, err := os.Hostname(); err != nil {
log.Printf("problem getting hostname: %v", err) log.Printf("problem getting hostname: %v", err)
@ -122,7 +136,7 @@ func main() {
} }
log.Printf("serving at: http://%s:%d/", hostname, c.Port) log.Printf("serving at: http://%s:%d/", hostname, c.Port)
sm := http.NewServeMux() sm := http.NewServeMux()
vain.NewServer(sm, db, c.Static, c.EmailTimeout) vain.NewServer(sm, db, m, c.Static, c.EmailTimeout, c.Insecure)
addr := fmt.Sprintf(":%d", c.Port) addr := fmt.Sprintf(":%d", c.Port)
if c.Cert == "" || c.Key == "" { if c.Cert == "" || c.Key == "" {

54
mail.go Normal file
View File

@ -0,0 +1,54 @@
package vain
import (
"bytes"
"fmt"
"net/mail"
"net/smtp"
)
type Mailer interface {
Send(to mail.Address, msg string) error
}
func NewEmail(from, host string, port int) (*Email, error) {
if _, err := mail.ParseAddress(from); err != nil {
return nil, fmt.Errorf("can't parse an email address for 'from': %v", err)
}
r := &Email{
host: host,
port: port,
from: from,
}
return r, nil
}
type Email struct {
host string
port int
from string
}
func (e Email) Send(to mail.Address, msg string) error {
c, err := smtp.Dial(fmt.Sprintf("%s:%d", e.host, e.port))
if err != nil {
return fmt.Errorf("couldn't dial mail server: %v", err)
}
defer c.Close()
if err := c.Mail(e.from); err != nil {
return err
}
if err := c.Rcpt(to.String()); err != nil {
return err
}
wc, err := c.Data()
if err != nil {
return fmt.Errorf("problem sending mail: %v", err)
}
buf := bytes.NewBufferString("Subject: your api key\n\n" + msg)
buf.WriteTo(wc)
if err := c.Quit(); err != nil {
return nil
}
return err
}

View File

@ -3,7 +3,6 @@ package vain
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"net/http" "net/http"
"net/mail" "net/mail"
"strings" "strings"
@ -28,22 +27,26 @@ func init() {
} }
} }
// NewServer populates a server, adds the routes, and returns it for use.
func NewServer(sm *http.ServeMux, store *DB, static string, emailTimeout time.Duration) *Server {
s := &Server{
db: store,
static: static,
emailTimeout: emailTimeout,
}
addRoutes(sm, s)
return s
}
// Server serves up the http. // Server serves up the http.
type Server struct { type Server struct {
db *DB db *DB
static string static string
emailTimeout time.Duration emailTimeout time.Duration
mail Mailer
insecure bool
}
// NewServer populates a server, adds the routes, and returns it for use.
func NewServer(sm *http.ServeMux, store *DB, m Mailer, static string, emailTimeout time.Duration, insecure bool) *Server {
s := &Server{
db: store,
static: static,
emailTimeout: emailTimeout,
mail: m,
insecure: insecure,
}
addRoutes(sm, s)
return s
} }
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@ -138,27 +141,36 @@ func (s *Server) register(w http.ResponseWriter, req *http.Request) {
return return
} }
addr := email[0] addr, err := mail.ParseAddress(email[0])
if _, err := mail.ParseAddress(addr); err != nil { if err != nil {
http.Error(w, fmt.Sprintf("invalid email detected: %v", err), http.StatusBadRequest) http.Error(w, fmt.Sprintf("invalid email detected: %v", err), http.StatusBadRequest)
return return
} }
tok, err := s.db.Register(addr) tok, err := s.db.Register(addr.Address)
if err := verrors.ToHTTP(err); err != nil { if err := verrors.ToHTTP(err); err != nil {
http.Error(w, err.Message, err.Code) http.Error(w, err.Message, err.Code)
return return
} }
proto := "https" proto := "https"
if req.TLS == nil { if s.insecure {
proto = "http" proto = "http"
} }
log.Printf("%s://%s/api/v0/confirm/%+v", proto, req.Host, tok)
resp := struct { resp := struct {
Msg string `json:"msg"` Msg string `json:"msg"`
}{ }{
Msg: "please check your email\n", Msg: "please check your email\n",
} }
err = s.mail.Send(
*addr,
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)
}
w.Header().Set("Content-type", "application/json") w.Header().Set("Content-type", "application/json")
json.NewEncoder(w).Encode(resp) json.NewEncoder(w).Encode(resp)
} }
@ -186,23 +198,35 @@ func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
return return
} }
addr := email[0] addr, err := mail.ParseAddress(email[0])
if _, err := mail.ParseAddress(addr); err != nil { if err != nil {
http.Error(w, fmt.Sprintf("invalid email detected: %v", err), http.StatusBadRequest) http.Error(w, fmt.Sprintf("invalid email detected: %v", err), http.StatusBadRequest)
return return
} }
tok, err := s.db.forgot(addr, s.emailTimeout) tok, err := s.db.forgot(addr.Address, s.emailTimeout)
if err := verrors.ToHTTP(err); err != nil { if err := verrors.ToHTTP(err); err != nil {
http.Error(w, err.Message, err.Code) http.Error(w, err.Message, err.Code)
return return
} }
log.Printf("http://%s/api/v0/confirm/%+v", req.Host, tok) proto := "https"
if s.insecure {
proto = "http"
}
resp := struct { resp := struct {
Msg string `json:"msg"` Msg string `json:"msg"`
}{ }{
Msg: "please check your email\n", Msg: "please check your email\n",
} }
err = s.mail.Send(
*addr,
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)
}
w.Header().Set("Content-type", "application/json") w.Header().Set("Content-type", "application/json")
json.NewEncoder(w).Encode(resp) json.NewEncoder(w).Encode(resp)
} }