From 256a45bdb6dae1ffbe59e7280f2e1bee5cf07329 Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Sun, 25 Nov 2018 22:57:48 +0100 Subject: [PATCH] add(system): login/logout api, rename PK fields --- api/system/spec.json | 33 ++++++++++++- api/system/spec/auth.json | 4 +- api/system/spec/user.json | 29 ++++++++++++ docs/system/README.md | 28 +++++++++++ system/rest/handlers/user.go | 20 ++++++++ system/rest/request/user.go | 91 ++++++++++++++++++++++++++++++++++++ system/rest/user.go | 8 ++++ system/types/organisation.go | 2 +- system/types/team.go | 2 +- system/types/user.go | 4 +- 10 files changed, 213 insertions(+), 8 deletions(-) diff --git a/api/system/spec.json b/api/system/spec.json index 51065788c..dc56ce502 100644 --- a/api/system/spec.json +++ b/api/system/spec.json @@ -11,14 +11,14 @@ "method": "GET", "title": "Check JWT token", "path": "/check", - "parameters": [] + "parameters": {} }, { "name": "logout", "method": "DELETE", "title": "Delete JWT token (Sign Out)", "path": "/check", - "parameters": [] + "parameters": {} } ] }, @@ -381,6 +381,35 @@ } ], "apis": [ + { + "name": "login", + "method": "POST", + "title": "Login user", + "path": "/login", + "parameters": { + "post": [ + { + "name": "username", + "type": "string", + "required": true, + "title": "Username" + }, + { + "name": "password", + "type": "string", + "required": true, + "title": "Password" + } + ] + } + }, + { + "name": "logout", + "method": "GET", + "title": "Delete JWT token (Sign Out)", + "path": "/logout", + "parameters": {} + }, { "name": "list", "method": "GET", diff --git a/api/system/spec/auth.json b/api/system/spec/auth.json index f3b873b14..ba128bcc9 100644 --- a/api/system/spec/auth.json +++ b/api/system/spec/auth.json @@ -13,14 +13,14 @@ "Method": "GET", "Title": "Check JWT token", "Path": "/check", - "Parameters": null + "Parameters": {} }, { "Name": "logout", "Method": "DELETE", "Title": "Delete JWT token (Sign Out)", "Path": "/check", - "Parameters": null + "Parameters": {} } ] } \ No newline at end of file diff --git a/api/system/spec/user.json b/api/system/spec/user.json index 865f0a8a4..4c0562477 100644 --- a/api/system/spec/user.json +++ b/api/system/spec/user.json @@ -17,6 +17,35 @@ ], "Path": "/users", "APIs": [ + { + "Name": "login", + "Method": "POST", + "Title": "Login user", + "Path": "/login", + "Parameters": { + "post": [ + { + "name": "username", + "required": true, + "title": "Username", + "type": "string" + }, + { + "name": "password", + "required": true, + "title": "Password", + "type": "string" + } + ] + } + }, + { + "Name": "logout", + "Method": "GET", + "Title": "Delete JWT token (Sign Out)", + "Path": "/logout", + "Parameters": {} + }, { "Name": "list", "Method": "GET", diff --git a/docs/system/README.md b/docs/system/README.md index c4ba8a9ef..2277726a5 100644 --- a/docs/system/README.md +++ b/docs/system/README.md @@ -277,6 +277,34 @@ An organisation may have many teams. Teams may have many channels available. Acc # Users +## Login user + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/users/login` | HTTP/S | POST | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| username | string | POST | Username | N/A | YES | +| password | string | POST | Password | N/A | YES | + +## Delete JWT token (Sign Out) + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/users/logout` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + ## Search users (Directory) #### Method diff --git a/system/rest/handlers/user.go b/system/rest/handlers/user.go index c95862a14..d3aa707db 100644 --- a/system/rest/handlers/user.go +++ b/system/rest/handlers/user.go @@ -27,6 +27,8 @@ import ( // Internal API interface type UserAPI interface { + Login(context.Context, *request.UserLogin) (interface{}, error) + Logout(context.Context, *request.UserLogout) (interface{}, error) List(context.Context, *request.UserList) (interface{}, error) Create(context.Context, *request.UserCreate) (interface{}, error) Edit(context.Context, *request.UserEdit) (interface{}, error) @@ -38,6 +40,8 @@ type UserAPI interface { // HTTP API interface type User struct { + Login func(http.ResponseWriter, *http.Request) + Logout func(http.ResponseWriter, *http.Request) List func(http.ResponseWriter, *http.Request) Create func(http.ResponseWriter, *http.Request) Edit func(http.ResponseWriter, *http.Request) @@ -49,6 +53,20 @@ type User struct { func NewUser(uh UserAPI) *User { return &User{ + Login: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewUserLogin() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return uh.Login(r.Context(), params) + }) + }, + Logout: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewUserLogout() + resputil.JSON(w, params.Fill(r), func() (interface{}, error) { + return uh.Logout(r.Context(), params) + }) + }, List: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() params := request.NewUserList() @@ -105,6 +123,8 @@ func (uh *User) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http r.Group(func(r chi.Router) { r.Use(middlewares...) r.Route("/users", func(r chi.Router) { + r.Post("/login", uh.Login) + r.Get("/logout", uh.Logout) r.Get("/", uh.List) r.Post("/", uh.Create) r.Put("/{userID}", uh.Edit) diff --git a/system/rest/request/user.go b/system/rest/request/user.go index 8e58394af..7168d4db2 100644 --- a/system/rest/request/user.go +++ b/system/rest/request/user.go @@ -30,6 +30,97 @@ var _ = chi.URLParam var _ = types.JSONText{} var _ = multipart.FileHeader{} +// User login request parameters +type UserLogin struct { + Username string + Password string +} + +func NewUserLogin() *UserLogin { + return &UserLogin{} +} + +func (u *UserLogin) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(u) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := post["username"]; ok { + + u.Username = val + } + if val, ok := post["password"]; ok { + + u.Password = val + } + + return err +} + +var _ RequestFiller = NewUserLogin() + +// User logout request parameters +type UserLogout struct { +} + +func NewUserLogout() *UserLogout { + return &UserLogout{} +} + +func (u *UserLogout) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(u) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + return err +} + +var _ RequestFiller = NewUserLogout() + // User list request parameters type UserList struct { Query string diff --git a/system/rest/user.go b/system/rest/user.go index c01c9301b..b6a839cf3 100644 --- a/system/rest/user.go +++ b/system/rest/user.go @@ -29,6 +29,14 @@ func (ctrl *User) List(ctx context.Context, r *request.UserList) (interface{}, e return ctrl.user.With(ctx).Find(&types.UserFilter{Query: r.Query}) } +func (ctrl *User) Login(ctx context.Context, r *request.UserLogin) (interface{}, error) { + return nil, errors.New("Not implemented: User.Login") +} + +func (ctrl *User) Logout(ctx context.Context, r *request.UserLogout) (interface{}, error) { + return nil, errors.New("Not implemented: User.Logout") +} + func (ctrl *User) Create(ctx context.Context, r *request.UserCreate) (interface{}, error) { user := &types.User{ Email: r.Email, diff --git a/system/types/organisation.go b/system/types/organisation.go index 7ac7dc32d..6e4d028ba 100644 --- a/system/types/organisation.go +++ b/system/types/organisation.go @@ -7,7 +7,7 @@ import ( type ( // Organisations - Organisations represent a top-level grouping entity. There may be many organisations defined in a single deployment. Organisation struct { - ID uint64 `json:"id" db:"id"` + ID uint64 `json:"organisationID,string" db:"id"` FQN string `json:"fqn" db:"fqn"` Name string `json:"name" db:"name"` CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` diff --git a/system/types/team.go b/system/types/team.go index 1331a9bf5..c47862791 100644 --- a/system/types/team.go +++ b/system/types/team.go @@ -7,7 +7,7 @@ import ( type ( // Teams - An organisation may have many teams. Teams may have many channels available. Access to channels may be shared between teams. Team struct { - ID uint64 `json:"id,string" db:"id"` + ID uint64 `json:"teamID,string" db:"id"` Name string `json:"name" db:"name"` Handle string `json:"handle" db:"handle"` CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` diff --git a/system/types/user.go b/system/types/user.go index e4d6ee34d..8be543dc5 100644 --- a/system/types/user.go +++ b/system/types/user.go @@ -9,7 +9,7 @@ import ( type ( User struct { - ID uint64 `json:"id,string" db:"id"` + ID uint64 `json:"userID,string" db:"id"` Username string `json:"username" db:"username"` Email string `json:"email" db:"email"` Name string `json:"name" db:"name"` @@ -19,7 +19,7 @@ type ( Meta types.JSONText `json:"-" db:"meta"` OrganisationID uint64 `json:"organisationID,string" db:"rel_organisation"` - UserID uint64 `json:"userID,string" db:"rel_user_id"` + RelatedUserID uint64 `json:"relatedUserID,string" db:"rel_user_id"` User *User `json:"user" db:"-"` Password []byte `json:"-" db:"password"`