From ef179a5d9168ed9b5422535e4878e218d962edd3 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Tue, 9 Jun 2020 12:22:44 +0800 Subject: [PATCH] Add account API. --- .gitignore | 7 +- controllers/account.go | 162 ++++++++++++++++++++++++++++++ controllers/basic.go | 13 +++ object/check.go | 46 +++++++++ object/member.go | 1 + routers/router.go | 5 + util/json.go | 27 +++++ util/log.go | 88 ++++++++++++++++ web/src/backend/AccountBackend.js | 45 +++++++++ 9 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 controllers/account.go create mode 100644 object/check.go create mode 100644 util/json.go create mode 100644 util/log.go create mode 100644 web/src/backend/AccountBackend.js diff --git a/.gitignore b/.gitignore index 73c572a3..fb2bd34b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,10 @@ .idea/ *.iml + +tmp/ +tmpFiles/ *.tmp -tmp/ \ No newline at end of file +logs/ +lastupdate.tmp +commentsRouter*.go \ No newline at end of file diff --git a/controllers/account.go b/controllers/account.go new file mode 100644 index 00000000..adc94b27 --- /dev/null +++ b/controllers/account.go @@ -0,0 +1,162 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "encoding/json" + + "github.com/casbin/casbin-forum/object" + "github.com/casbin/casbin-forum/util" +) + +type SignupForm struct { + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` +} + +type Response struct { + Status string `json:"status"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + Data2 interface{} `json:"data2"` +} + +// @Title Signup +// @Description sign up a new member +// @Param username formData string true "The username to sign up" +// @Param password formData string true "The password" +// @Success 200 {object} controllers.api_controller.Response The Response object +// @router /signup [post] +func (c *APIController) Signup() { + var resp Response + + if c.GetSessionUser() != "" { + resp = Response{Status: "error", Msg: "errorSignoutBeforeSignup", Data: c.GetSessionUser()} + c.Data["json"] = resp + c.ServeJSON() + return + } + + var form SignupForm + err := json.Unmarshal(c.Ctx.Input.RequestBody, &form) + if err != nil { + panic(err) + } + member, password, email := form.Username, form.Password, form.Email + + msg := object.CheckMemberSignup(member, password) + if msg != "" { + resp = Response{Status: "error", Msg: msg, Data: ""} + } else { + member := &object.Member{ + Id: member, + Password: password, + Email: email, + } + object.AddMember(member) + + //c.SetSessionUser(user) + + util.LogInfo(c.Ctx, "API: [%s] is signed up as new member", member) + resp = Response{Status: "ok", Msg: "success", Data: member} + } + + c.Data["json"] = resp + c.ServeJSON() +} + +// @Title Signin +// @Description sign in as a member +// @Param username formData string true "The username to sign in" +// @Param password formData string true "The password" +// @Success 200 {object} controllers.api_controller.Response The Response object +// @router /signin [post] +func (c *APIController) Signin() { + var resp Response + + if c.GetSessionUser() != "" { + resp = Response{Status: "error", Msg: "errorSignoutBeforeSignin", Data: c.GetSessionUser()} + c.Data["json"] = resp + c.ServeJSON() + return + } + + var form SignupForm + err := json.Unmarshal(c.Ctx.Input.RequestBody, &form) + if err != nil { + panic(err) + } + + var msg string + var member string + var password string + member, password = form.Username, form.Password + msg = object.CheckMemberLogin(member, password) + + if msg != "" { + resp = Response{Status: "error", Msg: msg, Data: ""} + } else { + c.SetSessionUser(member) + + util.LogInfo(c.Ctx, "API: [%s] signed in", member) + resp = Response{Status: "ok", Msg: "success", Data: member} + } + + c.Data["json"] = resp + c.ServeJSON() +} + +// @Title Signout +// @Description sign out the current member +// @Success 200 {object} controllers.api_controller.Response The Response object +// @router /signout [post] +func (c *APIController) Signout() { + var resp Response + + member := c.GetSessionUser() + util.LogInfo(c.Ctx, "API: [%s] signed out", member) + + c.SetSessionUser("") + + resp = Response{Status: "ok", Msg: "success", Data: member} + + c.Data["json"] = resp + c.ServeJSON() +} + +func (c *APIController) GetAccount() { + var resp Response + + if c.GetSessionUser() == "" { + resp = Response{Status: "error", Msg: "errorSigninFirst", Data: c.GetSessionUser()} + c.Data["json"] = resp + c.ServeJSON() + return + } + + var memberObj interface{} + username := c.GetSessionUser() + memberObj = object.GetMember(username) + resp = Response{Status: "ok", Msg: "", Data: util.StructToJson(memberObj)} + + c.Data["json"] = resp + c.ServeJSON() +} + +func (c *APIController) GetSessionId() { + c.Data["json"] = c.StartSession().SessionID() + c.ServeJSON() +} diff --git a/controllers/basic.go b/controllers/basic.go index fb84a83b..ef839100 100644 --- a/controllers/basic.go +++ b/controllers/basic.go @@ -19,3 +19,16 @@ import "github.com/astaxie/beego" type APIController struct { beego.Controller } + +func (c *APIController) GetSessionUser() string { + user := c.GetSession("username") + if user == nil { + return "" + } + + return user.(string) +} + +func (c *APIController) SetSessionUser(user string) { + c.SetSession("username", user) +} diff --git a/object/check.go b/object/check.go new file mode 100644 index 00000000..b8235dd9 --- /dev/null +++ b/object/check.go @@ -0,0 +1,46 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package object + +func HasMember(memberId string) bool { + return GetMember(memberId) != nil +} + +func IsPasswordCorrect(memberId string, password string) bool { + objMember := GetMember(memberId) + return objMember.Password == password +} + +func CheckMemberSignup(member string, password string) string { + if len(member) == 0 || len(password) == 0 { + return "errorUsernameOrPasswordEmpty" + } else if HasMember(member) { + return "errorUsernameExisted" + } else { + return "" + } +} + +func CheckMemberLogin(member string, password string) string { + if !HasMember(member) { + return "errorUsernameNotFound" + } + + if !IsPasswordCorrect(member, password) { + return "errorPasswordWrong" + } + + return "" +} diff --git a/object/member.go b/object/member.go index 103dec0c..973f25ee 100644 --- a/object/member.go +++ b/object/member.go @@ -16,6 +16,7 @@ package object type Member struct { Id string `xorm:"varchar(100) notnull pk" json:"id"` + Password string `xorm:"varchar(100) notnull" json:"-"` No int `json:"no"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"` Phone string `xorm:"varchar(100)" json:"phone"` diff --git a/routers/router.go b/routers/router.go index 1f7762c6..bf566790 100644 --- a/routers/router.go +++ b/routers/router.go @@ -56,4 +56,9 @@ func initAPI() { beego.Router("/api/update-node", &controllers.APIController{}, "POST:UpdateNode") beego.Router("/api/add-node", &controllers.APIController{}, "POST:AddNode") beego.Router("/api/delete-node", &controllers.APIController{}, "POST:DeleteNode") + + beego.Router("/api/signup", &controllers.APIController{}, "POST:Signup") + beego.Router("/api/signin", &controllers.APIController{}, "POST:Signin") + beego.Router("/api/signout", &controllers.APIController{}, "POST:Signout") + beego.Router("/api/get-account", &controllers.APIController{}, "GET:GetAccount") } diff --git a/util/json.go b/util/json.go new file mode 100644 index 00000000..5f3e9c34 --- /dev/null +++ b/util/json.go @@ -0,0 +1,27 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import "encoding/json" + +func StructToJson(v interface{}) string { + //data, err := json.MarshalIndent(v, "", " ") + data, err := json.Marshal(v) + if err != nil { + panic(err) + } + + return string(data) +} diff --git a/util/log.go b/util/log.go new file mode 100644 index 00000000..93e76049 --- /dev/null +++ b/util/log.go @@ -0,0 +1,88 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/logs" +) + +func GetIPInfo(clientIP string) string { + if clientIP == "" { + return "" + } + + ips := strings.Split(clientIP, ",") + res := "" + for i := range ips { + ip := strings.TrimSpace(ips[i]) + desc := "" // GetDescFromIP(ip) + ipstr := fmt.Sprintf("%s: %s", ip, desc) + if i != len(ips) - 1 { + res += ipstr + " -> " + } else { + res += ipstr + } + } + + return res +} + +func getIPFromRequest(req *http.Request) string { + clientIP := req.Header.Get("x-forwarded-for") + if clientIP == "" { + ipPort := strings.Split(req.RemoteAddr, ":") + if len(ipPort) >= 1 && len(ipPort) <= 2 { + clientIP = ipPort[0] + } else if len(ipPort) > 2 { + idx := strings.LastIndex(req.RemoteAddr, ":") + clientIP = req.RemoteAddr[0:idx] + clientIP = strings.TrimLeft(clientIP, "[") + clientIP = strings.TrimRight(clientIP, "]") + } + } + + return GetIPInfo(clientIP) +} + +func LogInfo(ctx *context.Context, f string, v ...interface{}) { + ipString := fmt.Sprintf("(%s) ", getIPFromRequest(ctx.Request)) + logs.Info(ipString + f, v...) +} + +func LogWarning(ctx *context.Context, f string, v ...interface{}) { + ipString := fmt.Sprintf("(%s) ", getIPFromRequest(ctx.Request)) + logs.Warning(ipString + f, v...) +} + +func ReadLog() []string { + f, err := os.Open("logs/casbin-forum.log") + if err != nil { + panic(err) + } + + bytes, err := ioutil.ReadAll(f) + if err != nil { + panic(err) + } + + return strings.Split(string(bytes), "\n") +} diff --git a/web/src/backend/AccountBackend.js b/web/src/backend/AccountBackend.js new file mode 100644 index 00000000..dac8125e --- /dev/null +++ b/web/src/backend/AccountBackend.js @@ -0,0 +1,45 @@ +// Copyright 2020 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as Setting from "../Setting"; + +export function getAccount() { + return fetch(`${Setting.ServerUrl}/api/get-account`, { + method: 'GET', + credentials: 'include' + }).then(res => res.json()); +} + +export function signup(values) { + return fetch(`${Setting.ServerUrl}/api/signup`, { + method: 'POST', + credentials: "include", + body: JSON.stringify(values), + }).then(res => res.json()); +} + +export function signin(values) { + return fetch(`${Setting.ServerUrl}/api/signin`, { + method: 'POST', + credentials: "include", + body: JSON.stringify(values), + }).then(res => res.json()); +} + +export function signout() { + return fetch(`${Setting.ServerUrl}/api/signout`, { + method: 'POST', + credentials: "include", + }).then(res => res.json()); +}