Files
WebDAV/internal/auth/admin.go
T
Yunling Zhang 78673558c8 feat(core): Go 模块与账号、认证、用量基础包
- account: accounts.json 原子读写、常量时间口令校验、增删改查与用户名校验
- auth: HMAC 会话签名 + 管理员 bcrypt 凭据初始化
- randstr: 口令生成;usage: 目录用量统计
- 单测覆盖口令对错、账号禁用、只读、路径逃逸、持久化与 0600 权限
2026-09-28 16:52:33 +08:00

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)
}