support api key

This commit is contained in:
zhenorzz 2023-07-14 10:14:48 +08:00
parent 93cd46514d
commit cb5f267e76
9 changed files with 175 additions and 114 deletions

View File

@ -6,7 +6,7 @@ package api
import (
"github.com/zhenorzz/goploy/cmd/server/api/middleware"
model2 "github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/server"
"github.com/zhenorzz/goploy/internal/server/response"
"net/http"
@ -31,11 +31,11 @@ func (Agent) GetServerID(gp *server.Goploy) server.Response {
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
s, err := model2.Server{
s, err := model.Server{
Name: reqData.Name,
IP: reqData.IP,
}.GetData()
@ -56,18 +56,18 @@ func (Agent) GetCronList(gp *server.Goploy) server.Response {
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
crons, err := model2.Cron{ServerID: reqData.ServerID}.GetList()
crons, err := model.Cron{ServerID: reqData.ServerID}.GetList()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
List model2.Crons `json:"list"`
List model.Crons `json:"list"`
}{List: crons},
}
}
@ -81,18 +81,18 @@ func (Agent) GetCronLogs(gp *server.Goploy) server.Response {
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
crons, err := model2.CronLog{ServerID: reqData.ServerID, CronID: reqData.CronID}.GetList(reqData.Page, reqData.Rows)
crons, err := model.CronLog{ServerID: reqData.ServerID, CronID: reqData.CronID}.GetList(reqData.Page, reqData.Rows)
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
List model2.CronLogs `json:"list"`
List model.CronLogs `json:"list"`
}{List: crons},
}
}
@ -107,11 +107,11 @@ func (Agent) CronReport(gp *server.Goploy) server.Response {
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
err := model2.CronLog{
err := model.CronLog{
ServerID: reqData.ServerId,
CronID: reqData.CronId,
ExecCode: reqData.ExecCode,
@ -135,11 +135,11 @@ func (Agent) Report(gp *server.Goploy) server.Response {
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
err := model2.ServerAgentLog{
err := model.ServerAgentLog{
ServerID: reqData.ServerId,
Type: reqData.Type,
Item: reqData.Item,

View File

@ -32,7 +32,6 @@ func (u User) Handler() []server.Route {
server.NewRoute("/user/info", http.MethodGet, u.Info),
server.NewRoute("/user/changePassword", http.MethodPut, u.ChangePassword),
server.NewRoute("/user/getList", http.MethodGet, u.GetList).Permissions(config.ShowMemberPage),
server.NewRoute("/user/getOption", http.MethodGet, u.GetOption),
server.NewRoute("/user/add", http.MethodPost, u.Add).Permissions(config.AddMember).LogFunc(middleware.AddOPLog),
server.NewRoute("/user/edit", http.MethodPut, u.Edit).Permissions(config.EditMember).LogFunc(middleware.AddOPLog),
server.NewRoute("/user/remove", http.MethodDelete, u.Remove).Permissions(config.DeleteMember).LogFunc(middleware.AddOPLog),
@ -41,13 +40,21 @@ func (u User) Handler() []server.Route {
}
}
// Login user
// @Summary Login
// @Tags User
// @Produce json
// @Param request body api.Login.ReqData true "body params"
// @Success 0 {array} api.Login.RespData
// @Failure 2 {string} string
// @Router /user/login [post]
func (User) Login(gp *server.Goploy) server.Response {
type ReqData struct {
Account string `json:"account" validate:"min=1,max=25"`
Password string `json:"password" validate:"password"`
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
@ -128,14 +135,25 @@ func (User) Login(gp *server.Goploy) server.Response {
HttpOnly: true,
}
http.SetCookie(gp.ResponseWriter, &cookie)
type RespData struct {
Token string `json:"token"`
NamespaceList model.Namespaces `json:"namespaceList"`
}
return response.JSON{
Data: struct {
Token string `json:"token"`
NamespaceList model.Namespaces `json:"namespaceList"`
}{Token: token, NamespaceList: namespaceList},
Data: RespData{Token: token, NamespaceList: namespaceList},
}
}
// ExtLogin user
// @Summary External login
// @Tags User
// @Produce json
// @Param request body api.ExtLogin.ReqData true "body params"
// @Success 0 {array} api.ExtLogin.RespData
// @Failure 2 {string} string
// @Router /user/extLogin [post]
func (User) ExtLogin(gp *server.Goploy) server.Response {
type ReqData struct {
Account string `json:"account" validate:"min=1,max=25"`
@ -143,7 +161,7 @@ func (User) ExtLogin(gp *server.Goploy) server.Response {
Token string `json:"token" validate:"len=32"`
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.IllegalParam, Message: err.Error()}
}
@ -191,14 +209,23 @@ func (User) ExtLogin(gp *server.Goploy) server.Response {
}
http.SetCookie(gp.ResponseWriter, &cookie)
type RespData struct {
Token string `json:"token"`
NamespaceList model.Namespaces `json:"namespaceList"`
}
return response.JSON{
Data: struct {
Token string `json:"token"`
NamespaceList model.Namespaces `json:"namespaceList"`
}{Token: token, NamespaceList: namespaceList},
Data: RespData{Token: token, NamespaceList: namespaceList},
}
}
// Info shows user information
// @Summary Show logged-in user information
// @Tags User
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Success 0 {array} api.Info.RespData
// @Failure 2 {string} string
// @Router /user/info [get]
func (User) Info(gp *server.Goploy) server.Response {
type RespData struct {
UserInfo struct {
@ -229,28 +256,37 @@ func (User) Info(gp *server.Goploy) server.Response {
return response.JSON{Data: data}
}
// GetList lists all users
// @Summary List all users
// @Tags User
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Success 0 {array} api.GetList.RespData
// @Failure 2 {string} string
// @Router /user/getList [get]
func (User) GetList(*server.Goploy) server.Response {
users, err := model.User{}.GetList()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{
Data: struct {
Users model.Users `json:"list"`
}{Users: users},
}
}
func (User) GetOption(*server.Goploy) server.Response {
users, err := model.User{}.GetAll()
if err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
return response.JSON{Data: struct {
type RespData struct {
Users model.Users `json:"list"`
}{Users: users}}
}
return response.JSON{
Data: RespData{Users: users},
}
}
// Add adds a user
// @Summary Add a user
// @Tags User
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Param request query api.Add.ReqData true "query params"
// @Success 0 {array} api.Add.RespData
// @Failure 2 {string} string
// @Router /user/add [post]
func (User) Add(gp *server.Goploy) server.Response {
type ReqData struct {
Account string `json:"account" validate:"min=1,max=25"`
@ -261,7 +297,7 @@ func (User) Add(gp *server.Goploy) server.Response {
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
@ -292,13 +328,23 @@ func (User) Add(gp *server.Goploy) server.Response {
}
}
type RespData struct {
ID int64 `json:"id"`
}
return response.JSON{
Data: struct {
ID int64 `json:"id"`
}{ID: id},
Data: RespData{ID: id},
}
}
// Edit edits the user
// @Summary Edit the user
// @Tags User
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Param request query api.Edit.ReqData true "query params"
// @Success 0 {string} string
// @Failure 2 {string} string
// @Router /user/edit [put]
func (User) Edit(gp *server.Goploy) server.Response {
type ReqData struct {
ID int64 `json:"id" validate:"gt=0"`
@ -308,7 +354,7 @@ func (User) Edit(gp *server.Goploy) server.Response {
SuperManager int64 `json:"superManager" validate:"min=0,max=1"`
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
userInfo, err := model.User{ID: reqData.ID}.GetData()
@ -348,12 +394,21 @@ func (User) Edit(gp *server.Goploy) server.Response {
return response.JSON{}
}
// Remove removes the user
// @Summary Remove the user
// @Tags User
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Param request query api.Remove.ReqData true "query params"
// @Success 0 {string} string
// @Failure 2 {string} string
// @Router /user/remove [delete]
func (User) Remove(gp *server.Goploy) server.Response {
type ReqData struct {
ID int64 `json:"id" validate:"gt=0"`
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
if reqData.ID == 1 {
@ -365,13 +420,22 @@ func (User) Remove(gp *server.Goploy) server.Response {
return response.JSON{}
}
// ChangePassword change the user password
// @Summary Change the user password
// @Tags User
// @Produce json
// @Security ApiKeyHeader || ApiKeyQueryParam || NamespaceHeader || NamespaceQueryParam
// @Param request query api.ChangePassword.ReqData true "query params"
// @Success 0 {string} string
// @Failure 2 {string} string
// @Router /user/changePassword [put]
func (User) ChangePassword(gp *server.Goploy) server.Response {
type ReqData struct {
OldPassword string `json:"oldPwd" validate:"password"`
NewPassword string `json:"newPwd" validate:"password"`
}
var reqData ReqData
if err := decodeJson(gp.Body, &reqData); err != nil {
if err := gp.Decode(&reqData); err != nil {
return response.JSON{Code: response.Error, Message: err.Error()}
}
userData, err := model.User{ID: gp.UserInfo.ID}.GetData()

View File

@ -59,7 +59,7 @@ func init() {
// @contact.url https://github.com/zhenorzz/goploy
// @contact.email zhenorzz@gmail.com
// @tag.name API
// @tag.description the response type is response.JSON, code = 0 for success, otherwise error, see the message for more details.
// @tag.description the response type is response.JSON, code=0(success), code=1(deny), code=2(error), code=10000(AccountDisabled), code=10001(IllegalRequest), code=10002(NamespaceInvalid), code=10003(IllegalParam), code=10086(LoginExpired)
// @license.name GPLv3
// @license.url https://www.gnu.org/licenses/gpl-3.0.html
// @host 127.0.0.1:3001
@ -70,7 +70,7 @@ func init() {
// @name X-API-KEY
// @securityDefinitions.apikey ApiKeyQueryParam
// @in query
// @name api_key
// @name X-API-KEY
// @securityDefinitions.apikey NamespaceHeader
// @in query
// @name G-N-ID

View File

@ -5,3 +5,4 @@
package config
const NamespaceHeaderName = "G-N-ID"
const ApiKeyHeaderName = "X-API-KEY"

2
database/1.15.1.sql Normal file
View File

@ -0,0 +1,2 @@
ALTER TABLE `user`
ADD COLUMN `api_key` varchar(255) NOT NULL DEFAULT '' AFTER `super_manager`;

View File

@ -254,6 +254,7 @@ CREATE TABLE IF NOT EXISTS `user` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`last_login_time` datetime DEFAULT NULL,
`super_manager` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT 'the mark of super admin',
`api_key` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;

View File

@ -22,7 +22,6 @@ const (
GeneralUser = 0
)
// User -
type User struct {
ID int64 `json:"id"`
Account string `json:"account"`
@ -34,12 +33,11 @@ type User struct {
InsertTime string `json:"insertTime"`
UpdateTime string `json:"updateTime"`
LastLoginTime string `json:"lastLoginTime"`
ApiKey string `json:"apiKey"`
}
// Users -
type Users []User
// GetData -
func (u User) GetData() (User, error) {
var user User
err := sq.
@ -55,7 +53,6 @@ func (u User) GetData() (User, error) {
return user, nil
}
// GetDataByAccount -
func (u User) GetDataByAccount() (User, error) {
var user User
err := sq.
@ -71,7 +68,6 @@ func (u User) GetDataByAccount() (User, error) {
return user, nil
}
// GetDataByContact -
func (u User) GetDataByContact() (User, error) {
var user User
err := sq.
@ -87,7 +83,21 @@ func (u User) GetDataByContact() (User, error) {
return user, nil
}
// GetList -
func (u User) GetDataByApiKey() (User, error) {
var user User
err := sq.
Select("id, account, password, name, contact, super_manager, state, insert_time, update_time").
From(userTable).
Where(sq.Eq{"api_key": u.ApiKey}).
RunWith(DB).
QueryRow().
Scan(&user.ID, &user.Account, &user.Password, &user.Name, &user.Contact, &user.SuperManager, &user.State, &user.InsertTime, &user.UpdateTime)
if err != nil {
return user, err
}
return user, nil
}
func (u User) GetList() (Users, error) {
rows, err := sq.
Select("id, account, name, contact, super_manager, insert_time, update_time").
@ -127,29 +137,6 @@ func (u User) GetTotal() (int64, error) {
return total, nil
}
func (u User) GetAll() (Users, error) {
rows, err := sq.
Select("id, account, name, contact, super_manager").
From(userTable).
Where(sq.Eq{"state": Enable}).
OrderBy("id DESC").
RunWith(DB).
Query()
if err != nil {
return nil, err
}
users := Users{}
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Account, &user.Name, &user.Contact, &user.SuperManager); err != nil {
return users, err
}
users = append(users, user)
}
return users, nil
}
func (u User) AddRow() (int64, error) {
if u.Password == "" {
u.Password = u.Account + "!@#"
@ -175,7 +162,6 @@ func (u User) AddRow() (int64, error) {
return id, err
}
// EditRow -
func (u User) EditRow() error {
builder := sq.
Update(userTable).
@ -198,7 +184,6 @@ func (u User) EditRow() error {
return err
}
// RemoveRow -
func (u User) RemoveRow() error {
_, err := sq.
Update(userTable).
@ -211,7 +196,6 @@ func (u User) RemoveRow() error {
return err
}
// UpdatePassword -
func (u User) UpdatePassword() error {
password := []byte(u.Password)
// Hashing the password with the default cost of 10
@ -243,7 +227,7 @@ func (u User) UpdateLastLoginTime() error {
return err
}
// Validate if user exists
// Validate user password
func (u User) Validate(inputPassword string) error {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(inputPassword))
if err != nil {
@ -252,7 +236,6 @@ func (u User) Validate(inputPassword string) error {
return nil
}
// CreateToken -
func (u User) CreateToken() (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": u.ID,

View File

@ -10,7 +10,7 @@ import (
"github.com/golang-jwt/jwt"
log "github.com/sirupsen/logrus"
"github.com/zhenorzz/goploy/config"
model2 "github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/model"
"github.com/zhenorzz/goploy/internal/server/response"
"github.com/zhenorzz/goploy/web"
"io"
@ -90,29 +90,6 @@ func (rt *Router) doRequest(w http.ResponseWriter, r *http.Request) (*Goploy, Re
}
if !route.white {
unParseToken := ""
// check token
goployTokenCookie, err := r.Cookie(config.Toml.Cookie.Name)
if err != nil {
unParseToken = r.URL.Query().Get("api_key")
//unParseToken = r.URL.Query().Get(config.Toml.Cookie.Name)
} else {
unParseToken = goployTokenCookie.Value
}
if unParseToken == "" {
return gp, response.JSON{Code: response.IllegalRequest, Message: "Illegal request"}
}
claims := jwt.MapClaims{}
token, err := jwt.ParseWithClaims(unParseToken, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(config.Toml.JWT.Key), nil
})
if err != nil || !token.Valid {
return gp, response.JSON{Code: response.LoginExpired, Message: "Login expired"}
}
namespaceIDRaw := r.Header.Get(config.NamespaceHeaderName)
if namespaceIDRaw == "" {
namespaceIDRaw = r.URL.Query().Get(config.NamespaceHeaderName)
@ -123,25 +100,58 @@ func (rt *Router) doRequest(w http.ResponseWriter, r *http.Request) (*Goploy, Re
return gp, response.JSON{Code: response.Deny, Message: "Invalid namespace"}
}
gp.UserInfo, err = model2.User{ID: int64(claims["id"].(float64))}.GetData()
if err != nil {
return gp, response.JSON{Code: response.Deny, Message: "Get user information error"}
apiKey := r.Header.Get(config.ApiKeyHeaderName)
if apiKey == "" {
apiKey = r.URL.Query().Get(config.ApiKeyHeaderName)
}
unParseToken := ""
// check token
goployTokenCookie, err := r.Cookie(config.Toml.Cookie.Name)
if err != nil {
unParseToken = r.URL.Query().Get(config.Toml.Cookie.Name)
} else {
unParseToken = goployTokenCookie.Value
}
if unParseToken != "" {
claims := jwt.MapClaims{}
token, err := jwt.ParseWithClaims(unParseToken, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(config.Toml.JWT.Key), nil
})
if err != nil || !token.Valid {
return gp, response.JSON{Code: response.LoginExpired, Message: "Login expired"}
}
gp.UserInfo, err = model.User{ID: int64(claims["id"].(float64))}.GetData()
if err != nil {
return gp, response.JSON{Code: response.Deny, Message: "Get user information error"}
}
} else if apiKey != "" {
gp.UserInfo, err = model.User{ApiKey: apiKey}.GetDataByApiKey()
if err != nil {
return gp, response.JSON{Code: response.Deny, Message: "Get user information using api key error"}
}
} else {
return gp, response.JSON{Code: response.IllegalRequest, Message: "Illegal request"}
}
if gp.UserInfo.State != 1 {
return gp, response.JSON{Code: response.AccountDisabled, Message: "No available user"}
}
if gp.UserInfo.SuperManager == model2.SuperManager {
permissionIDs, err := model2.Permission{}.GetIDs()
if gp.UserInfo.SuperManager == model.SuperManager {
permissionIDs, err := model.Permission{}.GetIDs()
if err != nil {
return gp, response.JSON{Code: response.Deny, Message: err.Error()}
}
gp.Namespace.ID = namespaceID
gp.Namespace.PermissionIDs = permissionIDs
} else {
namespace, err := model2.NamespaceUser{
namespace, err := model.NamespaceUser{
NamespaceID: namespaceID,
UserID: int64(claims["id"].(float64)),
UserID: gp.UserInfo.ID,
}.GetDataByUserNamespace()
if err != nil {
if err == sql.ErrNoRows {
@ -158,7 +168,7 @@ func (rt *Router) doRequest(w http.ResponseWriter, r *http.Request) (*Goploy, Re
return gp, response.JSON{Code: response.Deny, Message: err.Error()}
}
goployTokenStr, err := model2.User{ID: int64(claims["id"].(float64)), Name: claims["name"].(string)}.CreateToken()
goployTokenStr, err := gp.UserInfo.CreateToken()
if err == nil {
// update jwt time
cookie := http.Cookie{Name: config.Toml.Cookie.Name, Value: goployTokenStr, Path: "/", MaxAge: config.Toml.Cookie.Expire, HttpOnly: true}
@ -170,7 +180,7 @@ func (rt *Router) doRequest(w http.ResponseWriter, r *http.Request) (*Goploy, Re
gp.ResponseWriter = w
gp.URLQuery = r.URL.Query()
// save the body request data because ioutil.ReadAll will clear the requestBody
// save the body request data because io.ReadAll will clear the requestBody
if r.ContentLength > 0 && hasContentType(r, "application/json") {
gp.Body, _ = io.ReadAll(r.Body)
}

View File

@ -101,7 +101,7 @@ export class UserList extends Request {
}
export class UserOption extends Request {
readonly url = '/user/getOption'
readonly url = '/user/getList'
readonly method = 'get'
public declare datagram: {
list: UserData[]