You've already forked agentic-coding-workflow
feat(mcp): plaintext token generator + SHA-256 hash helper
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tokenRawLen 是 plaintext 随机字节长度。32 字节 = 256 bit 熵,
|
||||||
|
// base64.RawURL 编码后 43 字符(无 padding);加 "mcpt_" 前缀总 48 字符。
|
||||||
|
//
|
||||||
|
// 与 user.NewSessionToken 的 24 字节比,MCP token 加大到 32 字节是因为:
|
||||||
|
// admin 签发的 token 长期存在(90 天默认),熵预算更宽。
|
||||||
|
const tokenRawLen = 32
|
||||||
|
|
||||||
|
// tokenPrefix 是 plaintext 的肉眼可读前缀。鉴权时不参与 hash 计算 —— hash
|
||||||
|
// 是对完整字符串(含前缀)做 SHA-256;前缀仅是日志/调试时区分用。
|
||||||
|
const tokenPrefix = "mcpt_"
|
||||||
|
|
||||||
|
// NewToken 生成 (plaintext, hash) 对。plaintext 仅在签发时返回一次,
|
||||||
|
// 后续认证只用 hash 反查。
|
||||||
|
func NewToken() (plaintext string, hash []byte, err error) {
|
||||||
|
raw := make([]byte, tokenRawLen)
|
||||||
|
if _, err := rand.Read(raw); err != nil {
|
||||||
|
return "", nil, fmt.Errorf("rand: %w", err)
|
||||||
|
}
|
||||||
|
enc := base64.RawURLEncoding.EncodeToString(raw)
|
||||||
|
plain := tokenPrefix + enc
|
||||||
|
return plain, HashToken(plain), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashToken 对 token plaintext(含前缀)做 SHA-256,返回 32 字节固定长度。
|
||||||
|
// 纯函数:相同输入恒返回相同输出,便于鉴权时按请求中的明文反查 DB。
|
||||||
|
func HashToken(plaintext string) []byte {
|
||||||
|
sum := sha256.Sum256([]byte(plaintext))
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewToken_FormatAndUniqueness(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tok1, hash1, err := NewToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
tok2, hash2, err := NewToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// 前缀正确
|
||||||
|
assert.True(t, strings.HasPrefix(tok1, "mcpt_"))
|
||||||
|
assert.True(t, strings.HasPrefix(tok2, "mcpt_"))
|
||||||
|
|
||||||
|
// 两次生成不重复(碰撞概率 ≈ 2^-128)
|
||||||
|
assert.NotEqual(t, tok1, tok2)
|
||||||
|
assert.NotEqual(t, hash1, hash2)
|
||||||
|
|
||||||
|
// hash 是 32 字节
|
||||||
|
assert.Len(t, hash1, 32)
|
||||||
|
assert.Len(t, hash2, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashToken_Deterministic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
plain := "mcpt_someplaintext"
|
||||||
|
h1 := HashToken(plain)
|
||||||
|
h2 := HashToken(plain)
|
||||||
|
|
||||||
|
assert.Equal(t, h1, h2)
|
||||||
|
assert.Len(t, h1, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewToken_HashMatchesPlaintext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tok, hash, err := NewToken()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected := HashToken(tok)
|
||||||
|
assert.Equal(t, expected, hash)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user