Skip to content

Commit

Permalink
added registration apis
Browse files Browse the repository at this point in the history
Signed-off-by: guacamole <[email protected]>
  • Loading branch information
guacamole committed Jun 20, 2021
1 parent 82485cd commit 6dbf925
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 313 deletions.
26 changes: 26 additions & 0 deletions auth/auth.go
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
}
22 changes: 22 additions & 0 deletions auth/bcrypt.go
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
}

26 changes: 26 additions & 0 deletions auth/jwt.go
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
}
63 changes: 63 additions & 0 deletions auth/signin.go
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,
})

}
182 changes: 182 additions & 0 deletions auth/signup.go
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",
})
}
6 changes: 6 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type (
SkynetStorePath string `mapstructure:"skynet_store_path"`
TLSCertPath string `mapstructure:"tls_cert_path"`
TLSKeyPath string `mapstructure:"tls_key_path"`
SigningSecret string `mapstructure:"signing_secret"`
SkynetConfig SkynetConfig `mapstructure:"skynet_config"`
}

Expand Down Expand Up @@ -51,6 +52,11 @@ func Load(path string) (*RegistryConfig, error) {
return nil, err
}

if config.SigningSecret == "" {
fmt.Println("signing secret absent")
os.Exit(1)
}

return &config, nil
}

Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ require (
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/NebulousLabs/go-skynet/v2 v2.0.1
github.com/containerd/containerd v1.5.2 // indirect
github.com/dgraph-io/badger/v3 v3.2103.0 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/dgraph-io/badger/v3 v3.2103.0
github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker v20.10.6+incompatible
github.com/fatih/color v1.7.0
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
github.com/google/go-containerregistry v0.5.1
github.com/labstack/echo-contrib v0.11.0 // indirect
github.com/labstack/echo-contrib v0.11.0
github.com/labstack/echo/v4 v4.3.0
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/prometheus/common v0.28.0 // indirect
github.com/rs/zerolog v1.22.0
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/viper v1.7.1
github.com/uber-go/atomic v1.4.0 // indirect
github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b
gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
Expand All @@ -28,4 +28,4 @@ require (
google.golang.org/grpc v1.38.0 // indirect
)

replace github.com/NebulousLabs/go-skynet/v2 => /Users/jasdeep/oss/go-skynet
replace github.com/NebulousLabs/go-skynet/v2 => /Users/gunjanvalecha/oss/go-skynet
Loading

0 comments on commit 6dbf925

Please sign in to comment.