From 59a1cb3c1c24cb06fe5e62b3c89ff18983cccf7d Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 3 Nov 2017 20:07:50 +0100 Subject: [PATCH] Enhanced oAuth stuff: - improved syntax when only error is retuned - refactored frontend --- config/config.go | 9 ++---- handlers/auth.go | 49 ++++++++++++++++++------------- handlers/handlers.go | 9 ++---- handlers/public.go | 7 ++--- static/src/App/App.js | 62 ++++++++++++++++++++++----------------- store/store.go | 59 ------------------------------------- store/util.go | 68 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 124 deletions(-) create mode 100644 store/util.go diff --git a/config/config.go b/config/config.go index 97e37e1..4bde030 100644 --- a/config/config.go +++ b/config/config.go @@ -54,8 +54,7 @@ func Preload() error { if err != nil { return errors.Wrap(err, "could not get configuration path") } - err = updateConfig() - if err != nil { + if err = updateConfig(); err != nil { return errors.Wrap(err, "could not update config") } return nil @@ -66,8 +65,7 @@ func updateConfig() error { if err != nil { return errors.Wrap(err, "could not read configuration file") } - err = json.Unmarshal(file, &config) - if err != nil { + if err = json.Unmarshal(file, &config); err != nil { return errors.Wrap(err, "could not unmarshal configuration file") } return nil @@ -87,8 +85,7 @@ func Set(conf *Configuration) error { if err != nil { return err } - err = ioutil.WriteFile(configPath, data, 0644) - if err != nil { + if err = ioutil.WriteFile(configPath, data, 0644); err != nil { return err } config = conf diff --git a/handlers/auth.go b/handlers/auth.go index a669f0a..22bbbc7 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -18,6 +18,9 @@ type jwtClaims struct { jwt.StandardClaims OAuthProvider string OAuthID string + OAuthName string + OAuthPicture string + OAuthEmail string } type oAuthUser struct { @@ -34,8 +37,6 @@ type oAuthUser struct { } func (h *Handler) initOAuth() { - store := sessions.NewCookieStore([]byte("secret")) - h.oAuthConf = &oauth2.Config{ ClientID: h.config.OAuth.Google.ClientID, ClientSecret: h.config.OAuth.Google.ClientSecret, @@ -45,13 +46,13 @@ func (h *Handler) initOAuth() { }, Endpoint: google.Endpoint, } - h.engine.Use(sessions.Sessions("backend", store)) - h.engine.GET("/api/v1/login", h.handleGoogleLogin) + h.engine.Use(sessions.Sessions("backend", sessions.NewCookieStore(h.config.Secret))) + h.engine.GET("/api/v1/login", h.handleGoogleRedirect) h.engine.GET("/api/v1/callback", h.handleGoogleCallback) h.engine.POST("/api/v1/check", h.handleGoogleCheck) } -func (h *Handler) handleGoogleLogin(c *gin.Context) { +func (h *Handler) handleGoogleRedirect(c *gin.Context) { state := h.randToken() session := sessions.Default(c) session.Set("state", state) @@ -63,8 +64,7 @@ func (h *Handler) handleGoogleCheck(c *gin.Context) { var data struct { Token string `binding:"required"` } - err := c.ShouldBind(&data) - if err != nil { + if err := c.ShouldBind(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -72,7 +72,13 @@ func (h *Handler) handleGoogleCheck(c *gin.Context) { return h.config.Secret, nil }) if claims, ok := token.Claims.(*jwtClaims); ok && token.Valid { - c.JSON(http.StatusOK, claims) + c.JSON(http.StatusOK, gin.H{ + "ID": claims.OAuthID, + "Email": claims.OAuthEmail, + "Name": claims.OAuthName, + "Picture": claims.OAuthPicture, + "Provider": claims.OAuthProvider, + }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } @@ -82,42 +88,43 @@ func (h *Handler) handleGoogleCallback(c *gin.Context) { session := sessions.Default(c) retrievedState := session.Get("state") if retrievedState != c.Query("state") { - c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("Invalid session state: %s", retrievedState)}) + c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("invalid session state: %s", retrievedState)}) return } oAuthToken, err := h.oAuthConf.Exchange(oauth2.NoContext, c.Query("code")) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not exchange code: %v", err)}) return } client := h.oAuthConf.Client(oauth2.NoContext, oAuthToken) - userinfo, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo") + oAuthUserInfoReq, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo") if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not get user data: %v", err)}) return } - defer userinfo.Body.Close() - data, err := ioutil.ReadAll(userinfo.Body) + defer oAuthUserInfoReq.Body.Close() + data, err := ioutil.ReadAll(oAuthUserInfoReq.Body) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Could not read body: %v", err)}) + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("could not read body: %v", err)}) return } - var user oAuthUser - err = json.Unmarshal(data, &user) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Decoding userinfo failed: %v", err)}) + if err = json.Unmarshal(data, &user); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("decoding user info failed: %v", err)}) return } - // you would like it to contain. + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims{ jwt.StandardClaims{ - ExpiresAt: time.Now().Add(time.Minute * 10).Unix(), + ExpiresAt: time.Now().Add(time.Hour * 24 * 365).Unix(), }, "google", user.Sub, + user.Name, + user.Picture, + user.Email, }) // Sign and get the complete encoded token as a string using the secret diff --git a/handlers/handlers.go b/handlers/handlers.go index 059cbb1..0c9aa69 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -28,8 +28,7 @@ func New(handlerConfig config.Handlers, store store.Store) (*Handler, error) { engine: gin.Default(), } h.setHandlers() - err := h.checkIfSecretExist() - if err != nil { + if err := h.checkIfSecretExist(); err != nil { return nil, errors.Wrap(err, "could not check if secret exist") } h.initOAuth() @@ -40,13 +39,11 @@ func (h *Handler) checkIfSecretExist() error { conf := config.Get() if conf.Handlers.Secret == nil { b := make([]byte, 128) - _, err := rand.Read(b) - if err != nil { + if _, err := rand.Read(b); err != nil { return err } conf.Handlers.Secret = b - err = config.Set(conf) - if err != nil { + if err := config.Set(conf); err != nil { return err } } diff --git a/handlers/public.go b/handlers/public.go index f20e3a9..9682587 100644 --- a/handlers/public.go +++ b/handlers/public.go @@ -18,8 +18,7 @@ func (h *Handler) handleInfo(c *gin.Context) { var data struct { ID string `binding:"required"` } - err := c.ShouldBind(&data) - if err != nil { + if err := c.ShouldBind(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -45,8 +44,7 @@ func (h *Handler) handleAccess(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } - err = h.store.IncreaseVisitCounter(id) - if err != nil { + if h.store.IncreaseVisitCounter(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -67,7 +65,6 @@ func (h *Handler) handleCreate(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - data.URL = h.getSchemaAndHost(c) + "/" + id c.JSON(http.StatusOK, data) } diff --git a/static/src/App/App.js b/static/src/App/App.js index 155805f..6ece408 100644 --- a/static/src/App/App.js +++ b/static/src/App/App.js @@ -2,48 +2,57 @@ import React, { Component } from 'react' import { Container, Input, Segment, Form, Modal, Button } from 'semantic-ui-react' import './App.css'; -class ContainerExampleContainer extends Component { +class AppComponent extends Component { handleURLChange = (e, { value }) => this.url = value - - handleURLSubmit() { - console.log(this.url) + handleURLSubmit = () => { + console.log("handle Submit", "URL:", this.url) } componentWillMount() { - console.log("componentWillMount") - } - componentDidMount = () => { - console.log("componentDidMount") + this.checkAuth() } state = { open: true, + userData: {}, authorized: false } - onOAuthClose = () => { + onOAuthClose() { this.setState({ open: true }) } - + checkAuth = () => { + const that = this, + token = window.localStorage.getItem("token"); + if (token) { + fetch("/api/v1/check", { + method: "POST", + body: JSON.stringify({ + Token: token + }), + headers: { + 'Content-Type': 'application/json' + } + }).then(res => res.ok ? res.json() : Promise.reject(res.json())) // Check if the request was StatusOK, otherwise reject Promise + .then(d => { + that.setState({ userData: d }) + that.setState({ authorized: true }) + }) + .catch(e => { + window.localStorage.removeItem("token"); + that.setState({ authorized: false }) + }) + } + } onAuthCallback = data => { + // clear the old event listener, so that the event can only emitted be once window.removeEventListener('onAuthCallback', this.onAuthCallback); - var token = data.detail.token; - fetch("/api/v1/check", { - method: "POST", - body: JSON.stringify({ - Token: token - }), - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }).then(res => res.text()) - .then(d => console.log(d)) + window.localStorage.setItem("token", data.detail.token); + this.checkAuth(); } - onAuthClick = () => { - console.log("onAuthClick") window.addEventListener('onAuthCallback', this.onAuthCallback, false); + // Open the oAuth window that is it centered in the middle of the screen var wwidth = 400, wHeight = 500; var wLeft = (window.screen.width / 2) - (wwidth / 2); @@ -60,7 +69,7 @@ class ContainerExampleContainer extends Component {

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa strong. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede link mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi.

- +
@@ -84,8 +93,7 @@ class ContainerExampleContainer extends Component { ) } - } } -export default ContainerExampleContainer; +export default AppComponent; diff --git a/store/store.go b/store/store.go index 94b8d50..3e0cdf8 100644 --- a/store/store.go +++ b/store/store.go @@ -3,7 +3,6 @@ package store import ( "encoding/json" - "math/rand" "time" "github.com/asaskevich/govalidator" @@ -128,65 +127,7 @@ func (s *Store) CreateEntry(URL, remoteAddr string) (string, error) { return "", ErrGeneratingTriesFailed } -// checkExistens returns true if a entry with a given ID -// exists and false if not -func (s *Store) checkExistence(id string) bool { - raw, err := s.GetEntryByIDRaw(id) - if err != nil && err != ErrNoEntryFound { - return true - } - if raw != nil { - return true - } - return false -} - -// createEntry creates a new entry -func (s *Store) createEntry(URL, remoteAddr string) (string, error) { - id := generateRandomString(s.idLength) - exists := s.checkExistence(id) - if !exists { - raw, err := json.Marshal(Entry{ - URL: URL, - RemoteAddr: remoteAddr, - CreatedOn: time.Now(), - }) - if err != nil { - return "", err - } - return id, s.createEntryRaw([]byte(id), raw) - } - return "", errors.New("entry already exists") -} - -// createEntryRaw creates a entry with the given key value pair -func (s *Store) createEntryRaw(key, value []byte) error { - err := s.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket(s.bucketName) - raw := bucket.Get(key) - if raw != nil { - return errors.New("entry value is not empty") - } - err := bucket.Put(key, value) - if err != nil { - return errors.Wrap(err, "could not put data into bucket") - } - return nil - }) - return err -} - // Close closes the bolt db database func (s *Store) Close() error { return s.db.Close() } - -// generateRandomString generates a random string with an predefined length -func generateRandomString(length int) string { - letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - b := make([]rune, length) - for i := range b { - b[i] = letterRunes[rand.Intn(len(letterRunes))] - } - return string(b) -} diff --git a/store/util.go b/store/util.go new file mode 100644 index 0000000..483b2e0 --- /dev/null +++ b/store/util.go @@ -0,0 +1,68 @@ +package store + +import ( + "encoding/json" + "math/rand" + "time" + + "github.com/boltdb/bolt" + "github.com/pkg/errors" +) + +// checkExistens returns true if a entry with a given ID +// exists and false if not +func (s *Store) checkExistence(id string) bool { + raw, err := s.GetEntryByIDRaw(id) + if err != nil && err != ErrNoEntryFound { + return true + } + if raw != nil { + return true + } + return false +} + +// createEntryRaw creates a entry with the given key value pair +func (s *Store) createEntryRaw(key, value []byte) error { + err := s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket(s.bucketName) + raw := bucket.Get(key) + if raw != nil { + return errors.New("entry value is not empty") + } + err := bucket.Put(key, value) + if err != nil { + return errors.Wrap(err, "could not put data into bucket") + } + return nil + }) + return err +} + +// createEntry creates a new entry +func (s *Store) createEntry(URL, remoteAddr string) (string, error) { + id := generateRandomString(s.idLength) + exists := s.checkExistence(id) + if !exists { + raw, err := json.Marshal(Entry{ + URL: URL, + RemoteAddr: remoteAddr, + CreatedOn: time.Now(), + }) + if err != nil { + return "", err + } + return id, s.createEntryRaw([]byte(id), raw) + } + return "", errors.New("entry already exists") +} + +// generateRandomString generates a random string with an predefined length +func generateRandomString(length int) string { + letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + b := make([]rune, length) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +}