From c06ff7846c55e2fe5de9678f3d83815cb6322f32 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 16 Nov 2017 22:08:12 +0100 Subject: [PATCH] Added GitHub as oAuth Provider: #23 --- handlers/auth.go | 5 ++-- handlers/auth/auth.go | 3 -- handlers/auth/github.go | 63 +++++++++++++++++++++++++++++++++++++++++ handlers/auth/google.go | 10 ++----- static/src/index.css | 13 +++++++++ static/src/index.js | 25 ++++++++++------ store/store_test.go | 2 +- util/config.go | 2 ++ 8 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 static/src/index.css diff --git a/handlers/auth.go b/handlers/auth.go index 21c2239..6e38db7 100644 --- a/handlers/auth.go +++ b/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"` } diff --git a/handlers/auth/auth.go b/handlers/auth/auth.go index 916d8e0..05d42ba 100644 --- a/handlers/auth/auth.go +++ b/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, }) diff --git a/handlers/auth/github.go b/handlers/auth/github.go index 8832b06..34ac2bd 100644 --- a/handlers/auth/github.go +++ b/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" +} diff --git a/handlers/auth/google.go b/handlers/auth/google.go index 828d4a2..b2a4a19 100644 --- a/handlers/auth/google.go +++ b/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{ diff --git a/static/src/index.css b/static/src/index.css new file mode 100644 index 0000000..506b1da --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/static/src/index.js b/static/src/index.js index 8f9d5b4..2a84fea 100644 --- a/static/src/index.js +++ b/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 {

Currently you are only able to use Google as authentication service:

- +
+ +
+
diff --git a/store/store_test.go b/store/store_test.go index 2417c70..7554da1 100644 --- a/store/store_test.go +++ b/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 diff --git a/util/config.go b/util/config.go index d17e0d9..42034e9 100644 --- a/util/config.go +++ b/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") }