diff --git a/_examples/README.md b/_examples/README.md index 1378c576..0c87b1d9 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -170,6 +170,7 @@ * [Singleton](mvc/singleton) * [Regexp](mvc/regexp/main.go) * [Session Controller](mvc/session-controller/main.go) + * [Authenticated Controller](mvc/authenticated-controller/main.go) * [Websocket Controller](mvc/websocket) * [Register Middleware](mvc/middleware) * Object-Relational Mapping diff --git a/_examples/mvc/authenticated-controller/main.go b/_examples/mvc/authenticated-controller/main.go new file mode 100644 index 00000000..1100bb68 --- /dev/null +++ b/_examples/mvc/authenticated-controller/main.go @@ -0,0 +1,104 @@ +// Package main shows how to use a dependency to check if a user is logged in +// using a special custom Go type `Authenticated`, which when, +// present on a controller's method or a field then +// it limits the visibility to "authenticated" users only. +// +// The same result could be done through a middleware as well, however using a static type +// any person reads your code can see that an "X" controller or method +// should only be used by "authenticated" users. +package main + +import ( + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/mvc" + "github.com/kataras/iris/v12/sessions" +) + +func main() { + app := iris.New() + app.Logger().SetLevel("debug") + + sess := sessions.New(sessions.Config{ + Cookie: "myapp_session_id", + AllowReclaim: true, + }) + app.Use(sess.Handler()) + + userRouter := app.Party("/user") + { + // Initialize a new MVC application on top of the "userRouter". + userApp := mvc.New(userRouter) + // Register Dependencies. + userApp.Register(authDependency) + + // Register Controllers. + userApp.Handle(new(MeController)) + userApp.Handle(new(UserController)) + } + + // Open a client, e.g. Postman and visit the below endpoints. + // GET: http://localhost:8080/user + // POST: http://localhost:8080/user/login + // GET: http://localhost:8080/user + // GET: http://localhost:8080/user/me + // POST: http://localhost:8080/user/logout + app.Listen(":8080") +} + +// Authenticated is a custom type used as "annotation" for resources that requires authentication, +// its value should be the logged user ID. +type Authenticated uint64 + +func authDependency(ctx iris.Context, session *sessions.Session) Authenticated { + userID := session.GetUint64Default("user_id", 0) + if userID == 0 { + // If execution was stopped + // any controller's method will not be executed at all. + ctx.StopWithStatus(iris.StatusUnauthorized) + return 0 + } + + return Authenticated(userID) +} + +// UserController serves the "public" User API. +type UserController struct { + Session *sessions.Session +} + +// PostLogin serves +// POST: /user/login +func (c *UserController) PostLogin() mvc.Response { + c.Session.Set("user_id", 1) + + // Redirect (you can still use the Context.Redirect if you want so). + return mvc.Response{ + Path: "/user", + Code: iris.StatusFound, + } +} + +// PostLogout serves +// POST: /user/logout +func (c *UserController) PostLogout(ctx iris.Context) { + c.Session.Man.Destroy(ctx) +} + +// GetMe showcases that the same type can be used inside controller's method too, +// a second controller like `MeController` is not required. +// GET: user/me +func (c *UserController) GetMe(_ Authenticated) string { + return `UserController.GetMe: The Authenticated type +can be used to secure a controller's method too.` +} + +// MeController provides the logged user's available actions. +type MeController struct { + CurrentUserID Authenticated +} + +// Get returns a message for the shake of the example. +// GET: /user +func (c *MeController) Get() string { + return "This will be executed only when the user is logged in" +} diff --git a/_examples/orm/gorm/main.go b/_examples/orm/gorm/main.go index d0906109..00a10b3c 100644 --- a/_examples/orm/gorm/main.go +++ b/_examples/orm/gorm/main.go @@ -61,7 +61,7 @@ func main() { } db.AutoMigrate(&User{}) // create table: // AutoMigrate run auto migration for given models, will only add missing fields, won't delete/change current data - app.Post("/post_user", func(context iris.Context) { + app.Post("/post_user", func(ctx iris.Context) { var user User user = User{ Username: "gorm", @@ -71,41 +71,41 @@ func main() { } if err := db.FirstOrCreate(&user); err == nil { app.Logger().Fatalf("created one record failed: %s", err.Error) - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusBadRequest, "error": err.Error, }) return } - context.JSON( + ctx.JSON( iris.Map{ "code": http.StatusOK, "data": user.Serializer(), }) }) - app.Get("/get_user/{id:uint}", func(context iris.Context) { + app.Get("/get_user/{id:uint}", func(ctx iris.Context) { var user User - id, _ := context.Params().GetUint("id") + id, _ := ctx.Params().GetUint("id") app.Logger().Println(id) if err := db.Where("id = ?", int(id)).First(&user).Error; err != nil { app.Logger().Fatalf("find one record failed: %t", err == nil) - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusBadRequest, "error": err.Error, }) return } - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusOK, "data": user.Serializer(), }) }) - app.Delete("/delete_user/{id:uint}", func(context iris.Context) { - id, _ := context.Params().GetUint("id") + app.Delete("/delete_user/{id:uint}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint("id") if id == 0 { - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusOK, "detail": "query param id should not be nil", }) @@ -114,23 +114,23 @@ func main() { var user User if err := db.Where("id = ?", id).First(&user).Error; err != nil { app.Logger().Fatalf("record not found") - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusOK, "detail": err.Error, }) return } db.Delete(&user) - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusOK, "data": user.Serializer(), }) }) - app.Patch("/patch_user/{id:uint}", func(context iris.Context) { - id, _ := context.Params().GetUint("id") + app.Patch("/patch_user/{id:uint}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint("id") if id == 0 { - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusOK, "detail": "query param id should not be nil", }) @@ -140,7 +140,7 @@ func main() { tx := db.Begin() if err := tx.Where("id = ?", id).First(&user).Error; err != nil { app.Logger().Fatalf("record not found") - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusOK, "detail": err.Error, }) @@ -148,19 +148,19 @@ func main() { } var body patchParam - context.ReadJSON(&body) + ctx.ReadJSON(&body) app.Logger().Println(body) if err := tx.Model(&user).Updates(map[string]interface{}{"username": body.Data.UserName, "password": body.Data.Password}).Error; err != nil { app.Logger().Fatalf("update record failed") tx.Rollback() - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusBadRequest, "error": err.Error, }) return } tx.Commit() - context.JSON(iris.Map{ + ctx.JSON(iris.Map{ "code": http.StatusOK, "data": user.Serializer(), }) diff --git a/_examples/routing/route-register-rule/main_test.go b/_examples/routing/route-register-rule/main_test.go index c6f11a30..9ac5ed9f 100644 --- a/_examples/routing/route-register-rule/main_test.go +++ b/_examples/routing/route-register-rule/main_test.go @@ -14,9 +14,9 @@ func TestRouteRegisterRuleExample(t *testing.T) { for _, method := range router.AllMethods { tt := e.Request(method, "/").Expect().Status(httptest.StatusOK).Body() if method == "GET" { - tt.Equal("From GET: github.com/kataras/iris/v12/_examples/routing/route-register-rule.getHandler") + tt.Equal("From GET: iris/_examples/routing/route-register-rule.getHandler") } else { - tt.Equal("From " + method + ": github.com/kataras/iris/v12/_examples/routing/route-register-rule.anyHandler") + tt.Equal("From " + method + ": iris/_examples/routing/route-register-rule.anyHandler") } } } diff --git a/go19.go b/aliases.go similarity index 99% rename from go19.go rename to aliases.go index 59587e66..fff77f82 100644 --- a/go19.go +++ b/aliases.go @@ -1,5 +1,3 @@ -// +build go1.9 - package iris import ( diff --git a/httptest/httptest_go19.go b/httptest/aliases.go similarity index 90% rename from httptest/httptest_go19.go rename to httptest/aliases.go index 6be2b47b..05abb22f 100644 --- a/httptest/httptest_go19.go +++ b/httptest/aliases.go @@ -1,5 +1,3 @@ -// +build go1.9 - package httptest import "github.com/gavv/httpexpect" diff --git a/sessions/session.go b/sessions/session.go index f6473770..f4110ec0 100644 --- a/sessions/session.go +++ b/sessions/session.go @@ -1,6 +1,7 @@ package sessions import ( + "math" "reflect" "strconv" "sync" @@ -225,20 +226,22 @@ func newErrEntryNotFound(key string, kind reflect.Kind, value interface{}) *ErrE func (s *Session) GetInt(key string) (int, error) { v := s.Get(key) - if vint, ok := v.(int); ok { - return vint, nil - } + if v != nil { + if vint, ok := v.(int); ok { + return vint, nil + } - if vfloat64, ok := v.(float64); ok { - return int(vfloat64), nil - } + if vfloat64, ok := v.(float64); ok { + return int(vfloat64), nil + } - if vint64, ok := v.(int64); ok { - return int(vint64), nil - } + if vint64, ok := v.(int64); ok { + return int(vint64), nil + } - if vstring, sok := v.(string); sok { - return strconv.Atoi(vstring) + if vstring, sok := v.(string); sok { + return strconv.Atoi(vstring) + } } return -1, newErrEntryNotFound(key, reflect.Int, v) @@ -277,21 +280,22 @@ func (s *Session) Decrement(key string, n int) (newValue int) { // if key doesn't exist then it returns -1 and a non-nil error. func (s *Session) GetInt64(key string) (int64, error) { v := s.Get(key) + if v != nil { + if vint64, ok := v.(int64); ok { + return vint64, nil + } - if vint64, ok := v.(int64); ok { - return vint64, nil - } + if vfloat64, ok := v.(float64); ok { + return int64(vfloat64), nil + } - if vfloat64, ok := v.(float64); ok { - return int64(vfloat64), nil - } + if vint, ok := v.(int); ok { + return int64(vint), nil + } - if vint, ok := v.(int); ok { - return int64(vint), nil - } - - if vstring, sok := v.(string); sok { - return strconv.ParseInt(vstring, 10, 64) + if vstring, sok := v.(string); sok { + return strconv.ParseInt(vstring, 10, 64) + } } return -1, newErrEntryNotFound(key, reflect.Int64, v) @@ -307,6 +311,49 @@ func (s *Session) GetInt64Default(key string, defaultValue int64) int64 { return defaultValue } +// GetUint64 same as `Get` but returns as uint64, +// if key doesn't exist then it returns 0 and a non-nil error. +func (s *Session) GetUint64(key string) (uint64, error) { + v := s.Get(key) + if v != nil { + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 64) + if err != nil { + return 0, err + } + if val > math.MaxUint64 { + break + } + return uint64(val), nil + case uint8: + return uint64(vv), nil + case uint16: + return uint64(vv), nil + case uint32: + return uint64(vv), nil + case uint64: + return vv, nil + case int64: + return uint64(vv), nil + case int: + return uint64(vv), nil + } + } + + return 0, newErrEntryNotFound(key, reflect.Uint64, v) +} + +// GetUint64Default same as `Get` but returns as uint64, +// if key doesn't exist it returns the "defaultValue". +func (s *Session) GetUint64Default(key string, defaultValue uint64) uint64 { + if v, err := s.GetUint64(key); err == nil { + return v + } + + return defaultValue +} + // GetFloat32 same as `Get` but returns its float32 representation, // if key doesn't exist then it returns -1 and a non-nil error. func (s *Session) GetFloat32(key string) (float32, error) { diff --git a/sessions/sessions.go b/sessions/sessions.go index c8e3194c..1b25c8c2 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -7,6 +7,10 @@ import ( "github.com/kataras/iris/v12/context" ) +func init() { + context.SetHandlerName("iris/sessions.*Handler", "iris.session") +} + // A Sessions manager should be responsible to Start a sesion, based // on a Context, which should return // a compatible Session interface, type. If the external session manager diff --git a/websocket/websocket_go19.go b/websocket/aliases.go similarity index 99% rename from websocket/websocket_go19.go rename to websocket/aliases.go index fa611ade..90c2da67 100644 --- a/websocket/websocket_go19.go +++ b/websocket/aliases.go @@ -1,5 +1,3 @@ -// +build go1.9 - package websocket import (