Skip to content

Instantly share code, notes, and snippets.

@nkatsaros
Created August 27, 2018 20:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nkatsaros/3929d3f40dd97bc46057e61b473ce879 to your computer and use it in GitHub Desktop.
Save nkatsaros/3929d3f40dd97bc46057e61b473ce879 to your computer and use it in GitHub Desktop.
gophercon sympatico, auth extracted
diff --git a/cmd/auth/logging.go b/cmd/auth/logging.go
new file mode 100644
index 0000000..712f806
--- /dev/null
+++ b/cmd/auth/logging.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/go-kit/kit/log"
+
+ "github.com/peterbourgon/sympatico/internal/ctxlog"
+)
+
+type loggingMiddleware struct {
+ next http.Handler
+ logger log.Logger
+}
+
+func newLoggingMiddleware(next http.Handler, logger log.Logger) *loggingMiddleware {
+ return &loggingMiddleware{next, logger}
+}
+
+func (mw *loggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ var (
+ iw = &interceptingWriter{http.StatusOK, w}
+ ctx, ctxlog = ctxlog.New(r.Context(), "http_method", r.Method, "http_path", r.URL.Path)
+ )
+
+ defer func(begin time.Time) {
+ ctxlog.Log("http_status_code", iw.code, "http_duration", time.Since(begin))
+ mw.logger.Log(ctxlog.Keyvals()...)
+ }(time.Now())
+
+ mw.next.ServeHTTP(iw, r.WithContext(ctx))
+}
+
+type interceptingWriter struct {
+ code int
+ http.ResponseWriter
+}
+
+func (iw *interceptingWriter) WriteHeader(code int) {
+ iw.code = code
+ iw.ResponseWriter.WriteHeader(code)
+}
diff --git a/cmd/auth/main.go b/cmd/auth/main.go
new file mode 100644
index 0000000..6eeae44
--- /dev/null
+++ b/cmd/auth/main.go
@@ -0,0 +1,124 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net/http"
+ "os"
+ "os/signal"
+ "syscall"
+ "text/tabwriter"
+ "time"
+
+ "github.com/go-kit/kit/log"
+ "github.com/gorilla/mux"
+ "github.com/oklog/run"
+ "github.com/pkg/errors"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+
+ "github.com/peterbourgon/sympatico/internal/auth"
+)
+
+func main() {
+ fs := flag.NewFlagSet("auth", flag.ExitOnError)
+ var (
+ apiAddr = fs.String("api", "127.0.0.1:8081", "HTTP API listen address")
+ authURN = fs.String("auth-urn", "file:auth.db", "URN for auth DB")
+ )
+ fs.Usage = usageFor(fs, "auth [flags]")
+ fs.Parse(os.Args[1:])
+
+ var logger log.Logger
+ {
+ logger = log.NewLogfmtLogger(os.Stdout)
+ logger = log.With(logger, "ts", log.DefaultTimestampUTC)
+ }
+
+ var authEventsTotal *prometheus.CounterVec
+ {
+ authEventsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
+ Subsystem: "auth",
+ Name: "events_total",
+ Help: "Total number of auth events.",
+ }, []string{"method", "success"})
+ }
+
+ var authsvc *auth.Service
+ {
+ authrepo, err := auth.NewSQLiteRepository(*authURN)
+ if err != nil {
+ logger.Log("during", "auth.NewSQLiteRepository", "err", err)
+ os.Exit(1)
+ }
+ authsvc = auth.NewService(authrepo, authEventsTotal)
+ }
+
+ var authserver *auth.HTTPServer
+ {
+ authserver = auth.NewHTTPServer(authsvc)
+ }
+
+ var api http.Handler
+ {
+ r := mux.NewRouter()
+ r.PathPrefix("/auth/").Handler(http.StripPrefix("/auth", authserver))
+ api = newLoggingMiddleware(r, logger)
+ }
+
+ var g run.Group
+ {
+ mux := http.NewServeMux()
+ mux.Handle("/metrics", promhttp.Handler())
+ mux.Handle("/", api)
+ server := &http.Server{
+ Addr: *apiAddr,
+ Handler: mux,
+ }
+ g.Add(func() error {
+ logger.Log("component", "API", "addr", *apiAddr)
+ return server.ListenAndServe()
+ }, func(error) {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+ defer cancel()
+ server.Shutdown(ctx)
+ })
+ }
+ {
+ ctx, cancel := context.WithCancel(context.Background())
+ g.Add(func() error {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case sig := <-c:
+ return errors.Errorf("received signal %s", sig)
+ }
+ }, func(error) {
+ cancel()
+ })
+ }
+ logger.Log("exit", g.Run())
+}
+
+func usageFor(fs *flag.FlagSet, short string) func() {
+ return func() {
+ fmt.Fprintf(os.Stdout, "USAGE\n")
+ fmt.Fprintf(os.Stdout, " %s\n", short)
+ fmt.Fprintf(os.Stdout, "\n")
+ fmt.Fprintf(os.Stdout, "FLAGS\n")
+ tw := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
+ fs.VisitAll(func(f *flag.Flag) {
+ def := f.DefValue
+ if def == "" {
+ def = "..."
+ }
+ fmt.Fprintf(tw, " -%s %s\t%s\n", f.Name, f.DefValue, f.Usage)
+ })
+ tw.Flush()
+ fmt.Fprintf(os.Stderr, "\n")
+ }
+}
diff --git a/cmd/monolith/main.go b/cmd/monolith/main.go
index b0e493c..a9ff477 100644
--- a/cmd/monolith/main.go
+++ b/cmd/monolith/main.go
@@ -4,9 +4,13 @@ import (
"context"
"flag"
"fmt"
+ "io"
+ "io/ioutil"
"net/http"
+ "net/url"
"os"
"os/signal"
+ "path"
"syscall"
"text/tabwriter"
"time"
@@ -19,7 +23,6 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
- "github.com/peterbourgon/sympatico/internal/auth"
"github.com/peterbourgon/sympatico/internal/dna"
)
@@ -27,7 +30,7 @@ func main() {
fs := flag.NewFlagSet("monolith", flag.ExitOnError)
var (
apiAddr = fs.String("api", "127.0.0.1:8080", "HTTP API listen address")
- authURN = fs.String("auth-urn", "file:auth.db", "URN for auth DB")
+ authURL = fs.String("auth-url", "http://127.0.0.1:8081", "URL for auth service")
dnaURN = fs.String("dna-urn", "file:dna.db", "URN for DNA DB")
)
fs.Usage = usageFor(fs, "monolith [flags]")
@@ -39,14 +42,8 @@ func main() {
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
}
- var authEventsTotal *prometheus.CounterVec
var dnaCheckDuration *prometheus.HistogramVec
{
- authEventsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
- Subsystem: "auth",
- Name: "events_total",
- Help: "Total number of auth events.",
- }, []string{"method", "success"})
dnaCheckDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Subsystem: "dna",
Name: "check_duration_seconds",
@@ -55,21 +52,6 @@ func main() {
}, []string{"success"})
}
- var authsvc *auth.Service
- {
- authrepo, err := auth.NewSQLiteRepository(*authURN)
- if err != nil {
- logger.Log("during", "auth.NewSQLiteRepository", "err", err)
- os.Exit(1)
- }
- authsvc = auth.NewService(authrepo, authEventsTotal)
- }
-
- var authserver *auth.HTTPServer
- {
- authserver = auth.NewHTTPServer(authsvc)
- }
-
var dnasvc *dna.Service
{
dnarepo, err := dna.NewSQLiteRepository(*dnaURN)
@@ -77,7 +59,7 @@ func main() {
logger.Log("during", "dna.NewSQLiteRepository", "err", err)
os.Exit(1)
}
- dnasvc = dna.NewService(dnarepo, authsvc, dnaCheckDuration)
+ dnasvc = dna.NewService(dnarepo, &authClient{*authURL}, dnaCheckDuration)
}
var dnaserver *dna.HTTPServer
@@ -88,7 +70,6 @@ func main() {
var api http.Handler
{
r := mux.NewRouter()
- r.PathPrefix("/auth/").Handler(http.StripPrefix("/auth", authserver))
r.PathPrefix("/dna/").Handler(http.StripPrefix("/dna", dnaserver))
api = newLoggingMiddleware(r, logger)
}
@@ -147,3 +128,31 @@ func usageFor(fs *flag.FlagSet, short string) func() {
fmt.Fprintf(os.Stderr, "\n")
}
}
+
+type authClient struct {
+ URL string
+}
+
+func (c *authClient) Validate(ctx context.Context, user, token string) error {
+ u, err := url.Parse(c.URL)
+ if err != nil {
+ return errors.Wrapf(err, "failed to parse auth url")
+ }
+ u.Path = path.Join(u.Path, "auth", "validate")
+ q := u.Query()
+ q.Set("user", user)
+ q.Set("token", token)
+ u.RawQuery = q.Encode()
+ resp, err := http.Post(u.String(), "text/plain", nil)
+ if err != nil {
+ return errors.Wrapf(err, "bad validate request")
+ }
+ defer resp.Body.Close()
+ if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
+ return errors.Wrapf(err, "failed to discard body")
+ }
+ if resp.StatusCode != 200 {
+ return errors.Wrapf(err, "not validated")
+ }
+ return nil
+}
diff --git a/internal/auth/http_server.go b/internal/auth/http_server.go
index 904d339..6f7dde3 100644
--- a/internal/auth/http_server.go
+++ b/internal/auth/http_server.go
@@ -23,6 +23,7 @@ func NewHTTPServer(service *Service) *HTTPServer {
r.Methods("POST").Path("/signup").HandlerFunc(s.handleSignup)
r.Methods("POST").Path("/login").HandlerFunc(s.handleLogin)
r.Methods("POST").Path("/logout").HandlerFunc(s.handleLogout)
+ r.Methods("POST").Path("/validate").HandlerFunc(s.handleValidate)
}
s.router = r
return s
@@ -78,3 +79,20 @@ func (s *HTTPServer) handleLogout(w http.ResponseWriter, r *http.Request) {
}
fmt.Fprintln(w, "logout successful")
}
+
+func (s *HTTPServer) handleValidate(w http.ResponseWriter, r *http.Request) {
+ var (
+ user = r.URL.Query().Get("user")
+ token = r.URL.Query().Get("token")
+ )
+ err := s.service.Validate(r.Context(), user, token)
+ if err == ErrBadAuth {
+ http.Error(w, err.Error(), http.StatusUnauthorized)
+ return
+ }
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ fmt.Fprintln(w, "validate successful")
+}
diff --git a/monolith_test.fish b/monolith_test.fish
index e62f646..98eb0cb 100755
--- a/monolith_test.fish
+++ b/monolith_test.fish
@@ -1,12 +1,12 @@
#!/usr/bin/env fish
-curl -Ss -XPOST "localhost:8080/auth/signup?user=charlie&pass=abc123"
+curl -Ss -XPOST "localhost:8081/auth/signup?user=charlie&pass=abc123"
echo -n "user=charlie count, want 1, have "
echo "SELECT Count(*) FROM credentials WHERE user = 'charlie';" | sqlite3 auth.db
-curl -Ss -XPOST "localhost:8080/auth/login?user=charlie&pass=abc123" | read token
+curl -Ss -XPOST "localhost:8081/auth/login?user=charlie&pass=abc123" | read token
echo -n "after login, token count for charlie, want 1, have "
echo "SELECT Count(*) FROM tokens WHERE user = 'charlie';" | sqlite3 auth.db
@@ -22,7 +22,7 @@ if test "$selected" = "$sequence" ; echo "sequence check pass" ; else ; echo "se
curl -Ss -XGET "localhost:8080/dna/check?user=charlie&token=$token&subsequence=$subsequence"
-curl -Ss -XPOST "localhost:8080/auth/logout?user=charlie&token=$token"
+curl -Ss -XPOST "localhost:8081/auth/logout?user=charlie&token=$token"
echo -n "after logout, token count for charlie, want 0, have "
echo "SELECT Count(*) FROM tokens WHERE user = 'charlie';" | sqlite3 auth.db
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment