Skip to content

Commit

Permalink
Merge pull request #132 from containerish/password-management-apis
Browse files Browse the repository at this point in the history
Password management apis
  • Loading branch information
guacamole authored Mar 31, 2022
2 parents 811d4f5 + 681ea12 commit 7f603ba
Show file tree
Hide file tree
Showing 21 changed files with 387 additions and 136 deletions.
2 changes: 2 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Authentication interface {
ReadUserWithSession(ctx echo.Context) error
RenewAccessToken(ctx echo.Context) error
VerifyEmail(ctx echo.Context) error
ResetPassword(ctx echo.Context) error
Invites(ctx echo.Context) error
}

// New is the constructor function returns an Authentication implementation
Expand Down
4 changes: 3 additions & 1 deletion auth/bcrypt.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package auth

import "golang.org/x/crypto/bcrypt"
import (
"golang.org/x/crypto/bcrypt"
)

const bcryptMinCost = 6

Expand Down
38 changes: 38 additions & 0 deletions auth/invites.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package auth

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

"github.com/labstack/echo/v4"
)

type List struct {
Emails string
}

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{
"error": err.Error(),
"msg": "error decode body, expecting and array of emails",
})
}
err = a.emailClient.WelcomeEmail(strings.Split(list.Emails, ","))
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"msg": "err sending invites",
})
}

a.logger.Log(ctx, err)
return ctx.JSON(http.StatusAccepted, echo.Map{
"msg": "success",
})
}
3 changes: 1 addition & 2 deletions auth/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/containerish/OpenRegistry/types"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
)

Expand Down Expand Up @@ -158,7 +157,7 @@ func (a *auth) createOAuthClaims(u types.User, token *oauth2.Token) PlatformClai
StandardClaims: jwt.StandardClaims{
Audience: a.c.Endpoint(),
ExpiresAt: time.Now().Add(time.Hour).Unix(),
Id: uuid.NewString(),
Id: u.Id,
IssuedAt: time.Now().Unix(),
Issuer: a.c.Endpoint(),
NotBefore: time.Now().Unix(),
Expand Down
15 changes: 14 additions & 1 deletion auth/jwt_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@ package auth
import (
"fmt"
"net/http"
"strings"
"time"

"github.com/containerish/OpenRegistry/types"
"github.com/fatih/color"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

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

// JWT basically uses the default JWT middleware by echo, but has a slightly different skipper func
func (a *auth) JWT() echo.MiddlewareFunc {
return middleware.JWTWithConfig(middleware.JWTConfig{
Skipper: func(ctx echo.Context) bool {
if strings.HasPrefix(ctx.Request().RequestURI, "/auth") {
return false
}

// if JWT_AUTH is not set, we don't need to perform JWT authentication
jwtAuth, ok := ctx.Get(JWT_AUTH_KEY).(bool)
if !ok {
Expand All @@ -33,6 +44,7 @@ func (a *auth) JWT() echo.MiddlewareFunc {
ErrorHandlerWithContext: func(err error, ctx echo.Context) error {
// ErrorHandlerWithContext only logs the failing requtest
ctx.Set(types.HandlerStartTime, time.Now())
color.Red(ctx.QueryParam("token"))
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": err.Error(),
Expand All @@ -45,6 +57,7 @@ func (a *auth) JWT() echo.MiddlewareFunc {
SigningKeys: map[string]interface{}{},
SigningMethod: jwt.SigningMethodHS256.Name,
Claims: &Claims{},
TokenLookup: fmt.Sprintf("cookie:%s,header:%s", AccessCookieKey, echo.HeaderAuthorization),
})
}

Expand Down Expand Up @@ -73,7 +86,7 @@ func (a *auth) ACL() echo.MiddlewareFunc {

username := ctx.Param("username")

user, err := a.pgStore.GetUserById(ctx.Request().Context(), claims.Id)
user, err := a.pgStore.GetUserById(ctx.Request().Context(), claims.Id, false)
if err != nil {
a.logger.Log(ctx, err)
return ctx.NoContent(http.StatusUnauthorized)
Expand Down
2 changes: 1 addition & 1 deletion auth/renew.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (a *auth) RenewAccessToken(ctx echo.Context) error {
}

userId := claims.Id
user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId)
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{
Expand Down
125 changes: 125 additions & 0 deletions auth/reset_password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package auth

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

"github.com/containerish/OpenRegistry/services/email"
"github.com/containerish/OpenRegistry/types"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
)

func (a *auth) ResetPassword(ctx echo.Context) error {
token, ok := ctx.Get("user").(*jwt.Token)
if !ok {
err := fmt.Errorf("JWT token can not be empty")
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": err.Error(),
})
}

c, ok := token.Claims.(*Claims)
if !ok {
err := fmt.Errorf("invalid claims in JWT")
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
})
}

userId := c.Id
user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, true)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusNotFound, echo.Map{
"error": err.Error(),
})
}

var pwd *types.Password

kind := ctx.QueryParam("kind")

if kind == "forgot" {
if err = a.emailClient.SendEmail(user, token.Raw, email.ResetPasswordEmailKind); err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"msg": "error sending reset password link",
})
}
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusAccepted, echo.Map{
"msg": "success",
})

}

err = json.NewDecoder(ctx.Request().Body).Decode(&pwd)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"msg": "request body could not be decoded",
})
}

if kind == "forgot_password_callback" {
hashPassword, err := a.hashPassword(pwd.NewPassword)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
})
}

if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
})
}

return ctx.NoContent(http.StatusOK)
}

if !a.verifyPassword(user.Password, pwd.OldPassword) {
err = fmt.Errorf("passwords do not match")
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
})
}

if pwd.OldPassword == pwd.NewPassword {
err = fmt.Errorf("new password can not be same as old password")
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
})
}

newHashedPwd, err := a.hashPassword(pwd.NewPassword)
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
})
}

if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, newHashedPwd); err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"msg": "error updating user in db",
})
}

a.logger.Log(ctx, nil)
return ctx.JSON(http.StatusAccepted, echo.Map{
"msg": "success",
})
}
1 change: 1 addition & 0 deletions auth/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (a *auth) SignIn(ctx echo.Context) error {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"msg": "error while get user",
})
}

Expand Down
15 changes: 4 additions & 11 deletions auth/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"unicode"

"github.com/containerish/OpenRegistry/config"
"github.com/containerish/OpenRegistry/services/email"
"github.com/containerish/OpenRegistry/types"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
Expand Down Expand Up @@ -79,15 +80,7 @@ func (a *auth) SignUp(ctx echo.Context) error {
})
}

token, err := a.newWebLoginToken(newUser.Id, newUser.Username, "access")
if err != nil {
a.logger.Log(ctx, err)
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"code": "CREATE_NEW_ACCESS_TOKEN",
})
}

token := uuid.NewString()
err = a.pgStore.AddVerifyEmail(ctx.Request().Context(), token, newUser.Id)
if err != nil {
ctx.Set(types.HttpEndpointErrorKey, err.Error())
Expand All @@ -97,7 +90,7 @@ func (a *auth) SignUp(ctx echo.Context) error {
})
}

if err = a.emailClient.SendEmail(newUser, token); err != nil {
if err = a.emailClient.SendEmail(newUser, token, email.VerifyEmailKind); err != nil {
ctx.Set(types.HttpEndpointErrorKey, err.Error())
return ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
Expand All @@ -106,7 +99,7 @@ func (a *auth) SignUp(ctx echo.Context) error {

a.logger.Log(ctx, err)
return ctx.JSON(http.StatusCreated, echo.Map{
"message": "user successfully created",
"message": "signup was successful, please check your email to activate your account",
})
}

Expand Down
Loading

0 comments on commit 7f603ba

Please sign in to comment.