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. 62
      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 {
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

49
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

9
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
}
}

7
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)
}

62
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 {
<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.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>
</Segment>
@ -84,8 +93,7 @@ class ContainerExampleContainer extends Component {
</Modal>
)
}
}
}
export default ContainerExampleContainer;
export default AppComponent;

59
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)
}

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