-
-
Save paultyng/5d71bbb34e0f27af36aef57ada8c3492 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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