Browse Source

Added GitHub as oAuth Provider: #23

dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
c06ff7846c
  1. 5
      handlers/auth.go
  2. 3
      handlers/auth/auth.go
  3. 63
      handlers/auth/github.go
  4. 10
      handlers/auth/google.go
  5. 13
      static/src/index.css
  6. 25
      static/src/index.js
  7. 2
      store/store_test.go
  8. 2
      util/config.go

5
handlers/auth.go

@ -18,8 +18,9 @@ func (h *Handler) initOAuth() {
h.engine.Use(sessions.Sessions("backend", sessions.NewCookieStore(util.GetPrivateKey())))
auth.WithAdapterWrapper(auth.NewGoogleAdapter(viper.GetString("Google.ClientID"), viper.GetString("Google.ClientSecret"), viper.GetString("base_url")), h.engine.Group("/api/v1/auth/google"))
auth.WithAdapterWrapper(auth.NewGithubAdapter(viper.GetString("GitHub.ClientID"), viper.GetString("GitHub.ClientSecret"), viper.GetString("base_url")), h.engine.Group("/api/v1/auth/github"))
h.engine.POST("/api/v1/check", h.handleGoogleCheck)
h.engine.POST("/api/v1/check", h.handleAuthCheck)
}
func (h *Handler) parseJWT(wt string) (*auth.JWTClaims, error) {
@ -58,7 +59,7 @@ func (h *Handler) authMiddleware(c *gin.Context) {
c.Next()
}
func (h *Handler) handleGoogleCheck(c *gin.Context) {
func (h *Handler) handleAuthCheck(c *gin.Context) {
var data struct {
Token string `binding:"required"`
}

3
handlers/auth/auth.go

@ -65,19 +65,16 @@ func (a *AdapterWrapper) HandleCallback(c *gin.Context) {
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("invalid session state: %s", sessionState)})
return
}
user, err := a.GetUserData(receivedState, c.Query("code"))
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
token, err := a.newJWT(user, a.GetOAuthProviderName())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.HTML(http.StatusOK, "token.tmpl", gin.H{
"token": token,
})

63
handlers/auth/github.go

@ -1 +1,64 @@
package auth
import (
"context"
"encoding/json"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2/github"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
type githubAdapter struct {
config *oauth2.Config
}
// NewGithubAdapter creates an oAuth adapter out of the credentials and the baseURL
func NewGithubAdapter(clientID, clientSecret, baseURL string) Adapter {
return &githubAdapter{&oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: baseURL + "/api/v1/auth/github/callback",
Scopes: []string{
"(no scope)",
},
Endpoint: github.Endpoint,
}}
}
func (a *githubAdapter) GetRedirectURL(state string) string {
return a.config.AuthCodeURL(state)
}
func (a *githubAdapter) GetUserData(state, code string) (*user, error) {
logrus.Debugf("Getting User Data with state: %s, and code: %s", state, code)
oAuthToken, err := a.config.Exchange(context.Background(), code)
if err != nil {
return nil, errors.Wrap(err, "could not exchange code")
}
oAuthUserInfoReq, err := a.config.Client(context.Background(), oAuthToken).Get("https://api.github.com/user")
if err != nil {
return nil, errors.Wrap(err, "could not get user data")
}
defer oAuthUserInfoReq.Body.Close()
var gUser struct {
ID int `json:"id"`
AvatarURL string `json:"avatar_url"`
Name string `json:"name"`
}
if err = json.NewDecoder(oAuthUserInfoReq.Body).Decode(&gUser); err != nil {
return nil, errors.Wrap(err, "decoding user info failed")
}
return &user{
ID: string(gUser.ID),
Name: gUser.Name,
Picture: gUser.AvatarURL,
}, nil
}
func (a *githubAdapter) GetOAuthProviderName() string {
return "github"
}

10
handlers/auth/google.go

@ -3,7 +3,6 @@ package auth
import (
"context"
"encoding/json"
"io/ioutil"
"github.com/pkg/errors"
"golang.org/x/oauth2"
@ -37,22 +36,17 @@ func (a *googleAdapter) GetUserData(state, code string) (*user, error) {
return nil, errors.Wrap(err, "could not exchange code")
}
client := a.config.Client(context.Background(), oAuthToken)
oAuthUserInfoReq, err := client.Get("https://www.googleapis.com/oauth2/v3/userinfo")
oAuthUserInfoReq, err := a.config.Client(context.Background(), oAuthToken).Get("https://www.googleapis.com/oauth2/v3/userinfo")
if err != nil {
return nil, errors.Wrap(err, "could not get user data")
}
defer oAuthUserInfoReq.Body.Close()
data, err := ioutil.ReadAll(oAuthUserInfoReq.Body)
if err != nil {
return nil, errors.Wrap(err, "could not read body")
}
var gUser struct {
Sub string `json:"sub"`
Name string `json:"name"`
Picture string `json:"picture"`
}
if err = json.Unmarshal(data, &gUser); err != nil {
if err = json.NewDecoder(oAuthUserInfoReq.Body).Decode(&gUser); err != nil {
return nil, errors.Wrap(err, "decoding user info failed")
}
return &user{

13
static/src/index.css

@ -0,0 +1,13 @@
.swatch-github-gray-light {
background-color:#999;
}
.swatch-github-gray {
background-color:#767676;
}
.swatch-github-gray-dark {
background-color:#333;
}
.ui.github.button {
background-color: #333;
color: #fff;
}

25
static/src/index.js

@ -1,7 +1,7 @@
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
import { HashRouter, Route, Link } from 'react-router-dom'
import { Menu, Container, Modal, Button, Image } from 'semantic-ui-react'
import { Menu, Container, Modal, Button, Image, Icon } from 'semantic-ui-react'
import 'semantic-ui-css/semantic.min.css';
import About from './About/About'
@ -9,6 +9,8 @@ import Home from './Home/Home'
import ShareX from './ShareX/ShareX'
import Lookup from './Lookup/Lookup'
import './index.css'
export default class BaseComponent extends Component {
state = {
open: true,
@ -51,7 +53,7 @@ export default class BaseComponent extends Component {
}
}
onAuthCallback = data => {
onOAuthCallback = data => {
if (data.isTrusted) {
// clear the old event listener, so that the event can only emitted be once
window.removeEventListener('message', this.onAuthCallback);
@ -59,14 +61,14 @@ export default class BaseComponent extends Component {
this.checkAuth();
}
}
onAuthClick = () => {
window.addEventListener('message', this.onAuthCallback, false);
onOAuthClick = provider => {
window.addEventListener('message', this.onOAuthCallback, 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);
var wTop = (window.screen.height / 2) - (wHeight / 2);
window.open('/api/v1/auth/google/login', '', `width=${wwidth}, height=${wHeight}, top=${wTop}, left=${wLeft}`)
window.open(`/api/v1/auth/${provider}/login`, '', `width=${wwidth}, height=${wHeight}, top=${wTop}, left=${wLeft}`)
}
handleLogout = () => {
@ -85,9 +87,16 @@ export default class BaseComponent extends Component {
<Modal.Content>
<p>Currently you are only able to use Google as authentication service:</p>
<div className='ui center aligned segment'>
<Button className='ui google plus button' onClick={this.onAuthClick}>
<i className='google icon'></i>
Login with Google
<Button className='ui google plus button' onClick={this.onOAuthClick.bind(this, "google")}>
<Icon name='google' /> Login with Google
</Button>
<div className="ui divider"></div>
<Button className='github' onClick={this.onOAuthClick.bind(this, "github")}>
<Icon name='github' /> Login with GitHub
</Button>
<div className="ui divider"></div>
<Button color='twitter' onClick={this.onOAuthClick.bind(this, "twitter")}>
<Icon name='twitter' /> Login with Twitter
</Button>
</div>
</Modal.Content>

2
store/store_test.go

@ -12,7 +12,7 @@ const (
)
func TestGenerateRandomString(t *testing.T) {
viper.SetDefault("data_dir", "data")
viper.SetDefault("data_dir", "./data")
viper.SetDefault("shorted_id_length", 4)
tt := []struct {
name string

2
util/config.go

@ -32,6 +32,8 @@ func ReadInConfig() error {
case viper.ConfigFileNotFoundError:
logrus.Info("No configuration file found, using defaults and environment overrides.")
break
case nil:
break
default:
return errors.Wrap(err, "could not read config file")
}

Loading…
Cancel
Save