diff --git a/controllers/user.go b/controllers/user.go
index c74d1546..77cbeaa2 100644
--- a/controllers/user.go
+++ b/controllers/user.go
@@ -528,3 +528,25 @@ func (c *ApiController) GetUserCount() {
c.Data["json"] = count
c.ServeJSON()
}
+
+// AddUserkeys
+// @Title AddUserkeys
+// @router /add-user-keys [post]
+// @Tag User API
+func (c *ApiController) AddUserkeys() {
+ var user object.User
+ err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
+ if err != nil {
+ c.ResponseError(err.Error())
+ return
+ }
+
+ isAdmin := c.IsAdmin()
+ affected, err := object.AddUserkeys(&user, isAdmin)
+ if err != nil {
+ c.ResponseError(err.Error())
+ return
+ }
+
+ c.ResponseOk(affected)
+}
diff --git a/object/user.go b/object/user.go
index 91adeb71..07357972 100644
--- a/object/user.go
+++ b/object/user.go
@@ -78,6 +78,8 @@ type User struct {
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
Groups []string `xorm:"varchar(1000)" json:"groups"`
+ AccessKey string `xorm:"varchar(100)" json:"accessKey"`
+ SecretKey string `xorm:"varchar(100)" json:"secretKey"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
@@ -422,6 +424,23 @@ func GetUserByUserId(owner string, userId string) (*User, error) {
}
}
+func GetUserByAccessKey(accessKey string) (*User, error) {
+ if accessKey == "" {
+ return nil, nil
+ }
+ user := User{AccessKey: accessKey}
+ existed, err := adapter.Engine.Get(&user)
+ if err != nil {
+ return nil, err
+ }
+
+ if existed {
+ return &user, nil
+ } else {
+ return nil, nil
+ }
+}
+
func GetUser(id string) (*User, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getUser(owner, name)
@@ -526,7 +545,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
"owner", "display_name", "avatar",
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
- "signin_wrong_times", "last_signin_wrong_time", "groups",
+ "signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "secret_key",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
@@ -928,3 +947,14 @@ func (user *User) GetPreferMfa(masked bool) *MfaProps {
return user.MultiFactorAuths[0]
}
}
+
+func AddUserkeys(user *User, isAdmin bool) (bool, error) {
+ if user == nil {
+ return false, nil
+ }
+
+ user.AccessKey = util.GenerateId()
+ user.SecretKey = util.GenerateId()
+
+ return UpdateUser(user.GetId(), user, []string{}, isAdmin)
+}
diff --git a/routers/authz_filter.go b/routers/authz_filter.go
index 2634b2c4..80139f07 100644
--- a/routers/authz_filter.go
+++ b/routers/authz_filter.go
@@ -26,8 +26,10 @@ import (
)
type Object struct {
- Owner string `json:"owner"`
- Name string `json:"name"`
+ Owner string `json:"owner"`
+ Name string `json:"name"`
+ AccessKey string `json:"accessKey"`
+ SecretKey string `json:"secretKey"`
}
func getUsername(ctx *context.Context) (username string) {
@@ -43,6 +45,9 @@ func getUsername(ctx *context.Context) (username string) {
username = getUsernameByClientIdSecret(ctx)
}
+ if username == "" {
+ username = getUsernameByKeys(ctx)
+ }
return
}
@@ -98,6 +103,30 @@ func getObject(ctx *context.Context) (string, string) {
}
}
+func getKeys(ctx *context.Context) (string, string) {
+ method := ctx.Request.Method
+
+ if method == http.MethodGet {
+ accessKey := ctx.Input.Query("accesskey")
+ secretKey := ctx.Input.Query("secretkey")
+ return accessKey, secretKey
+ } else {
+ body := ctx.Input.RequestBody
+
+ if len(body) == 0 {
+ return ctx.Request.Form.Get("accesskey"), ctx.Request.Form.Get("secretkey")
+ }
+
+ var obj Object
+ err := json.Unmarshal(body, &obj)
+ if err != nil {
+ return "", ""
+ }
+
+ return obj.AccessKey, obj.SecretKey
+ }
+}
+
func willLog(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
if subOwner == "anonymous" && subName == "anonymous" && method == "GET" && (urlPath == "/api/get-account" || urlPath == "/api/get-app-login") && objOwner == "" && objName == "" {
return false
diff --git a/routers/base.go b/routers/base.go
index 716ba356..3b2b649a 100644
--- a/routers/base.go
+++ b/routers/base.go
@@ -84,6 +84,18 @@ func getUsernameByClientIdSecret(ctx *context.Context) string {
return fmt.Sprintf("app/%s", application.Name)
}
+func getUsernameByKeys(ctx *context.Context) string {
+ accessKey, secretKey := getKeys(ctx)
+ user, err := object.GetUserByAccessKey(accessKey)
+ if err != nil {
+ panic(err)
+ }
+ if user != nil && secretKey == user.SecretKey {
+ return user.GetId()
+ }
+ return ""
+}
+
func getSessionUser(ctx *context.Context) string {
user := ctx.Input.CruSession.Get("username")
if user == nil {
diff --git a/routers/router.go b/routers/router.go
index 94faae3a..ca661b0c 100644
--- a/routers/router.go
+++ b/routers/router.go
@@ -73,6 +73,7 @@ func initAPI() {
beego.Router("/api/get-user-count", &controllers.ApiController{}, "GET:GetUserCount")
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
+ beego.Router("/api/add-user-keys", &controllers.ApiController{}, "POST:AddUserkeys")
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
diff --git a/web/src/OrganizationListPage.js b/web/src/OrganizationListPage.js
index 9d76914f..2439a4e2 100644
--- a/web/src/OrganizationListPage.js
+++ b/web/src/OrganizationListPage.js
@@ -60,6 +60,7 @@ class OrganizationListPage extends BaseListPage {
{name: "Bio", visible: true, viewRule: "Public", modifyRule: "Self"},
{name: "Tag", visible: true, viewRule: "Public", modifyRule: "Admin"},
{name: "Signup application", visible: true, viewRule: "Public", modifyRule: "Admin"},
+ {name: "API key", label: i18next.t("general:API key")},
{name: "Roles", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Permissions", visible: true, viewRule: "Public", modifyRule: "Immutable"},
{name: "Groups", visible: true, viewRule: "Public", modifyRule: "Immutable"},
diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js
index c1f3af79..2a907f41 100644
--- a/web/src/UserEditPage.js
+++ b/web/src/UserEditPage.js
@@ -91,6 +91,17 @@ class UserEditPage extends React.Component {
});
}
+ addUserKeys() {
+ UserBackend.addUserKeys(this.state.user)
+ .then((res) => {
+ if (res.status === "ok") {
+ this.getUser();
+ } else {
+ Setting.showMessage("error", res.msg);
+ }
+ });
+ }
+
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
@@ -266,6 +277,11 @@ class UserEditPage extends React.Component {
}
}
+ let isKeysGenerated = false;
+ if (this.state.user.accessKey !== "" && this.state.user.accessKey !== "") {
+ isKeysGenerated = true;
+ }
+
if (accountItem.name === "Organization") {
return (
@@ -691,6 +707,37 @@ class UserEditPage extends React.Component {
);
+ } else if (accountItem.name === "API key") {
+ return (
+
+
+ {Setting.getLabel(i18next.t("general:API key"), i18next.t("general:API key - Tooltip"))} :
+
+
+
+
+ {Setting.getLabel(i18next.t("general:Access key"), i18next.t("general:Access key - Tooltip"))} :
+
+
+
+
+
+
+
+ {Setting.getLabel(i18next.t("general:Secret key"), i18next.t("general:Secret key - Tooltip"))} :
+
+
+
+
+
+
+
+
+
+
+
+
+ );
} else if (accountItem.name === "Roles") {
return (
diff --git a/web/src/backend/UserBackend.js b/web/src/backend/UserBackend.js
index eb6180c7..e4743ea3 100644
--- a/web/src/backend/UserBackend.js
+++ b/web/src/backend/UserBackend.js
@@ -45,6 +45,17 @@ export function getUser(owner, name) {
}).then(res => res.json());
}
+export function addUserKeys(user) {
+ return fetch(`${Setting.ServerUrl}/api/add-user-keys`, {
+ method: "POST",
+ credentials: "include",
+ body: JSON.stringify(user),
+ headers: {
+ "Accept-Language": Setting.getAcceptLanguage(),
+ },
+ }).then(res => res.json());
+}
+
export function updateUser(owner, name, user) {
const newUser = Setting.deepCopy(user);
return fetch(`${Setting.ServerUrl}/api/update-user?id=${owner}/${encodeURIComponent(name)}`, {
diff --git a/web/src/table/AccountTable.js b/web/src/table/AccountTable.js
index 9aa9b3de..544efd1c 100644
--- a/web/src/table/AccountTable.js
+++ b/web/src/table/AccountTable.js
@@ -91,6 +91,7 @@ class AccountTable extends React.Component {
{name: "Karma", label: i18next.t("user:Karma")},
{name: "Ranking", label: i18next.t("user:Ranking")},
{name: "Signup application", label: i18next.t("general:Signup application")},
+ {name: "API key", label: i18next.t("general:API key")},
{name: "Roles", label: i18next.t("general:Roles")},
{name: "Permissions", label: i18next.t("general:Permissions")},
{name: "Groups", label: i18next.t("general:Groups")},