- account: accounts.json 原子读写、常量时间口令校验、增删改查与用户名校验 - auth: HMAC 会话签名 + 管理员 bcrypt 凭据初始化 - randstr: 口令生成;usage: 目录用量统计 - 单测覆盖口令对错、账号禁用、只读、路径逃逸、持久化与 0600 权限
93 lines
2.2 KiB
Go
93 lines
2.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
"github.com/YLing2024/davbox/internal/randstr"
|
|
)
|
|
|
|
type adminFile struct {
|
|
Hash string `json:"hash"`
|
|
}
|
|
|
|
// LoadOrCreateSecret 读取或首次生成 32 字节 cookie 签名密钥。
|
|
func LoadOrCreateSecret(dataDir string) ([]byte, error) {
|
|
path := filepath.Join(dataDir, "secret.key")
|
|
raw, err := os.ReadFile(path)
|
|
if err == nil && len(raw) >= 32 {
|
|
return raw, nil
|
|
}
|
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
return nil, err
|
|
}
|
|
key := make([]byte, 32)
|
|
if _, err := rand.Read(key); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := os.WriteFile(path, key, 0o600); err != nil {
|
|
return nil, err
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// LoadOrCreateAdmin 读取或首次创建管理员口令。首次创建时生成 24 位口令,
|
|
// 写入 bcrypt 哈希,明文只落到 admin-password.txt 并在 stdout 打印一次。
|
|
func LoadOrCreateAdmin(dataDir string) ([]byte, error) {
|
|
path := filepath.Join(dataDir, "admin.json")
|
|
raw, err := os.ReadFile(path)
|
|
if err == nil {
|
|
var af adminFile
|
|
if err := json.Unmarshal(raw, &af); err != nil {
|
|
return nil, fmt.Errorf("解析 admin.json: %w", err)
|
|
}
|
|
if af.Hash == "" {
|
|
return nil, errors.New("admin.json 缺少 hash 字段")
|
|
}
|
|
return []byte(af.Hash), nil
|
|
}
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
return nil, err
|
|
}
|
|
|
|
pass, err := randstr.Generate(24)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
buf, err := json.MarshalIndent(adminFile{Hash: string(hash)}, "", " ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
buf = append(buf, '\n')
|
|
if err := writeFile0600(path, buf); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := writeFile0600(filepath.Join(dataDir, "admin-password.txt"), []byte(pass+"\n")); err != nil {
|
|
return nil, err
|
|
}
|
|
fmt.Printf("管理员初始口令(仅本次打印,请自行保存):%s\n", pass)
|
|
return hash, nil
|
|
}
|
|
|
|
func writeFile0600(path string, data []byte) error {
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
if _, err := f.Write(data); err != nil {
|
|
return err
|
|
}
|
|
return f.Chmod(0o600)
|
|
}
|