Skip to content

Instantly share code, notes, and snippets.

@paultyng
Forked from codesword/authentication.go
Created April 17, 2017 18:36
Show Gist options
  • Select an option

  • Save paultyng/5d71bbb34e0f27af36aef57ada8c3492 to your computer and use it in GitHub Desktop.

Select an option

Save paultyng/5d71bbb34e0f27af36aef57ada8c3492 to your computer and use it in GitHub Desktop.

Revisions

  1. Ikem Okonkwo renamed this gist Jan 22, 2017. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. Ikem Okonkwo created this gist Jan 22, 2017.
    216 changes: 216 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,216 @@
    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
    }