- account: accounts.json 原子读写、常量时间口令校验、增删改查与用户名校验 - auth: HMAC 会话签名 + 管理员 bcrypt 凭据初始化 - randstr: 口令生成;usage: 目录用量统计 - 单测覆盖口令对错、账号禁用、只读、路径逃逸、持久化与 0600 权限
67 lines
1.6 KiB
Go
67 lines
1.6 KiB
Go
// Package auth 提供会话 cookie 的签名/校验,以及管理员凭据的初始化。
|
|
package auth
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Session 是 cookie 里承载的最小信息。
|
|
type Session struct {
|
|
Kind string `json:"k"`
|
|
User string `json:"u,omitempty"`
|
|
Exp int64 `json:"e"`
|
|
}
|
|
|
|
// Signer 用 HMAC-SHA256 对会话载荷签名。
|
|
type Signer struct {
|
|
key []byte
|
|
}
|
|
|
|
// NewSigner 用 secret.key 构造签名器。
|
|
func NewSigner(key []byte) *Signer {
|
|
return &Signer{key: key}
|
|
}
|
|
|
|
// Issue 生成 kind/user 对应的 cookie 值,ttl 之后过期。
|
|
func (s *Signer) Issue(kind, user string, ttl time.Duration) string {
|
|
sess := Session{Kind: kind, User: user, Exp: time.Now().Add(ttl).Unix()}
|
|
payload, _ := json.Marshal(sess)
|
|
body := base64.RawURLEncoding.EncodeToString(payload)
|
|
return body + "." + s.sign(body)
|
|
}
|
|
|
|
func (s *Signer) sign(body string) string {
|
|
m := hmac.New(sha256.New, s.key)
|
|
m.Write([]byte(body))
|
|
return base64.RawURLEncoding.EncodeToString(m.Sum(nil))
|
|
}
|
|
|
|
// Verify 校验签名与有效期,返回会话内容。
|
|
func (s *Signer) Verify(token string) (Session, bool) {
|
|
i := strings.IndexByte(token, '.')
|
|
if i < 0 {
|
|
return Session{}, false
|
|
}
|
|
body, sig := token[:i], token[i+1:]
|
|
if !hmac.Equal([]byte(sig), []byte(s.sign(body))) {
|
|
return Session{}, false
|
|
}
|
|
payload, err := base64.RawURLEncoding.DecodeString(body)
|
|
if err != nil {
|
|
return Session{}, false
|
|
}
|
|
var sess Session
|
|
if err := json.Unmarshal(payload, &sess); err != nil {
|
|
return Session{}, false
|
|
}
|
|
if sess.Exp < time.Now().Unix() {
|
|
return Session{}, false
|
|
}
|
|
return sess, true
|
|
}
|