implement logout

This commit is contained in:
Eugene
2017-12-22 02:45:07 -06:00
parent d9597571f7
commit abf9811aaa
8 changed files with 129 additions and 37 deletions
+39 -3
View File
@@ -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
+1 -2
View File
@@ -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,
}),
}
+40 -5
View File
@@ -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 {
+30 -3
View File
@@ -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) {
+18 -2
View File
@@ -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})
+1
View File
@@ -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:"-"`
}
-16
View File
@@ -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",
}
-6
View File
@@ -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",