Skip to content

Instantly share code, notes, and snippets.

@paultyng
Forked from codesword/authentication.go
Created April 17, 2017 18:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paultyng/5d71bbb34e0f27af36aef57ada8c3492 to your computer and use it in GitHub Desktop.
Save paultyng/5d71bbb34e0f27af36aef57ada8c3492 to your computer and use it in GitHub Desktop.
package auth
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"os"
"regexp"
"time"
"github.com/andela/micro-api-gateway/log"
"github.com/labstack/echo"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
const (
redirectStatusCode = 302
keyToken = "oauth2_token"
keyNextPage = "redirect_url"
googleUserInfoURL = "https://www.googleapis.com/plus/v1/people/me?access_token="
)
var (
// PathLogin is the path to handle OAuth 2.0 logins.
PathLogin = "/login"
// PathLogout is the path to handle OAuth 2.0 logouts.
PathLogout = "/logout"
// PathCallback is the path to handle callback from OAuth 2.0 backend
// to exchange credentials.
PathCallback = "/auth/google/callback"
pathExchange = "/token"
cookie *http.Cookie
)
func GoogleAuthFromConfig(keyPath string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
conf := googleAuthConfig(keyPath)
switch c.Request().URL.Path {
case PathLogin:
return login(conf, c)
case PathLogout:
return logout(c)
case pathExchange:
return exchange(c)
case PathCallback:
return handleOAuth2Callback(conf, c)
default:
return next(c)
}
}
}
}
func login(f *oauth2.Config, c echo.Context) error {
to := c.QueryParam(keyNextPage)
return c.Redirect(redirectStatusCode, f.AuthCodeURL(to))
}
func logout(c echo.Context) error {
to := c.QueryParam(keyNextPage)
cookie, _ = c.Cookie("jwt-token")
cookie = &http.Cookie{
Name: "jwt-token",
Value: "",
Domain: os.Getenv("COOKIE_DOMAIN"),
Path: "/",
Expires: time.Now(),
MaxAge: -1,
}
c.SetCookie(cookie)
return c.Redirect(redirectStatusCode, to)
}
func exchange(c echo.Context) error {
accessToken := c.QueryParam("google_token")
response, err := http.Get(googleUserInfoURL + accessToken)
if err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": err.Error()})
}
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": err.Error()})
}
gUser := GoogleUser{}
err = json.Unmarshal(contents, &gUser)
user := gUser.toUserService(accessToken)
token, err := generateToken(user)
if err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": err.Error()})
}
return c.JSON(http.StatusOK, echo.Map{"token": token})
}
func handleOAuth2Callback(f *oauth2.Config, c echo.Context) error {
next := c.QueryParam("state")
code := c.QueryParam("code")
t, err := f.Exchange(oauth2.NoContext, code)
if err != nil {
return redirectWithError("exchange oauth token failed", next, c, err)
}
response, err := http.Get(googleUserInfoURL + t.AccessToken)
if err != nil {
return redirectWithError("fetch user info failed", next, c, err)
}
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
return redirectWithError("readAll response Body failed", next, c, err)
}
gUser := GoogleUser{}
err = json.Unmarshal(contents, &gUser)
user := gUser.toUserService(t.AccessToken)
token, err := generateToken(user)
if err != nil {
return redirectWithError("failed to create user token", next, c, err)
}
// Set cookie if andela subdomain. Return token in url if not running on
// andela's subdomain or if mobile app
if match, err := regexp.MatchString(`.*andela\.(com|me)`, next); err == nil && match {
if err == nil {
cookie = &http.Cookie{
Name: "jwt-token",
Value: token,
Domain: os.Getenv("COOKIE_DOMAIN"),
Path: "/",
Expires: time.Now().Add(time.Hour * 72),
}
c.SetCookie(cookie)
} else {
log.Error("An error has occured, unable to generate token!")
}
} else {
next = next + "?token=" + token
}
return c.Redirect(redirectStatusCode, next)
}
func redirectWithError(message string, to string, c echo.Context, err error) error {
log.Error(message, err)
to = to + "?error=" + url.QueryEscape(message)
return c.Redirect(redirectStatusCode, to)
}
func googleAuthConfig(keyPath string) *oauth2.Config {
jsonKey, err := ioutil.ReadFile(keyPath)
if err != nil {
log.Error(err)
}
conf, err := google.ConfigFromJSON(jsonKey, "email")
if err != nil {
log.Error(err)
}
conf.Scopes = []string{
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
}
conf.RedirectURL = os.Getenv("HOST_NAME") + "/auth/google/callback"
return conf
}
func generateToken(user *users.User) (string, error) {
// Create the token
user, err := usersClient.FindOrCreateUser(context.Background(), user)
if err != nil {
logger.Error("Tried to get user", "method", "GenerateToken", "message", err)
return "", err
}
claims := NewClaims(user)
claims.Permissions, err = getPermission(user.Roles)
if err != nil {
logger.Error("Tried to get users permissions", "method", "GenerateToken", "message", err)
return "", err
}
token := jwt.NewWithClaims(jwt.GetSigningMethod("RS256"), jwt.MapClaims{
"UserInfo": claims,
"exp": time.Now().Add(time.Hour * 24 * 3).Unix(),
})
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString(signKey)
if err != nil {
logger.Error("Tried signing key", "method", "GenerateToken", "message", err)
return "", err
}
return tokenString, nil
}
func getPermission(roles []*users.Role) (map[string]string, error) {
var ids []string
for _, role := range roles {
ids = append(ids, role.Id)
}
rolesID := authorization.RolesID{Ids: ids}
list, err := authorizationClient.FetchPermissions(context.Background(), &rolesID)
if err != nil || list.Values == nil {
return map[string]string{}, err
}
return list.Values, err
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment