Browse Source

Enhanced oAuth stuff:

- improved syntax when only error is retuned
- refactored frontend
dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
59a1cb3c1c
  1. 9
      config/config.go
  2. 49
      handlers/auth.go
  3. 9
      handlers/handlers.go
  4. 7
      handlers/public.go
  5. 50
      static/src/App/App.js
  6. 59
      store/store.go
  7. 68
      store/util.go

9
config/config.go

@ -54,8 +54,7 @@ func Preload() error {
if err != nil { if err != nil {
return errors.Wrap(err, "could not get configuration path") return errors.Wrap(err, "could not get configuration path")
} }
err = updateConfig() if err = updateConfig(); err != nil {
if err != nil {
return errors.Wrap(err, "could not update config") return errors.Wrap(err, "could not update config")
} }
return nil return nil
@ -66,8 +65,7 @@ func updateConfig() error {
if err != nil { if err != nil {
return errors.Wrap(err, "could not read configuration file") return errors.Wrap(err, "could not read configuration file")
} }
err = json.Unmarshal(file, &config) if err = json.Unmarshal(file, &config); err != nil {
if err != nil {
return errors.Wrap(err, "could not unmarshal configuration file") return errors.Wrap(err, "could not unmarshal configuration file")
} }
return nil return nil
@ -87,8 +85,7 @@ func Set(conf *Configuration) error {
if err != nil { if err != nil {
return err return err
} }
err = ioutil.WriteFile(configPath, data, 0644) if err = ioutil.WriteFile(configPath, data, 0644); err != nil {
if err != nil {
return err return err
} }
config = conf config = conf

49
handlers/auth.go

@ -18,6 +18,9 @@ type jwtClaims struct {
jwt.StandardClaims jwt.StandardClaims
OAuthProvider string OAuthProvider string
OAuthID string OAuthID string
OAuthName string
OAuthPicture string
OAuthEmail string
} }
type oAuthUser struct { type oAuthUser struct {
@ -34,8 +37,6 @@ type oAuthUser struct {
} }
func (h *Handler) initOAuth() { func (h *Handler) initOAuth() {
store := sessions.NewCookieStore([]byte("secret"))
h.oAuthConf = &oauth2.Config{ h.oAuthConf = &oauth2.Config{
ClientID: h.config.OAuth.Google.ClientID, ClientID: h.config.OAuth.Google.ClientID,
ClientSecret: h.config.OAuth.Google.ClientSecret, ClientSecret: h.config.OAuth.Google.ClientSecret,
@ -45,13 +46,13 @@ func (h *Handler) initOAuth() {
}, },
Endpoint: google.Endpoint, Endpoint: google.Endpoint,
} }
h.engine.Use(sessions.Sessions("backend", store)) h.engine.Use(sessions.Sessions("backend", sessions.NewCookieStore(h.config.Secret)))
h.engine.GET("/api/v1/login", h.handleGoogleLogin) h.engine.GET("/api/v1/login", h.handleGoogleRedirect)
h.engine.GET("/api/v1/callback", h.handleGoogleCallback) h.engine.GET("/api/v1/callback", h.handleGoogleCallback)
h.engine.POST("/api/v1/check", h.handleGoogleCheck) 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() state := h.randToken()
session := sessions.Default(c) session := sessions.Default(c)
session.Set("state", state) session.Set("state", state)
@ -63,8 +64,7 @@ func (h *Handler) handleGoogleCheck(c *gin.Context) {
var data struct { var data struct {
Token string `binding:"required"` Token string `binding:"required"`
} }
err := c.ShouldBind(&data) if err := c.ShouldBind(&data); err != nil {
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
@ -72,7 +72,13 @@ func (h *Handler) handleGoogleCheck(c *gin.Context) {
return h.config.Secret, nil return h.config.Secret, nil
}) })
if claims, ok := token.Claims.(*jwtClaims); ok && token.Valid { 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 { } else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
} }
@ -82,42 +88,43 @@ func (h *Handler) handleGoogleCallback(c *gin.Context) {
session := sessions.Default(c) session := sessions.Default(c)
retrievedState := session.Get("state") retrievedState := session.Get("state")
if retrievedState != c.Query("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 return
} }
oAuthToken, err := h.oAuthConf.Exchange(oauth2.NoContext, c.Query("code")) oAuthToken, err := h.oAuthConf.Exchange(oauth2.NoContext, c.Query("code"))
if err != nil { 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 return
} }
client := h.oAuthConf.Client(oauth2.NoContext, oAuthToken) 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 { 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 return
} }
defer userinfo.Body.Close() defer oAuthUserInfoReq.Body.Close()
data, err := ioutil.ReadAll(userinfo.Body) data, err := ioutil.ReadAll(oAuthUserInfoReq.Body)
if err != nil { 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 return
} }
var user oAuthUser var user oAuthUser
err = json.Unmarshal(data, &user) if err = json.Unmarshal(data, &user); err != nil {
if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("decoding user info failed: %v", err)})
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Decoding userinfo failed: %v", err)})
return return
} }
// you would like it to contain.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims{
jwt.StandardClaims{ jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 10).Unix(), ExpiresAt: time.Now().Add(time.Hour * 24 * 365).Unix(),
}, },
"google", "google",
user.Sub, user.Sub,
user.Name,
user.Picture,
user.Email,
}) })
// Sign and get the complete encoded token as a string using the secret // Sign and get the complete encoded token as a string using the secret

9
handlers/handlers.go

@ -28,8 +28,7 @@ func New(handlerConfig config.Handlers, store store.Store) (*Handler, error) {
engine: gin.Default(), engine: gin.Default(),
} }
h.setHandlers() h.setHandlers()
err := h.checkIfSecretExist() if err := h.checkIfSecretExist(); err != nil {
if err != nil {
return nil, errors.Wrap(err, "could not check if secret exist") return nil, errors.Wrap(err, "could not check if secret exist")
} }
h.initOAuth() h.initOAuth()
@ -40,13 +39,11 @@ func (h *Handler) checkIfSecretExist() error {
conf := config.Get() conf := config.Get()
if conf.Handlers.Secret == nil { if conf.Handlers.Secret == nil {
b := make([]byte, 128) b := make([]byte, 128)
_, err := rand.Read(b) if _, err := rand.Read(b); err != nil {
if err != nil {
return err return err
} }
conf.Handlers.Secret = b conf.Handlers.Secret = b
err = config.Set(conf) if err := config.Set(conf); err != nil {
if err != nil {
return err return err
} }
} }

7
handlers/public.go

@ -18,8 +18,7 @@ func (h *Handler) handleInfo(c *gin.Context) {
var data struct { var data struct {
ID string `binding:"required"` ID string `binding:"required"`
} }
err := c.ShouldBind(&data) if err := c.ShouldBind(&data); err != nil {
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
@ -45,8 +44,7 @@ func (h *Handler) handleAccess(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return return
} }
err = h.store.IncreaseVisitCounter(id) if h.store.IncreaseVisitCounter(id); err != nil {
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@ -67,7 +65,6 @@ func (h *Handler) handleCreate(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
data.URL = h.getSchemaAndHost(c) + "/" + id data.URL = h.getSchemaAndHost(c) + "/" + id
c.JSON(http.StatusOK, data) c.JSON(http.StatusOK, data)
} }

50
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 { Container, Input, Segment, Form, Modal, Button } from 'semantic-ui-react'
import './App.css'; import './App.css';
class ContainerExampleContainer extends Component { class AppComponent extends Component {
handleURLChange = (e, { value }) => this.url = value handleURLChange = (e, { value }) => this.url = value
handleURLSubmit = () => {
handleURLSubmit() { console.log("handle Submit", "URL:", this.url)
console.log(this.url)
} }
componentWillMount() { componentWillMount() {
console.log("componentWillMount") this.checkAuth()
}
componentDidMount = () => {
console.log("componentDidMount")
} }
state = { state = {
open: true, open: true,
userData: {},
authorized: false authorized: false
} }
onOAuthClose = () => { onOAuthClose() {
this.setState({ open: true }) this.setState({ open: true })
} }
checkAuth = () => {
onAuthCallback = data => { const that = this,
window.removeEventListener('onAuthCallback', this.onAuthCallback); token = window.localStorage.getItem("token");
var token = data.detail.token; if (token) {
fetch("/api/v1/check", { fetch("/api/v1/check", {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
Token: token Token: token
}), }),
headers: { headers: {
'Accept': 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}).then(res => res.text()) }).then(res => res.ok ? res.json() : Promise.reject(res.json())) // Check if the request was StatusOK, otherwise reject Promise
.then(d => console.log(d)) .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);
window.localStorage.setItem("token", data.detail.token);
this.checkAuth();
} }
onAuthClick = () => { onAuthClick = () => {
console.log("onAuthClick")
window.addEventListener('onAuthCallback', this.onAuthCallback, false); window.addEventListener('onAuthCallback', this.onAuthCallback, false);
// Open the oAuth window that is it centered in the middle of the screen
var wwidth = 400, var wwidth = 400,
wHeight = 500; wHeight = 500;
var wLeft = (window.screen.width / 2) - (wwidth / 2); var wLeft = (window.screen.width / 2) - (wwidth / 2);
@ -60,7 +69,7 @@ class ContainerExampleContainer extends Component {
<p>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.</p> <p>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.</p>
<Form onSubmit={this.handleURLSubmit}> <Form onSubmit={this.handleURLSubmit}>
<Form.Field> <Form.Field>
<Input size='big' action={{ icon: 'arrow right' }} type='email' onChange={this.handleURLChange} name='url' placeholder='Enter your long URL here' /> <Input size='big' action={{ icon: 'arrow right', labelPosition: 'right', content: 'Shorten' }} type='url' onChange={this.handleURLChange} name='url' placeholder='Paste a link to shorten it' />
</Form.Field> </Form.Field>
</Form> </Form>
</Segment> </Segment>
@ -84,8 +93,7 @@ class ContainerExampleContainer extends Component {
</Modal> </Modal>
) )
} }
} }
} }
export default ContainerExampleContainer; export default AppComponent;

59
store/store.go

@ -3,7 +3,6 @@ package store
import ( import (
"encoding/json" "encoding/json"
"math/rand"
"time" "time"
"github.com/asaskevich/govalidator" "github.com/asaskevich/govalidator"
@ -128,65 +127,7 @@ func (s *Store) CreateEntry(URL, remoteAddr string) (string, error) {
return "", ErrGeneratingTriesFailed 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 // Close closes the bolt db database
func (s *Store) Close() error { func (s *Store) Close() error {
return s.db.Close() 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)
}

68
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)
}
Loading…
Cancel
Save