Skip to content

Commit

Permalink
Merge pull request #134 from containerish/touchups
Browse files Browse the repository at this point in the history
Enhanced Logging
  • Loading branch information
guacamole authored Apr 3, 2022
2 parents 7f603ba + bb0651e commit a308a20
Show file tree
Hide file tree
Showing 31 changed files with 927 additions and 388 deletions.
3 changes: 2 additions & 1 deletion auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Authentication interface {
RenewAccessToken(ctx echo.Context) error
VerifyEmail(ctx echo.Context) error
ResetPassword(ctx echo.Context) error
ForgotPassword(ctx echo.Context) error
Invites(ctx echo.Context) error
}

Expand All @@ -48,7 +49,7 @@ func New(
}

ghClient := gh.NewClient(nil)
emailClient := email.New(c.Email, c.Endpoint())
emailClient := email.New(c.Email, c.WebAppEndpoint)

a := &auth{
c: c,
Expand Down
60 changes: 37 additions & 23 deletions auth/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error {
stateToken := ctx.FormValue("state")
_, ok := a.oauthStateStore[stateToken]
if !ok {
a.logger.Log(ctx, fmt.Errorf("missing or invalid state token"))
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": "missing or invalid state token",
err := fmt.Errorf("INVALID_STATE_TOKEN")
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "missing or invalid state token",
})
a.logger.Log(ctx, err)
return echoErr
}
// no need to compare the stateToken from QueryParam \w stateToken from a.oauthStateStore
// the key is the actual token :p
Expand All @@ -42,73 +45,84 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error {
code := ctx.FormValue("code")
token, err := a.github.Exchange(context.Background(), code)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"code": "GITHUB_EXCHANGE_ERR",
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "github exchange error",
"code": "GITHUB_EXCHANGE_ERR",
})
a.logger.Log(ctx, err)
return echoErr
}

req, err := a.ghClient.NewRequest(http.MethodGet, "/user", nil)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusPreconditionFailed, echo.Map{
"error": err.Error(),
"code": "GH_CLIENT_REQ_FAILED",
echoErr := ctx.JSON(http.StatusPreconditionFailed, echo.Map{
"error": err.Error(),
"message": "github client request failed",
"code": "GH_CLIENT_REQ_FAILED",
})
a.logger.Log(ctx, err)
return echoErr
}

req.Header.Set("Authorization", "token "+token.AccessToken)
var oauthUser types.User
_, err = a.ghClient.Do(ctx.Request().Context(), req, &oauthUser)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"code": "GH_CLIENT_REQ_EXEC_FAILED",
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "github client request execution failed",
"code": "GH_CLIENT_REQ_EXEC_FAILED",
})
a.logger.Log(ctx, err)
return echoErr
}

oauthUser.Username = oauthUser.Login
oauthUser.Id = uuid.NewString()

accessToken, refreshToken, err := a.SignOAuthToken(oauthUser, token)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"cause": "JWT_SIGNING",
})
a.logger.Log(ctx, err)
return echoErr
}

oauthUser.Password = refreshToken
if err = a.pgStore.AddOAuthUser(ctx.Request().Context(), &oauthUser); err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"code": "GH_OAUTH_STORE_OAUTH_USER",
})
a.logger.Log(ctx, err)
return echoErr
}

sessionId := uuid.NewString()
if err = a.pgStore.AddSession(ctx.Request().Context(), sessionId, refreshToken, oauthUser.Username); err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "ERR_CREATING_SESSION",
})
a.logger.Log(ctx, err)
return echoErr
}
val := fmt.Sprintf("%s:%s", sessionId, oauthUser.Id)

sessionCookie := a.createCookie("session_id", val, false, time.Now().Add(time.Hour*750))
accessCookie := a.createCookie("access", accessToken, true, time.Now().Add(time.Hour))
accessCookie := a.createCookie("access", accessToken, true, time.Now().Add(time.Hour*750))
refreshCookie := a.createCookie("refresh", refreshToken, true, time.Now().Add(time.Hour*750))

ctx.SetCookie(accessCookie)
ctx.SetCookie(refreshCookie)
ctx.SetCookie(sessionCookie)

err = ctx.Redirect(http.StatusTemporaryRedirect, a.c.WebAppRedirectURL)
a.logger.Log(ctx, nil)
return ctx.Redirect(http.StatusTemporaryRedirect, a.c.WebAppRedirectURL)
return err
}

const (
Expand Down
72 changes: 62 additions & 10 deletions auth/invites.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package auth

import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"

"github.com/labstack/echo/v4"
Expand All @@ -16,23 +18,73 @@ func (a *auth) Invites(ctx echo.Context) error {
var list List
err := json.NewDecoder(ctx.Request().Body).Decode(&list)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"msg": "error decode body, expecting and array of emails",
})
a.logger.Log(ctx, err)
return echoErr
}
err = a.emailClient.WelcomeEmail(strings.Split(list.Emails, ","))
if err != nil {

if list.Emails == "" {
err := fmt.Errorf("ERR_EMPTY_LIST")
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err,
"message": "cannot send empty list",
})
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"msg": "err sending invites",
return echoErr
}

emails := strings.Split(list.Emails, ",")

if err = a.validateEmailList(emails); err != nil {
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "error validating email list",
})
a.logger.Log(ctx, err)
return echoErr
}

a.logger.Log(ctx, err)
return ctx.JSON(http.StatusAccepted, echo.Map{
"msg": "success",
err = a.emailClient.WelcomeEmail(emails)
if err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "err sending invites",
})
a.logger.Log(ctx, err)
return echoErr
}

err = ctx.JSON(http.StatusAccepted, echo.Map{
"message": "invites sent successfully",
})
a.logger.Log(ctx, err)
return err
}

func (a *auth) validateEmailList(emails []string) error {
for _, email := range emails {
if err := a.verifyEmail(email); err != nil {
return fmt.Errorf("ERR_INVALID_EMAIL: %s", email)
}
}

return nil
}

func (a *auth) verifyEmail(email string) error {
if email == "" {
return fmt.Errorf("email can not be empty")
}

emailReg := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}" +
"[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")

if !emailReg.Match([]byte(email)) {
return fmt.Errorf("email format invalid")
}

return nil
}
10 changes: 7 additions & 3 deletions auth/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (a *auth) createOAuthClaims(u types.User, token *oauth2.Token) PlatformClai
OauthPayload: token,
StandardClaims: jwt.StandardClaims{
Audience: a.c.Endpoint(),
ExpiresAt: time.Now().Add(time.Hour).Unix(),
ExpiresAt: time.Now().Add(time.Hour * 750).Unix(),
Id: u.Id,
IssuedAt: time.Now().Unix(),
Issuer: a.c.Endpoint(),
Expand Down Expand Up @@ -234,14 +234,18 @@ claims format

func (a *auth) createClaims(id, tokenType string, acl AccessList) Claims {

var tokenLife int64
tokenLife := time.Now().Add(time.Minute * 10).Unix()
switch tokenType {
case "access":
tokenLife = time.Now().Add(time.Hour).Unix()
// TODO (jay-dee7)
// token can live for month now, but must be addressed when we implement PASETO
tokenLife = time.Now().Add(time.Hour * 750).Unix()
case "refresh":
tokenLife = time.Now().Add(time.Hour * 750).Unix()
case "service":
tokenLife = time.Now().Add(time.Hour * 750).Unix()
case "short-lived":
tokenLife = time.Now().Add(time.Minute * 30).Unix()
}

claims := Claims{
Expand Down
1 change: 1 addition & 0 deletions auth/jwt_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

const (
AccessCookieKey = "access"
RefreshCookKey = "refresh"
QueryToken = "token"
)

Expand Down
49 changes: 31 additions & 18 deletions auth/renew.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ func (a *auth) RenewAccessToken(ctx echo.Context) error {
c, err := ctx.Cookie("refresh")
if err != nil {
if err == http.ErrNoCookie {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusUnauthorized, echo.Map{
echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": err.Error(),
"message": "Unauthorised",
})
a.logger.Log(ctx, err)
return echoErr
}
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "error getting refresh cookie",
})
a.logger.Log(ctx, err)
return echoErr
}
refreshCookie := c.Value
var claims Claims
Expand All @@ -35,45 +37,56 @@ func (a *auth) RenewAccessToken(ctx echo.Context) error {
})
if err != nil {
if err == jwt.ErrSignatureInvalid {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusUnauthorized, echo.Map{
echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": err.Error(),
"message": "Signature error, unauthorised",
"message": "signature error, unauthorised",
})
a.logger.Log(ctx, err)
return echoErr
}
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),

echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "error parsing claims",
})
a.logger.Log(ctx, err)
return echoErr
}

if !tkn.Valid {
a.logger.Log(ctx, fmt.Errorf("invalid token, Unauthorised"))
return ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": "invalid token, unauthorised",
err := fmt.Errorf("invalid token, Unauthorised")
echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": err.Error(),
"message": "invalid token, unauthorised",
})
a.logger.Log(ctx, err)
return echoErr
}

userId := claims.Id
user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, false)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusUnauthorized, echo.Map{
echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": err.Error(),
"message": "user not found in database, unauthorised",
})
a.logger.Log(ctx, err)
return echoErr
}

tokenString, err := a.newWebLoginToken(userId, user.Username, "access")
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "error creating new web token",
})
a.logger.Log(ctx, err)
return echoErr
}

accessCookie := a.createCookie("access", tokenString, true, time.Now().Add(time.Hour))
ctx.SetCookie(accessCookie)
return ctx.NoContent(http.StatusNoContent)
err = ctx.NoContent(http.StatusNoContent)
a.logger.Log(ctx, err)
return err
}
Loading

0 comments on commit a308a20

Please sign in to comment.