implement logout
This commit is contained in:
@@ -1,9 +1,45 @@
|
||||
# remark [](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
@@ -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
@@ -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
@@ -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
@@ -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})
|
||||
|
||||
@@ -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
@@ -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",
|
||||
}
|
||||
Vendored
-6
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user