Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.42.1
version: v1.45.2
args: --config golangci.yaml
1 change: 1 addition & 0 deletions 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
ResetForgottenPassword(ctx echo.Context) error
ForgotPassword(ctx echo.Context) error
Invites(ctx echo.Context) error
}
Expand Down
24 changes: 7 additions & 17 deletions auth/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package auth
import (
"context"
"fmt"
"net"
"net/http"
"time"

Expand Down Expand Up @@ -38,6 +37,7 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error {
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
delete(a.oauthStateStore, stateToken)
Expand Down Expand Up @@ -93,20 +93,15 @@ func (a *auth) GithubLoginCallbackHandler(ctx echo.Context) error {

oauthUser.Password = refreshToken
if err = a.pgStore.AddOAuthUser(ctx.Request().Context(), &oauthUser); err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"code": "GH_OAUTH_STORE_OAUTH_USER",
})
redirectPath := fmt.Sprintf("%s/%s?error=%s", a.c.WebAppEndpoint, a.c.WebAppErrorRedirectPath, err.Error())
echoErr := ctx.Redirect(http.StatusTemporaryRedirect, redirectPath)
a.logger.Log(ctx, err)
return echoErr
}

sessionId := uuid.NewString()
if err = a.pgStore.AddSession(ctx.Request().Context(), sessionId, refreshToken, oauthUser.Username); err != nil {
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "ERR_CREATING_SESSION",
})
echoErr := ctx.Redirect(http.StatusTemporaryRedirect, a.c.WebAppErrorRedirectPath)
a.logger.Log(ctx, err)
return echoErr
}
Expand Down Expand Up @@ -134,23 +129,18 @@ func (a *auth) createCookie(name string, value string, httpOnly bool, expiresAt

secure := true
sameSite := http.SameSiteStrictMode
domain := a.c.Registry.FQDN
if a.c.Environment == config.Local {
secure = false
sameSite = http.SameSiteLaxMode
domain = "localhost"
}

webappEndpoint := a.c.WebAppEndpoint
if a.c.Environment == config.Local {
host, _, err := net.SplitHostPort(webappEndpoint)
if err != nil {
webappEndpoint = host
}
}
cookie := &http.Cookie{
Name: name,
Value: value,
Path: "/",
Domain: webappEndpoint,
Domain: domain,
Expires: expiresAt,
Secure: secure,
SameSite: sameSite,
Expand Down
2 changes: 0 additions & 2 deletions auth/jwt_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"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"
Expand Down Expand Up @@ -45,7 +44,6 @@ 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 Down
162 changes: 114 additions & 48 deletions auth/reset_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/labstack/echo/v4"
)

func (a *auth) ResetPassword(ctx echo.Context) error {
func (a *auth) ResetForgottenPassword(ctx echo.Context) error {
token, ok := ctx.Get("user").(*jwt.Token)
if !ok {
err := fmt.Errorf("ERR_EMPTY_TOKEN")
Expand All @@ -36,6 +36,18 @@ func (a *auth) ResetPassword(ctx echo.Context) error {
return echoErr
}

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

userId := c.Id
user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, true)
if err != nil {
Expand All @@ -47,79 +59,112 @@ func (a *auth) ResetPassword(ctx echo.Context) error {
return echoErr
}

var pwd *types.Password
kind := ctx.QueryParam("kind")
if err = validatePassword(pwd.NewPassword); err != nil {
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": `password must be alphanumeric, at least 8 chars long, must have at least one special character
and an uppercase letter`,
})
a.logger.Log(ctx, err)
return echoErr
}

err = json.NewDecoder(ctx.Request().Body).Decode(&pwd)
if err != nil {
if a.verifyPassword(user.Password, pwd.NewPassword) {

err = fmt.Errorf("new password can not be same as old password")
// error is already user friendly
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "request body could not be decoded",
"message": err.Error(),
})
a.logger.Log(ctx, err)
return echoErr
}
_ = ctx.Request().Body.Close()

if err = verifyPassword(pwd.NewPassword); err != nil {
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": `password must be alphanumeric, at least 8 chars long, must have at least one special character
and an uppercase letter`,
hashPassword, err := a.hashPassword(pwd.NewPassword)
if err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "ERR_HASH_NEW_PASSWORD",
})
a.logger.Log(ctx, err)
return echoErr
}

if kind == "forgot_password_callback" {
hashPassword, err := a.hashPassword(pwd.NewPassword)
if err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "ERR_HASH_NEW_PASSWORD",
})
a.logger.Log(ctx, err)
return echoErr
}
if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "error updating new password",
})
a.logger.Log(ctx, err)
return echoErr
}

if user.Password == hashPassword {
err = fmt.Errorf("new password can not be same as old password")
// error is already user friendly
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": err.Error(),
})
a.logger.Log(ctx, err)
return echoErr
}
err = ctx.JSON(http.StatusAccepted, echo.Map{
"message": "password changed successfully",
})
a.logger.Log(ctx, err)
return err
}

if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "error updating new password",
})
a.logger.Log(ctx, err)
return echoErr
}
func (a *auth) ResetPassword(ctx echo.Context) error {
token, ok := ctx.Get("user").(*jwt.Token)
if !ok {
err := fmt.Errorf("ERR_EMPTY_TOKEN")
echoErr := ctx.JSON(http.StatusUnauthorized, echo.Map{
"error": err.Error(),
"message": "JWT token can not be empty",
})
a.logger.Log(ctx, err)
return echoErr
}

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

if pwd.OldPassword == pwd.NewPassword {
err = fmt.Errorf("OLD_NEW_PWD_SAME")
var pwd *types.Password
err := json.NewDecoder(ctx.Request().Body).Decode(&pwd)
if err != nil {
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "new password can not be same as old password",
"message": "request body could not be decoded",
})
a.logger.Log(ctx, err)
return echoErr
}
_ = ctx.Request().Body.Close()

newHashedPwd, err := a.hashPassword(pwd.NewPassword)
userId := c.Id
user, err := a.pgStore.GetUserById(ctx.Request().Context(), userId, true)
if err != nil {
echoErr := ctx.JSON(http.StatusNotFound, echo.Map{
"error": err.Error(),
"message": "error getting user by ID from DB",
})
a.logger.Log(ctx, err)
return echoErr
}

// compare the current password with password hash from DB
if !a.verifyPassword(user.Password, pwd.OldPassword) {
err = fmt.Errorf("ERR_WRONG_PASSWORD")
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": "password is wrong",
})
a.logger.Log(ctx, err)
return echoErr
}

hashPassword, err := a.hashPassword(pwd.NewPassword)
if err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
Expand All @@ -129,7 +174,28 @@ and an uppercase letter`,
return echoErr
}

if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, newHashedPwd); err != nil {
if user.Password == hashPassword {
err = fmt.Errorf("new password can not be same as old password")
// error is already user friendly
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": err.Error(),
})
a.logger.Log(ctx, err)
return echoErr
}

if err = validatePassword(pwd.NewPassword); err != nil {
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"message": `password must be alphanumeric, at least 8 chars long, must have at least one special character
and an uppercase letter`,
})
a.logger.Log(ctx, err)
return echoErr
}

if err = a.pgStore.UpdateUserPWD(ctx.Request().Context(), userId, hashPassword); err != nil {
echoErr := ctx.JSON(http.StatusInternalServerError, echo.Map{
"error": err.Error(),
"message": "error updating new password",
Expand Down
4 changes: 2 additions & 2 deletions auth/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (a *auth) SignUp(ctx echo.Context) error {
return echoErr
}

if err := verifyPassword(u.Password); err != nil {
if err := validatePassword(u.Password); err != nil {
// err.Error() is already user friendly
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
Expand Down Expand Up @@ -155,7 +155,7 @@ func verifyEmail(email string) error {
return nil
}

func verifyPassword(password string) error {
func validatePassword(password string) error {
var uppercasePresent bool
var lowercasePresent bool
var numberPresent bool
Expand Down
1 change: 1 addition & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ environment: local
debug: true
web_app_url: "http://localhost:3000"
web_app_redirect_url: "/"
web_app_error_redirect_path: "/auth/unhandled"
registry:
dns_address: localhost
version: master
Expand Down
30 changes: 19 additions & 11 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,35 @@ import (

type (
OpenRegistryConfig struct {
Registry *Registry `mapstructure:"registry"`
StoreConfig *Store `mapstructure:"database"`
AuthConfig *Auth `mapstructure:"auth"`
LogConfig *Log `mapstructure:"log_service"`
SkynetConfig *Skynet `mapstructure:"skynet"`
OAuth *OAuth `mapstructure:"oauth"`
Email *Email `mapstructure:"email"`
Environment string `mapstructure:"environment"`
WebAppEndpoint string `mapstructure:"web_app_url"`
WebAppRedirectURL string `mapstructure:"web_app_redirect_url"`
Debug bool `mapstructure:"debug"`
Registry *Registry `mapstructure:"registry"`
StoreConfig *Store `mapstructure:"database"`
AuthConfig *Auth `mapstructure:"auth"`
LogConfig *Log `mapstructure:"log_service"`
SkynetConfig *Skynet `mapstructure:"skynet"`
OAuth *OAuth `mapstructure:"oauth"`
Email *Email `mapstructure:"email"`
Environment string `mapstructure:"environment"`
WebAppEndpoint string `mapstructure:"web_app_url"`
WebAppRedirectURL string `mapstructure:"web_app_redirect_url"`
WebAppErrorRedirectPath string `mapstructure:"web_app_error_redirect_path"`
Debug bool `mapstructure:"debug"`
}

Registry struct {
TLS TLS `mapstructure:"tls"`
DNSAddress string `mapstructure:"dns_address"`
FQDN string `mapstructure:"fqdn"`
SigningSecret string `mapstructure:"jwt_signing_secret"`
Host string `mapstructure:"host"`
Services []string `mapstructure:"services"`
Port uint `mapstructure:"port"`
}

TLS struct {
PrivateKey string `mapstructure:"priv_key"`
PubKey string `mapstructure:"pub_key"`
}

Auth struct {
SupportedServices map[string]bool `mapstructure:"supported_services"`
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ require (
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
)

require github.com/sendgrid/rest v2.6.9+incompatible // indirect

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-playground/locales v0.14.0 // indirect
Expand Down
Loading