Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced Logging #134

Merged
merged 3 commits into from
Apr 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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