From abf9811aaaff87c0b3c3668e395ec22fb45e1c67 Mon Sep 17 00:00:00 2001 From: Eugene Date: Fri, 22 Dec 2017 02:45:07 -0600 Subject: [PATCH] implement logout --- README.md | 42 +++++++++++++++++-- app/main.go | 3 +- app/rest/auth/auth.go | 45 ++++++++++++++++++--- app/rest/middleware.go | 33 +++++++++++++-- app/rest/server.go | 20 ++++++++- app/store/store.go | 1 + vendor/golang.org/x/oauth2/yandex/yandex.go | 16 -------- vendor/vendor.json | 6 --- 8 files changed, 129 insertions(+), 37 deletions(-) delete mode 100644 vendor/golang.org/x/oauth2/yandex/yandex.go diff --git a/README.md b/README.md index 348f26d1..66974d67 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,45 @@ # remark [![Build Status](http://drone.umputun.com:9080/api/badges/umputun/remark/status.svg)](http://drone.umputun.com:9080/umputun/remark) -description +Comment engine ## API -## command line parameters +### Authorization -## configuration +- `GET /login/{provider}?from=http://url` - login with one of supported providers and redirects to `url` +- `GET /logout` - logout +- `GET /user` - returns user info, auth required + +``` +type User struct { + Name string `json:"name"` + ID string `json:"id"` + Picture string `json:"picture"` + Profile string `json:"profile"` + Admin bool `json:"admin"` +} +``` + +_currently supported providers are `google` and `github`_ + +### Commenting + +- `POST /comment` - adds a comment. auth required + +``` +type Comment struct { + ID int64 `json:"id"` // read only + ParentID int64 `json:"pid"` + Text string `json:"text"` + User User `json:"user"` // read only + Locator Locator `json:"locator"` + Score int `json:"score"` // read only + Timestamp time.Time `json:"time"` // read only +} +``` + +- `GET /find?url=post-url` - find all comments for given post return list of `Comment` +- `GET /last/{max}` - get last `{max}` comments +- `GET /id/{id}` - get comment by `id` + +- `DELETE /comment/{id}` - delete comment by `id`. auth and admin required \ No newline at end of file diff --git a/app/main.go b/app/main.go index ad3c17d7..64a66991 100644 --- a/app/main.go +++ b/app/main.go @@ -51,17 +51,16 @@ func main() { Version: revision, Store: dataStore, SessionStore: sessionStore, + Admins: opts.Admins, AuthGoogle: auth.NewGoogle(auth.Params{ Cid: opts.GoogleCID, Csecret: opts.GoogleCSEC, SessionStore: sessionStore, - Admins: opts.Admins, }), AuthGithub: auth.NewGithub(auth.Params{ Cid: opts.GithubCID, Csecret: opts.GithubCSEC, SessionStore: sessionStore, - Admins: opts.Admins, }), } diff --git a/app/rest/auth/auth.go b/app/rest/auth/auth.go index 1e1222cb..38b470be 100644 --- a/app/rest/auth/auth.go +++ b/app/rest/auth/auth.go @@ -20,6 +20,8 @@ import ( // Provider represents oauth2 provider type Provider struct { + *sessions.FilesystemStore + Name string RedirectURL string InfoURL string @@ -27,7 +29,6 @@ type Provider struct { Scopes []string MapUser func(map[string]interface{}) store.User - *sessions.FilesystemStore conf *oauth2.Config } @@ -36,7 +37,6 @@ type Params struct { Cid string Csecret string SessionStore *sessions.FilesystemStore - Admins []string } // newProvider makes auth for given provider @@ -56,7 +56,7 @@ func initProvider(p Params, provider Provider) *Provider { return &provider } -// LoginHandler - GET /login/github +// LoginHandler - GET /login/github?from=http://radio-t.com func (p Provider) LoginHandler(w http.ResponseWriter, r *http.Request) { // make state (random) and store in session @@ -65,7 +65,14 @@ func (p Provider) LoginHandler(w http.ResponseWriter, r *http.Request) { if err != nil { log.Printf("[WARN] %s", err) } - session.Values["state-"+p.Name] = state + + session.Values["state"] = state + + if from := r.URL.Query().Get("from"); from != "" { + session.Values["from"] = from + } + + log.Printf("[DEBUG] login, %+v", session.Values) if err := session.Save(r, w); err != nil { http.Error(w, fmt.Sprintf("failed to save start, %s", err), http.StatusInternalServerError) return @@ -85,12 +92,13 @@ func (p Provider) AuthHandler(w http.ResponseWriter, r *http.Request) { } // compare saved state to the one from redirect url - retrievedState, ok := session.Values["state-"+p.Name] + retrievedState, ok := session.Values["state"] if !ok || retrievedState != r.URL.Query().Get("state") { http.Error(w, fmt.Sprintf("unexpected state %s", retrievedState.(string)), http.StatusUnauthorized) return } + log.Printf("[DEBUG] auth, %+v", session.Values) tok, err := p.conf.Exchange(context.Background(), r.URL.Query().Get("code")) if err != nil { http.Error(w, fmt.Sprintf("exchange failed, %s", err), http.StatusInternalServerError) @@ -130,9 +138,36 @@ func (p Provider) AuthHandler(w http.ResponseWriter, r *http.Request) { } log.Printf("[DEBUG] %+v", jData) + + // redirect to back url if presented + if fromUrl, ok := session.Values["from"]; ok { + http.Redirect(w, r, fromUrl.(string), http.StatusTemporaryRedirect) + return + } + render.JSON(w, r, jData) } +// LogoutHandler - GET /logout +func (p Provider) LogoutHandler(w http.ResponseWriter, r *http.Request) { + session, err := p.Get(r, "remark") + if err != nil { + http.Error(w, fmt.Sprintf("failed to get session, %s", err), http.StatusInternalServerError) + return + } + + session.Values["from"] = "" + delete(session.Values, "uinfo") + delete(session.Values, "from") + delete(session.Values, "state") + + if err = session.Save(r, w); err != nil { + http.Error(w, fmt.Sprintf("failed to reset user info, %s", err), http.StatusInternalServerError) + return + } + log.Printf("[DEBUG] logout, %+v", session.Values) +} + func randToken() string { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { diff --git a/app/rest/middleware.go b/app/rest/middleware.go index 630fcc7e..c0b57746 100644 --- a/app/rest/middleware.go +++ b/app/rest/middleware.go @@ -110,7 +110,7 @@ func Recoverer(next http.Handler) http.Handler { type contextKey string // Auth adds auth from session and populate user info -func Auth(sessionStore *sessions.FilesystemStore) func(http.Handler) http.Handler { +func Auth(sessionStore *sessions.FilesystemStore, admins []string) func(http.Handler) http.Handler { f := func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { @@ -122,12 +122,19 @@ func Auth(sessionStore *sessions.FilesystemStore) func(http.Handler) http.Handle uinfoData, ok := session.Values["uinfo"] if !ok { - http.Error(w, "login required", http.StatusUnauthorized) + http.Error(w, "Unauthorized", http.StatusUnauthorized) return } + user := uinfoData.(store.User) + for _, admin := range admins { + if admin == user.ID { + user.Admin = true + break + } + } ctx := r.Context() - ctx = context.WithValue(ctx, contextKey("user"), uinfoData.(store.User)) + ctx = context.WithValue(ctx, contextKey("user"), user) r = r.WithContext(ctx) h.ServeHTTP(w, r) @@ -137,6 +144,26 @@ func Auth(sessionStore *sessions.FilesystemStore) func(http.Handler) http.Handle return f } +// AdminOnly allows access to admins +func AdminOnly(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + + user, err := GetUserInfo(r) + if err != nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + if !user.Admin { + http.Error(w, "Access denied", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} + // GetUserInfo extracts user, or and token from request's context func GetUserInfo(r *http.Request) (user store.User, err error) { diff --git a/app/rest/server.go b/app/rest/server.go index cd88ff8b..4a20c6a9 100644 --- a/app/rest/server.go +++ b/app/rest/server.go @@ -20,6 +20,7 @@ import ( type Server struct { Version string Store store.Interface + Admins []string AuthGoogle *auth.Provider AuthGithub *auth.Provider SessionStore *sessions.FilesystemStore @@ -36,16 +37,21 @@ func (s *Server) Run() { router.Get("/login/google", s.AuthGoogle.LoginHandler) router.Get("/auth/google", s.AuthGoogle.AuthHandler) + router.Get("/logout", s.AuthGithub.LogoutHandler) // can hit any provider router.Get("/login/github", s.AuthGithub.LoginHandler) router.Get("/auth/github", s.AuthGithub.AuthHandler) router.Post("/comment", s.createCommentCtrl) - router.Delete("/comment/{id}", s.deleteCommentCtrl) router.Get("/find", s.getURLComments) - router.With(Auth(s.SessionStore)).Get("/last/{max}", s.getLastComments) router.Get("/id/{id}", s.getByID) + router.With(Auth(s.SessionStore, s.Admins)).Group(func(r chi.Router) { + r.Get("/last/{max}", s.getLastComments) + r.Get("/user", s.getUserInfo) + r.With(AdminOnly).Delete("/comment/{id}", s.deleteCommentCtrl) + }) + log.Fatal(http.ListenAndServe(":8080", router)) } @@ -163,6 +169,16 @@ func (s *Server) getByID(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, comment) } +// GET /user +func (s *Server) getUserInfo(w http.ResponseWriter, r *http.Request) { + user, err := GetUserInfo(r) + if err != nil { + httpError(w, r, http.StatusUnauthorized, err, "can't get user info") + return + } + render.JSON(w, r, user) +} + func httpError(w http.ResponseWriter, r *http.Request, code int, err error, details string) { render.Status(r, code) render.JSON(w, r, JSON{"error": err.Error(), "details": details}) diff --git a/app/store/store.go b/app/store/store.go index 31c29ffb..a1d1d182 100644 --- a/app/store/store.go +++ b/app/store/store.go @@ -25,6 +25,7 @@ type User struct { ID string `json:"id"` Picture string `json:"picture"` Profile string `json:"profile"` + Admin bool `json:"admin"` IP string `json:"-"` } diff --git a/vendor/golang.org/x/oauth2/yandex/yandex.go b/vendor/golang.org/x/oauth2/yandex/yandex.go deleted file mode 100644 index 5ebf666d..00000000 --- a/vendor/golang.org/x/oauth2/yandex/yandex.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package yandex provides constants for using OAuth2 to access Yandex APIs. -package yandex // import "golang.org/x/oauth2/yandex" - -import ( - "golang.org/x/oauth2" -) - -// Endpoint is the Yandex OAuth 2.0 endpoint. -var Endpoint = oauth2.Endpoint{ - AuthURL: "https://oauth.yandex.com/authorize", - TokenURL: "https://oauth.yandex.com/token", -} diff --git a/vendor/vendor.json b/vendor/vendor.json index adbfd006..5ccea3cd 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -172,12 +172,6 @@ "revision": "0448841f0cbe9d174c6c1cedd177f583337b8e2c", "revisionTime": "2017-12-14T23:38:05Z" }, - { - "checksumSHA1": "gPauu4Ln+yh9Nu9Un9PCRmgOqnY=", - "path": "golang.org/x/oauth2/yandex", - "revision": "0448841f0cbe9d174c6c1cedd177f583337b8e2c", - "revisionTime": "2017-12-14T23:38:05Z" - }, { "checksumSHA1": "8SH0adTcQlA+W5dzqiQ3Hft2VXg=", "path": "golang.org/x/sys/unix",