diff --git a/controllers/user.go b/controllers/user.go index 4c000764..52aac297 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -16,6 +16,8 @@ package controllers import ( "encoding/json" + "fmt" + "strings" "github.com/casdoor/casdoor/object" ) @@ -103,3 +105,71 @@ func (c *ApiController) DeleteUser() { c.Data["json"] = wrapActionResponse(object.DeleteUser(&user)) c.ServeJSON() } + +// @Title SetPassword +// @Description set password +// @Param userOwner formData string true "The owner of the user" +// @Param userName formData string true "The name of the user" +// @Param oldPassword formData string true "The old password of the user" +// @Param newPassword formData string true "The new password of the user" +// @Success 200 {object} controllers.Response The Response object +// @router /set-password [post] +func (c *ApiController) SetPassword() { + userOwner := c.Ctx.Request.Form.Get("userOwner") + userName := c.Ctx.Request.Form.Get("userName") + oldPassword := c.Ctx.Request.Form.Get("oldPassword") + newPassword := c.Ctx.Request.Form.Get("newPassword") + + requestUserId := c.GetSessionUser() + if requestUserId == "" { + c.ResponseError("Please login first.") + return + } + requestUser := object.GetUser(requestUserId) + if requestUser == nil { + c.ResponseError("Session outdated. Please login again.") + return + } + + userId := fmt.Sprintf("%s/%s", userOwner, userName) + targetUser := object.GetUser(userId) + if targetUser == nil { + c.ResponseError("Invalid user id.") + return + } + + hasPermission := false + + if requestUser.IsGlobalAdmin { + hasPermission = true + } else if requestUserId == userId { + hasPermission = true + } else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin { + hasPermission = true + } + + if !hasPermission { + c.ResponseError("You don't have the permission to do this.") + return + } + + if oldPassword != targetUser.Password { + c.ResponseError("Old password wrong.") + return + } + + if strings.Index(newPassword, " ") >= 0 { + c.ResponseError("New password contains blank space.") + return + } + + if newPassword == "" { + c.ResponseError("Invalid new password") + return + } + + targetUser.Password = newPassword + object.SetUserField(targetUser, "password", targetUser.Password) + c.Data["json"] = Response{Status: "ok"} + c.ServeJSON() +} diff --git a/controllers/util.go b/controllers/util.go index 97a218e9..18b4a9c3 100644 --- a/controllers/util.go +++ b/controllers/util.go @@ -52,3 +52,8 @@ func InitHttpClient() { //defer resp.Body.Close() //println("Response status: %s", resp.Status) } + +func (c *ApiController) ResponseError(error string) { + c.Data["json"] = Response{Status: "error", Msg: error} + c.ServeJSON() +} diff --git a/routers/router.go b/routers/router.go index a489d3cb..875f6af2 100644 --- a/routers/router.go +++ b/routers/router.go @@ -59,6 +59,7 @@ func initAPI() { beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser") beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser") beego.Router("/api/upload-avatar", &controllers.ApiController{}, "POST:UploadAvatar") + beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword") beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders") beego.Router("/api/get-default-providers", &controllers.ApiController{}, "GET:GetDefaultProviders") diff --git a/web/src/PasswordModal.js b/web/src/PasswordModal.js new file mode 100644 index 00000000..3ef2d249 --- /dev/null +++ b/web/src/PasswordModal.js @@ -0,0 +1,93 @@ +// Copyright 2021 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 {Button, Col, Modal, Row, Input,} from "antd"; +import i18next from "i18next"; +import React from "react"; +import * as UserBackend from "./backend/UserBackend"; +import * as Setting from "./Setting"; + +export const PasswordModal = (props) => { + const [visible, setVisible] = React.useState(false); + const [confirmLoading, setConfirmLoading] = React.useState(false); + const {user} = props; + + const showModal = () => { + setVisible(true); + }; + + const handleCancel = () => { + setVisible(false); + }; + + const handleOk = () => { + let oldPassword = document.getElementById("old-password")?.value; + let newPassword = document.getElementById("new-password").value; + let rePassword = document.getElementById("re-new-password").value; + if (oldPassword === null || oldPassword === undefined) oldPassword = ""; + if (newPassword === "" || rePassword === "") { + Setting.showMessage("error", i18next.t("user:Empty input!")); + return; + } + if (newPassword !== rePassword) { + Setting.showMessage("error", i18next.t("user:Two passwords you typed do not match.")); + return; + } + setConfirmLoading(true); + UserBackend.setPassword(user.owner, user.name, oldPassword, newPassword).then((res) => { + setConfirmLoading(false); + if (res.status === "ok") { + Setting.showMessage("success", i18next.t("user:Password Set")); + setVisible(false); + } + else Setting.showMessage("error", i18next.t(`user:${res.msg}`)); + }) + } + + let hasOldPassword = user.password !== ""; + + return ( + + + + + { hasOldPassword ? ( + + + + ) : null} + + + + + + + + + + ) +} + +export default PasswordModal; \ No newline at end of file diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index 812320ce..2f7b1e3d 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -24,6 +24,7 @@ import * as AuthBackend from "./auth/AuthBackend"; import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ProviderBackend from "./backend/ProviderBackend"; import * as Provider from "./auth/Provider"; +import PasswordModal from "./PasswordModal"; const { Option } = Select; @@ -266,11 +267,7 @@ class UserEditPage extends React.Component { {i18next.t("general:Password")}: - - - + diff --git a/web/src/backend/UserBackend.js b/web/src/backend/UserBackend.js index 837be764..ac11b5e0 100644 --- a/web/src/backend/UserBackend.js +++ b/web/src/backend/UserBackend.js @@ -79,3 +79,16 @@ export function uploadAvatar(avatar) { }); }); } + +export function setPassword(userOwner, userName, oldPassword, newPassword) { + let formData = new FormData(); + formData.append("userOwner", userOwner); + formData.append("userName", userName); + formData.append("oldPassword", oldPassword); + formData.append("newPassword", newPassword); + return fetch(`${Setting.ServerUrl}/api/set-password`, { + method: "POST", + credentials: "include", + body: formData + }).then(res => res.json()); +} diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 4b6ac082..0ffffcb2 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -98,7 +98,24 @@ "Unlink": "Unlink", "Is admin": "Is admin", "Is global admin": "Is global admin", - "Is forbidden": "Is forbidden" + "Is forbidden": "Is forbidden", + "Empty input!": "Empty input!", + "Two passwords you typed do not match.": "Two passwords you typed do not match.", + "Password Set": "Password Set", + "Set Password": "Set Password", + "Old Password": "Old Password", + "New Password": "New Password", + "Re-enter New": "Re-enter New", + "Please login first.": "Please login first.", + "Session outdated. Please login again.": "Session outdated. Please login again.", + "Invalid user id.": "Invalid user id.", + "You don't have the permission to do this.": "You don't have the permission to do this.", + "Old password wrong.": "Old password wrong.", + "New password contains blank space.": "New password contains blank space.", + "Invalid new password": "Invalid new password", + "Password": "Password", + "Cancel": "Cancel", + "input password": "input password" }, "application": { diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index bf310acf..3e11c445 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -100,7 +100,24 @@ "Unlink": "解绑", "Is admin": "是管理员", "Is global admin": "是全局管理员", - "Is forbidden": "被禁用" + "Is forbidden": "被禁用", + "Empty input!": "输入为空!", + "Two passwords you typed do not match.": "两次输入的密码不匹配。", + "Password Set": "密码已设置", + "Set Password": "设置密码", + "Old Password": "旧密码", + "New Password": "新密码", + "Re-enter New": "重复新密码", + "Please login first.": "请先登录。", + "Session outdated. Please login again.": "会话已过期,请重新登陆。", + "Invalid user id.": "用户 ID 错误", + "You don't have the permission to do this.": "你没有权限这么做。", + "Old password wrong.": "旧密码错误!", + "New password contains blank space.": "新密码包含空格。", + "Invalid new password": "非法的新密码。", + "Password": "密码", + "Cancel": "取消", + "input password": "输入密码" }, "application": {