Created
August 27, 2018 20:11
-
-
Save nkatsaros/3929d3f40dd97bc46057e61b473ce879 to your computer and use it in GitHub Desktop.
gophercon sympatico, auth extracted
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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