package api import ( "fmt" "os" "time" "myapp/domain/model" "myapp/domain/repository" "myapp/util" "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/middleware/jwt" ) const defaultSecretKey = "sercrethatmaycontainch@r$32chars" func getSecretKey() string { secret := os.Getenv(util.AppName + "_SECRET") if secret == "" { return defaultSecretKey } return secret } // UserClaims represents the user token claims. type UserClaims struct { UserID string `json:"user_id"` Roles []model.Role `json:"roles"` } // Validate implements the custom struct claims validator, // this is totally optionally and maybe unnecessary but good to know how. func (u *UserClaims) Validate() error { if u.UserID == "" { return fmt.Errorf("%w: %s", jwt.ErrMissingKey, "user_id") } return nil } // Verify allows only authorized clients. func Verify() iris.Handler { secret := getSecretKey() verifier := jwt.NewVerifier(jwt.HS256, []byte(secret), jwt.Expected{Issuer: util.AppName}) verifier.Extractors = []jwt.TokenExtractor{jwt.FromHeader} // extract token only from Authorization: Bearer $token return verifier.Verify(func() interface{} { return new(UserClaims) }) } // AllowAdmin allows only authorized clients with "admin" access role. // Should be registered after Verify. func AllowAdmin(ctx iris.Context) { if !IsAdmin(ctx) { ctx.StopWithText(iris.StatusForbidden, "admin access required") return } ctx.Next() } // SignIn accepts the user form data and returns a token to authorize a client. func SignIn(repo repository.UserRepository) iris.Handler { secret := getSecretKey() signer := jwt.NewSigner(jwt.HS256, []byte(secret), 15*time.Minute) return func(ctx iris.Context) { /* type LoginForm struct { Username string `form:"username"` Password string `form:"password"` } and ctx.ReadForm OR use the ctx.FormValue(s) method. */ var ( username = ctx.FormValue("username") password = ctx.FormValue("password") ) user, ok := repo.GetByUsernameAndPassword(username, password) if !ok { ctx.StopWithText(iris.StatusBadRequest, "wrong username or password") return } claims := UserClaims{ UserID: user.ID, Roles: user.Roles, } // Optionally, generate a JWT ID. jti, err := util.GenerateUUID() if err != nil { ctx.StopWithError(iris.StatusInternalServerError, err) return } token, err := signer.Sign(claims, jwt.Claims{ ID: jti, Issuer: util.AppName, }) if err != nil { ctx.StopWithError(iris.StatusInternalServerError, err) return } ctx.Write(token) } } // SignOut invalidates a user from server-side using the jwt Blocklist. func SignOut(ctx iris.Context) { ctx.Logout() // this is automatically binded to a function which invalidates the current request token by the JWT Verifier above. } // GetClaims returns the current authorized client claims. func GetClaims(ctx iris.Context) *UserClaims { claims := jwt.Get(ctx).(*UserClaims) return claims } // GetUserID returns the current authorized client's user id extracted from claims. func GetUserID(ctx iris.Context) string { return GetClaims(ctx).UserID } // IsAdmin reports whether the current client has admin access. func IsAdmin(ctx iris.Context) bool { for _, role := range GetClaims(ctx).Roles { if role == model.Admin { return true } } return false }