-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: guacamole <[email protected]>
- Loading branch information
Showing
10 changed files
with
343 additions
and
313 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package auth | ||
|
||
import ( | ||
"github.com/jay-dee7/parachute/cache" | ||
"github.com/jay-dee7/parachute/config" | ||
"github.com/labstack/echo/v4" | ||
) | ||
|
||
type Authentication interface { | ||
SignUp(ctx echo.Context) error | ||
SignIn(ctx echo.Context) error | ||
} | ||
|
||
type auth struct { | ||
store cache.Store | ||
c *config.RegistryConfig | ||
} | ||
|
||
func New(s cache.Store, c *config.RegistryConfig) Authentication { | ||
a := &auth{ | ||
store: s, | ||
c: c, | ||
} | ||
|
||
return a | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package auth | ||
|
||
import "golang.org/x/crypto/bcrypt" | ||
|
||
var bcryptMincost = 6 | ||
|
||
func (a *auth) hashPassword(password string) (string, error) { | ||
// Convert password string to byte slice | ||
var passwordBytes = []byte(password) | ||
|
||
// Hash password with Bcrypt's min cost | ||
hashedPasswordBytes, err := bcrypt.GenerateFromPassword(passwordBytes, bcryptMincost) | ||
|
||
return string(hashedPasswordBytes), err | ||
} | ||
|
||
func (a *auth) verifyPassword(hashedPassword, currPassword string) bool { | ||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(currPassword)) | ||
|
||
return err == nil | ||
} | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package auth | ||
|
||
import ( | ||
"fmt" | ||
"github.com/golang-jwt/jwt" | ||
"time" | ||
) | ||
|
||
func (a *auth) newToken(u User) (string,error) { | ||
token := jwt.New(jwt.SigningMethodHS256) | ||
|
||
// Set claims | ||
claims := token.Claims.(jwt.MapClaims) | ||
claims["username"] = u.Username | ||
claims["push"] = true | ||
claims["exp"] = time.Now().Add(time.Hour * 24*14).Unix() | ||
|
||
// Generate encoded token and send it as response. | ||
fmt.Printf("secret %s:",a.c.SigningSecret) | ||
t, err := token.SignedString([]byte(a.c.SigningSecret)) | ||
if err != nil { | ||
return "",err | ||
|
||
} | ||
return t, nil | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package auth | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/labstack/echo/v4" | ||
"net/http" | ||
) | ||
|
||
func (a *auth) SignIn(ctx echo.Context) error { | ||
var user User | ||
|
||
if err := json.NewDecoder(ctx.Request().Body).Decode(&user); err!= nil { | ||
return ctx.JSON(http.StatusBadRequest,echo.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
if user.Email == "" || user.Password == ""{ | ||
return ctx.JSON(http.StatusBadRequest,echo.Map{ | ||
"error": "Email/Password cannot be empty", | ||
}) | ||
} | ||
|
||
if err:= verifyEmail(user.Email); err!= nil { | ||
return ctx.JSON(http.StatusBadRequest,echo.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
|
||
key := fmt.Sprintf("%s/%s",UserNameSpace,user.Email) | ||
bz,err := a.store.Get([]byte(key)) | ||
if err!= nil{ | ||
return ctx.JSON(http.StatusBadRequest,echo.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
|
||
var userFromDb User | ||
if err := json.Unmarshal(bz,&userFromDb); err!= nil { | ||
return ctx.JSON(http.StatusInternalServerError,echo.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
|
||
if !a.verifyPassword(userFromDb.Password,user.Password) { | ||
return ctx.JSON(http.StatusUnauthorized,echo.Map{ | ||
"error": "invalid password", | ||
}) | ||
} | ||
|
||
token,err := a.newToken(user) | ||
if err!= nil{ | ||
return ctx.JSON(http.StatusInternalServerError,echo.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
|
||
return ctx.JSON(http.StatusOK,echo.Map{ | ||
"message": "user authenticated", | ||
"token": token, | ||
}) | ||
|
||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package auth | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/labstack/echo/v4" | ||
"io" | ||
"net/http" | ||
"regexp" | ||
"strings" | ||
"unicode" | ||
) | ||
|
||
type User struct { | ||
Id string `json:"id"` | ||
Email string `json:"email"` | ||
Username string `json:"username"` | ||
Password string `json:"password"` | ||
} | ||
|
||
const UserNameSpace = "users" | ||
|
||
func (a *auth) ValidateUser(u User) error { | ||
|
||
if err := verifyEmail(u.Email); err!= nil {return err} | ||
key := fmt.Sprintf("%s/%s", UserNameSpace, u.Email) | ||
_, err := a.store.Get([]byte(key)) | ||
if err == nil { | ||
return fmt.Errorf("user already exists, try loggin in or password reset") | ||
} | ||
|
||
if len(u.Username) < 3 { | ||
return fmt.Errorf("username should be atleast 3 chars") | ||
} | ||
|
||
bz, err := a.store.ListWithPrefix([]byte(UserNameSpace)) | ||
if err != nil { | ||
return fmt.Errorf("internal server error") | ||
} | ||
|
||
if bz != nil { | ||
|
||
var userList []User | ||
fmt.Printf("%s\n", bz) | ||
if err := json.Unmarshal(bz, &userList); err != nil { | ||
|
||
if strings.Contains(err.Error(), "object into Go value of type []auth.User") { | ||
var usr User | ||
if e := json.Unmarshal(bz, &usr); e != nil { | ||
return e | ||
} | ||
userList = append(userList, usr) | ||
} else { | ||
return fmt.Errorf("error in unmarshaling: %w", err) | ||
} | ||
} | ||
|
||
for _, user := range userList { | ||
if u.Username == user.Username { | ||
return fmt.Errorf("username already taken") | ||
} | ||
} | ||
} | ||
return verifyPassword(u.Password) | ||
} | ||
|
||
func 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 | ||
} | ||
|
||
func verifyPassword(password string) error { | ||
var uppercasePresent bool | ||
var lowercasePresent bool | ||
var numberPresent bool | ||
var specialCharPresent bool | ||
const minPassLength = 8 | ||
const maxPassLength = 64 | ||
var passLen int | ||
var errorString string | ||
|
||
for _, ch := range password { | ||
switch { | ||
case unicode.IsNumber(ch): | ||
numberPresent = true | ||
passLen++ | ||
case unicode.IsUpper(ch): | ||
uppercasePresent = true | ||
passLen++ | ||
case unicode.IsLower(ch): | ||
lowercasePresent = true | ||
passLen++ | ||
case unicode.IsPunct(ch) || unicode.IsSymbol(ch): | ||
specialCharPresent = true | ||
passLen++ | ||
case ch == ' ': | ||
passLen++ | ||
} | ||
} | ||
appendError := func(err string) { | ||
if len(strings.TrimSpace(errorString)) != 0 { | ||
errorString += ", " + err | ||
} else { | ||
errorString = err | ||
} | ||
} | ||
if !lowercasePresent { | ||
appendError("lowercase letter missing") | ||
} | ||
if !uppercasePresent { | ||
appendError("uppercase letter missing") | ||
} | ||
if !numberPresent { | ||
appendError("atleast one numeric character required") | ||
} | ||
if !specialCharPresent { | ||
appendError("special character missing") | ||
} | ||
if !(minPassLength <= passLen && passLen <= maxPassLength) { | ||
appendError(fmt.Sprintf("password length must be between %d to %d characters long", minPassLength, maxPassLength)) | ||
} | ||
|
||
if len(errorString) != 0 { | ||
return fmt.Errorf(errorString) | ||
} | ||
return nil | ||
} | ||
|
||
func (a *auth) SignUp(ctx echo.Context) error { | ||
|
||
var u User | ||
bz, err := io.ReadAll(ctx.Request().Body) | ||
if err != nil { | ||
return ctx.JSON(http.StatusBadRequest, echo.Map{ | ||
"error": err.Error(), | ||
"msg": "invalid request body", | ||
}) | ||
} | ||
ctx.Request().Body.Close() | ||
|
||
if err := json.Unmarshal(bz, &u); err != nil { | ||
return ctx.JSON(http.StatusBadRequest, echo.Map{ | ||
"error": err.Error(), | ||
"msg": "couldn't marshal user", | ||
}) | ||
} | ||
|
||
if err := a.ValidateUser(u); err != nil { | ||
return ctx.JSON(http.StatusBadRequest, echo.Map{ | ||
"error": err.Error(), | ||
"msg": "bananas", | ||
}) | ||
} | ||
|
||
hpwd, err := a.hashPassword(u.Password) | ||
if err != nil { | ||
return ctx.JSON(http.StatusInternalServerError, echo.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
u.Password = hpwd | ||
bz, _ = json.Marshal(u) | ||
|
||
key := fmt.Sprintf("%s/%s", UserNameSpace, u.Email) | ||
if err := a.store.Set([]byte(key), bz); err != nil { | ||
return ctx.JSON(http.StatusInternalServerError, echo.Map{ | ||
"error": err.Error(), | ||
}) | ||
} | ||
|
||
return ctx.JSON(http.StatusCreated, echo.Map{ | ||
"message": "user successfully created", | ||
}) | ||
} |
This file contains 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
This file contains 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
Oops, something went wrong.