feat: add user status and path permissions
This commit is contained in:
@@ -110,7 +110,7 @@ func account(r *http.Request, s *Server) *auth.Account {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return s.Auth.Guest
|
return s.Auth.Guest
|
||||||
}
|
}
|
||||||
if a, ok := s.Auth.Accounts[username]; ok {
|
if a, ok := s.Auth.Accounts[username]; ok && (a.Enabled || a.PasswordHash == "") {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
return s.Auth.Guest
|
return s.Auth.Guest
|
||||||
@@ -465,7 +465,7 @@ func (s *Server) me(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeJSONError(w, http.StatusUnauthorized, "not authenticated")
|
writeJSONError(w, http.StatusUnauthorized, "not authenticated")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSON(w, map[string]any{"username": a.Username, "home": a.Home, "admin": a.Admin, "mask": permString(a.MaskFor(a.Home)), "managed": a.PasswordHash != ""})
|
writeJSON(w, map[string]any{"username": a.Username, "home": a.Home, "admin": a.Admin, "mask": permString(a.MaskFor(a.Home)), "managed": a.PasswordHash != "", "enabled": a.Enabled})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) password(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) password(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -502,6 +502,21 @@ func (s *Server) password(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeJSON(w, map[string]any{"ok": true})
|
writeJSON(w, map[string]any{"ok": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userRuleJSON struct {
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
Mask string `json:"mask"`
|
||||||
|
Deny bool `json:"deny,omitempty"`
|
||||||
|
ChildrenOnly bool `json:"childrenOnly,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func userRulesJSON(rules []auth.PathRule) []userRuleJSON {
|
||||||
|
out := make([]userRuleJSON, 0, len(rules))
|
||||||
|
for _, r := range rules {
|
||||||
|
out = append(out, userRuleJSON{Dir: r.Dir, Mask: permString(r.Mask), Deny: r.Deny, ChildrenOnly: r.ChildrenOnly})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) users(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) users(w http.ResponseWriter, r *http.Request) {
|
||||||
a := account(r, s)
|
a := account(r, s)
|
||||||
if a == nil || !a.Admin {
|
if a == nil || !a.Admin {
|
||||||
@@ -519,7 +534,7 @@ func (s *Server) users(w http.ResponseWriter, r *http.Request) {
|
|||||||
if u.Username == "" || u == s.Auth.Guest {
|
if u.Username == "" || u == s.Auth.Guest {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out = append(out, map[string]any{"username": u.Username, "home": u.Home, "admin": u.Admin, "managed": u.PasswordHash != ""})
|
out = append(out, map[string]any{"username": u.Username, "home": u.Home, "admin": u.Admin, "managed": u.PasswordHash != "", "enabled": u.Enabled, "rules": userRulesJSON(u.Rules)})
|
||||||
}
|
}
|
||||||
writeJSON(w, map[string]any{"users": out})
|
writeJSON(w, map[string]any{"users": out})
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
@@ -552,6 +567,8 @@ func (s *Server) users(w http.ResponseWriter, r *http.Request) {
|
|||||||
var req struct {
|
var req struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
Enabled *bool `json:"enabled"`
|
||||||
|
Rules []userRuleJSON `json:"rules"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
writeJSONError(w, http.StatusBadRequest, "invalid json")
|
writeJSONError(w, http.StatusBadRequest, "invalid json")
|
||||||
@@ -561,11 +578,22 @@ func (s *Server) users(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeJSONError(w, http.StatusBadRequest, "invalid username")
|
writeJSONError(w, http.StatusBadRequest, "invalid username")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := s.UserStore.UpdatePassword(s.Auth, req.Username, req.Password); err != nil {
|
var rules []auth.PathRule
|
||||||
|
if req.Rules != nil {
|
||||||
|
for _, rr := range req.Rules {
|
||||||
|
dir := strings.Trim(strings.ReplaceAll(rr.Dir, "\\", "/"), "/")
|
||||||
|
if dir == "" || unsafePath(dir) {
|
||||||
|
writeJSONError(w, http.StatusBadRequest, "invalid rule path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rules = append(rules, auth.PathRule{Dir: dir, Mask: auth.ParseMask(rr.Mask), Deny: rr.Deny, ChildrenOnly: rr.ChildrenOnly})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.UserStore.Update(s.Auth, req.Username, req.Password, req.Enabled, rules); err != nil {
|
||||||
writeJSONError(w, http.StatusBadRequest, err.Error())
|
writeJSONError(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.logOp(r, fmt.Sprintf("reset password for '%s'", req.Username))
|
s.logOp(r, fmt.Sprintf("updated user '%s'", req.Username))
|
||||||
writeJSON(w, map[string]any{"ok": true})
|
writeJSON(w, map[string]any{"ok": true})
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
username := r.URL.Query().Get("username")
|
username := r.URL.Query().Get("username")
|
||||||
|
|||||||
@@ -76,6 +76,55 @@ func TestUserManagementAndPrivateHome(t *testing.T) {
|
|||||||
t.Fatalf("private home access: %d %s", rr.Code, rr.Body.String())
|
t.Fatalf("private home access: %d %s", rr.Code, rr.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Admin can disable a managed user, which immediately invalidates token-based access.
|
||||||
|
updateBody, _ := json.Marshal(map[string]any{"username": "alice", "enabled": false})
|
||||||
|
req = httptest.NewRequest(http.MethodPut, "/chfs/users", bytes.NewReader(updateBody))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
rr = httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rr, req)
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Fatalf("disable user: %d %s", rr.Code, rr.Body.String())
|
||||||
|
}
|
||||||
|
req = httptest.NewRequest(http.MethodPost, "/chfs/session", strings.NewReader("user=alice&pwd=alice-pass"))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
rr = httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rr, req)
|
||||||
|
if rr.Code == http.StatusCreated {
|
||||||
|
t.Fatal("disabled user was allowed to login")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-enable and grant read-only access to a shared directory.
|
||||||
|
rules := []map[string]any{
|
||||||
|
{"dir": "users/alice", "mask": "rwd"},
|
||||||
|
{"dir": "public", "mask": "r"},
|
||||||
|
}
|
||||||
|
updateBody, _ = json.Marshal(map[string]any{"username": "alice", "enabled": true, "rules": rules})
|
||||||
|
req = httptest.NewRequest(http.MethodPut, "/chfs/users", bytes.NewReader(updateBody))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
rr = httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rr, req)
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Fatalf("update rules: %d %s", rr.Code, rr.Body.String())
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Join(root, "public"), 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(root, "public", "readme.txt"), []byte("ok"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
token, err = auth.NewToken("alice", 30)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/chfs/files?filepath=public", nil)
|
||||||
|
req.AddCookie(&http.Cookie{Name: "JWT", Value: token})
|
||||||
|
rr = httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rr, req)
|
||||||
|
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "\"currentMask\":\"R\"") {
|
||||||
|
t.Fatalf("shared read rule: %d %s", rr.Code, rr.Body.String())
|
||||||
|
}
|
||||||
var saved []map[string]any
|
var saved []map[string]any
|
||||||
data, err := os.ReadFile(usersFile)
|
data, err := os.ReadFile(usersFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -60,9 +60,14 @@ async function login(){try{await fetch("/chfs/session",{method:"POST",headers:{"
|
|||||||
async function logout(){await fetch("/chfs/session",{method:"DELETE"});location.reload()}
|
async function logout(){await fetch("/chfs/session",{method:"DELETE"});location.reload()}
|
||||||
function show(html){$("dialog").innerHTML=html;$("modal").classList.add("show")}function closeModal(){$("modal").classList.remove("show")}
|
function show(html){$("dialog").innerHTML=html;$("modal").classList.add("show")}function closeModal(){$("modal").classList.remove("show")}
|
||||||
$("modal").addEventListener("click",e=>{if(e.target.id==="modal")closeModal()})
|
$("modal").addEventListener("click",e=>{if(e.target.id==="modal")closeModal()})
|
||||||
async function showUsers(){try{const d=await api("/chfs/users");let html='<h2>用户管理</h2><div class="field"><input id="nu" placeholder="用户名"><input id="np" type="password" placeholder="初始密码"><button onclick="createUser()">创建用户</button></div><div>';for(const u of d.users)html+='<p><b>'+esc(u.username)+'</b> · '+esc(u.home||"")+(u.managed&&!u.admin?' <button class="ghost" onclick="resetUser('+JSON.stringify(u.username)+')">重置密码</button> <button class="danger" onclick="deleteUser('+JSON.stringify(u.username)+')">删除</button>':"")+"</p>";html+='</div><div class="row"><button class="ghost" onclick="closeModal()">关闭</button></div>';show(html)}catch(e){alert(e.message)}}
|
async function showUsers(){try{const d=await api("/chfs/users");let html='<h2>用户管理</h2><div class="field"><input id="nu" placeholder="用户名"><input id="np" type="password" placeholder="初始密码"><button onclick="createUser()">创建用户</button></div><div>';for(const u of d.users)html+='<div style="padding:10px 0;border-bottom:1px solid #edf0f5"><b>'+esc(u.username)+'</b> · '+(u.enabled?"启用":"已禁用")+' · '+esc(u.home||"")+(u.managed&&!u.admin?' <button class="ghost" onclick="editUser('+JSON.stringify(u.username)+')">权限/状态</button> <button class="ghost" onclick="resetUser('+JSON.stringify(u.username)+')">重置密码</button> <button class="danger" onclick="deleteUser('+JSON.stringify(u.username)+')">删除</button>':"")+"</div>";html+='</div><div class="row"><button class="ghost" onclick="closeModal()">关闭</button></div>';show(html)}catch(e){alert(e.message)}}
|
||||||
async function createUser(){try{await api("/chfs/users",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:$("nu").value,password:$("np").value})});showUsers()}catch(e){alert(e.message)}}
|
async function createUser(){try{await api("/chfs/users",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:$("nu").value,password:$("np").value})});showUsers()}catch(e){alert(e.message)}}
|
||||||
async function resetUser(u){const p=prompt("为 "+u+" 设置新密码");if(!p)return;try{await api("/chfs/users",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:u,password:p})});alert("密码已更新")}catch(e){alert(e.message)}}
|
async function resetUser(u){const p=prompt("为 "+u+" 设置新密码");if(!p)return;try{await api("/chfs/users",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:u,password:p})});alert("密码已更新")}catch(e){alert(e.message)}}
|
||||||
|
async function editUser(name){const d=await api("/chfs/users");const u=d.users.find(x=>x.username===name);if(!u)return;let rows=(u.rules||[]).map((r,i)=>'<div class="row" style="justify-content:space-between;border-top:1px solid #edf0f5;padding:8px 0"><span><b>'+esc(r.dir)+'</b> · '+esc(r.mask||"禁止")+'</span><button class="danger" onclick="removeRule('+i+')">移除</button></div>').join("");show('<h2>'+esc(u.username)+'</h2><div class="notice">主目录:'+esc(u.home)+'<br>权限:R 读取 · W 写入 · D 删除</div><label><input id="ue" type="checkbox" '+(u.enabled?"checked":"")+'> 账号启用</label><div class="field"><input id="rp" placeholder="目录,例如 public 或 shared/team"><select id="rm"><option value="r">只读</option><option value="rw">读写</option><option value="rwd">读写删</option><option value="">禁止</option></select><button onclick="addRule()">添加目录权限</button></div><div id="rules">'+rows+'</div><div class="row"><button class="ghost" onclick="showUsers()">取消</button><button onclick="saveUser('+JSON.stringify(u.username)+')">保存</button></div>');window._editUser=u;window._editRules=[...(u.rules||[])]}
|
||||||
|
function addRule(){const dir=$("rp").value.trim().replace(/^\/+|\/+$/g,"");if(!dir)return;window._editRules.push({dir,mask:$("rm").value});renderEditRules()}
|
||||||
|
function removeRule(i){window._editRules.splice(i,1);renderEditRules()}
|
||||||
|
function renderEditRules(){$("rules").innerHTML=window._editRules.map((r,i)=>'<div class="row" style="justify-content:space-between;border-top:1px solid #edf0f5;padding:8px 0"><span><b>'+esc(r.dir)+'</b> · '+esc(r.mask||"禁止")+'</span><button class="danger" onclick="removeRule('+i+')">移除</button></div>').join("")}
|
||||||
|
async function saveUser(u){try{await api("/chfs/users",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:u,enabled:$("ue").checked,rules:window._editRules})});showUsers()}catch(e){alert(e.message)}}
|
||||||
async function deleteUser(u){if(!confirm("删除用户 "+u+"?用户目录不会自动删除。"))return;try{await api("/chfs/users?username="+encodeURIComponent(u),{method:"DELETE"});showUsers()}catch(e){alert(e.message)}}
|
async function deleteUser(u){if(!confirm("删除用户 "+u+"?用户目录不会自动删除。"))return;try{await api("/chfs/users?username="+encodeURIComponent(u),{method:"DELETE"});showUsers()}catch(e){alert(e.message)}}
|
||||||
init();
|
init();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ type Account struct {
|
|||||||
Rules []PathRule
|
Rules []PathRule
|
||||||
Home string
|
Home string
|
||||||
Admin bool
|
Admin bool
|
||||||
|
Enabled bool
|
||||||
}
|
}
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
Accounts map[string]*Account
|
Accounts map[string]*Account
|
||||||
@@ -85,7 +86,7 @@ func ParseRules(values []string) (*Manager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Authenticate(user, pwd string) *Account {
|
func (m *Manager) Authenticate(user, pwd string) *Account {
|
||||||
if a, ok := m.Accounts[user]; ok && (a.Password == pwd || a.PasswordHash == PasswordHash(pwd)) {
|
if a, ok := m.Accounts[user]; ok && (a.Enabled || a.PasswordHash == "") && (a.Password == pwd || a.PasswordHash == PasswordHash(pwd)) {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
if user == "" && pwd == "" {
|
if user == "" && pwd == "" {
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ type userRecord struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"passwordHash"`
|
Password string `json:"passwordHash"`
|
||||||
Home string `json:"home"`
|
Home string `json:"home"`
|
||||||
|
Enabled *bool `json:"enabled,omitempty"`
|
||||||
|
Rules []userRule `json:"rules,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type userRule struct {
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
Mask string `json:"mask"`
|
||||||
|
Deny bool `json:"deny,omitempty"`
|
||||||
|
ChildrenOnly bool `json:"childrenOnly,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var validUsername = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$")
|
var validUsername = regexp.MustCompile("^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$")
|
||||||
@@ -30,6 +39,11 @@ func ValidateUsername(username string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unsafeUserPath(p string) bool {
|
||||||
|
p = strings.ReplaceAll(p, "\\", "/")
|
||||||
|
return p == ".." || strings.HasPrefix(p, "../") || strings.Contains(p, "/../") || strings.HasPrefix(p, "/")
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UserStore) Load(m *Manager, homeRoot string) error {
|
func (u *UserStore) Load(m *Manager, homeRoot string) error {
|
||||||
if u.Path == "" {
|
if u.Path == "" {
|
||||||
return nil
|
return nil
|
||||||
@@ -56,8 +70,22 @@ func (u *UserStore) Load(m *Manager, homeRoot string) error {
|
|||||||
if home == "" {
|
if home == "" {
|
||||||
home = filepath.ToSlash(filepath.Join(homeRoot, rec.Username))
|
home = filepath.ToSlash(filepath.Join(homeRoot, rec.Username))
|
||||||
}
|
}
|
||||||
a := &Account{Username: rec.Username, PasswordHash: rec.Password, Mask: None, Home: home}
|
enabled := true
|
||||||
|
if rec.Enabled != nil {
|
||||||
|
enabled = *rec.Enabled
|
||||||
|
}
|
||||||
|
a := &Account{Username: rec.Username, PasswordHash: rec.Password, Mask: None, Home: home, Enabled: enabled}
|
||||||
|
if len(rec.Rules) == 0 {
|
||||||
a.Rules = append(a.Rules, PathRule{Dir: home, Mask: Read | Write | Delete})
|
a.Rules = append(a.Rules, PathRule{Dir: home, Mask: Read | Write | Delete})
|
||||||
|
} else {
|
||||||
|
for _, rr := range rec.Rules {
|
||||||
|
dir := strings.Trim(strings.ReplaceAll(rr.Dir, "\\", "/"), "/")
|
||||||
|
if dir == "" || unsafeUserPath(dir) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
a.Rules = append(a.Rules, PathRule{Dir: dir, Mask: ParseMask(rr.Mask), Deny: rr.Deny, ChildrenOnly: rr.ChildrenOnly})
|
||||||
|
}
|
||||||
|
}
|
||||||
m.Accounts[rec.Username] = a
|
m.Accounts[rec.Username] = a
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -74,7 +102,11 @@ func (u *UserStore) Save(m *Manager) error {
|
|||||||
if a.PasswordHash == "" || a.Home == "" || a.Admin {
|
if a.PasswordHash == "" || a.Home == "" || a.Admin {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
records = append(records, userRecord{Username: a.Username, Password: a.PasswordHash, Home: a.Home})
|
rec := userRecord{Username: a.Username, Password: a.PasswordHash, Home: a.Home, Enabled: &a.Enabled}
|
||||||
|
for _, rr := range a.Rules {
|
||||||
|
rec.Rules = append(rec.Rules, userRule{Dir: rr.Dir, Mask: maskString(rr.Mask), Deny: rr.Deny, ChildrenOnly: rr.ChildrenOnly})
|
||||||
|
}
|
||||||
|
records = append(records, rec)
|
||||||
}
|
}
|
||||||
data, err := json.MarshalIndent(records, "", " ")
|
data, err := json.MarshalIndent(records, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,12 +135,44 @@ func (u *UserStore) Add(m *Manager, username, password, home string) (*Account,
|
|||||||
return nil, fmt.Errorf("user already exists")
|
return nil, fmt.Errorf("user already exists")
|
||||||
}
|
}
|
||||||
home = strings.Trim(home, "/")
|
home = strings.Trim(home, "/")
|
||||||
a := &Account{Username: username, PasswordHash: PasswordHash(password), Mask: None, Home: home}
|
a := &Account{Username: username, PasswordHash: PasswordHash(password), Mask: None, Home: home, Enabled: true}
|
||||||
a.Rules = append(a.Rules, PathRule{Dir: home, Mask: Read | Write | Delete})
|
a.Rules = append(a.Rules, PathRule{Dir: home, Mask: Read | Write | Delete})
|
||||||
m.Accounts[username] = a
|
m.Accounts[username] = a
|
||||||
return a, u.Save(m)
|
return a, u.Save(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maskString(p PermMask) string {
|
||||||
|
var b strings.Builder
|
||||||
|
if p.HasRead() {
|
||||||
|
b.WriteByte('r')
|
||||||
|
}
|
||||||
|
if p.HasWrite() {
|
||||||
|
b.WriteByte('w')
|
||||||
|
}
|
||||||
|
if p.HasDelete() {
|
||||||
|
b.WriteByte('d')
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserStore) Update(m *Manager, username, password string, enabled *bool, rules []PathRule) error {
|
||||||
|
a, ok := m.Accounts[username]
|
||||||
|
if !ok || a.PasswordHash == "" || a.Admin {
|
||||||
|
return fmt.Errorf("managed user not found")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(password) != "" {
|
||||||
|
a.PasswordHash = PasswordHash(password)
|
||||||
|
a.Password = ""
|
||||||
|
}
|
||||||
|
if enabled != nil {
|
||||||
|
a.Enabled = *enabled
|
||||||
|
}
|
||||||
|
if rules != nil {
|
||||||
|
a.Rules = rules
|
||||||
|
}
|
||||||
|
return u.Save(m)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UserStore) UpdatePassword(m *Manager, username, password string) error {
|
func (u *UserStore) UpdatePassword(m *Manager, username, password string) error {
|
||||||
if strings.TrimSpace(password) == "" {
|
if strings.TrimSpace(password) == "" {
|
||||||
return fmt.Errorf("password required")
|
return fmt.Errorf("password required")
|
||||||
|
|||||||
Reference in New Issue
Block a user