feat(auth): complete sso subsystem integration
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package wayne
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderService = "X-Wayne-Service"
|
||||
HeaderTimestamp = "X-Wayne-Timestamp"
|
||||
HeaderNonce = "X-Wayne-Nonce"
|
||||
HeaderSignature = "X-Wayne-Signature"
|
||||
|
||||
SignaturePrefix = "sha256="
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingSecret = errors.New("wayne service api secret key is empty")
|
||||
ErrMissingHeader = errors.New("wayne service header value is empty")
|
||||
)
|
||||
|
||||
type SignedHeaders struct {
|
||||
Service string
|
||||
Timestamp string
|
||||
Nonce string
|
||||
Signature string
|
||||
}
|
||||
|
||||
func BodySHA256Hex(body []byte) string {
|
||||
sum := sha256.Sum256(body)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func SigningPayload(method, uri, timestamp, nonce string, body []byte) string {
|
||||
return strings.Join([]string{
|
||||
strings.ToUpper(strings.TrimSpace(method)),
|
||||
uri,
|
||||
timestamp,
|
||||
nonce,
|
||||
BodySHA256Hex(body),
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func Sign(secret, method, uri, timestamp, nonce string, body []byte) (string, error) {
|
||||
secret = secretValue(secret)
|
||||
if secret == "" {
|
||||
return "", ErrMissingSecret
|
||||
}
|
||||
payload := SigningPayload(method, uri, timestamp, nonce, body)
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
_, _ = mac.Write([]byte(payload))
|
||||
return SignaturePrefix + hex.EncodeToString(mac.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func Verify(secret, signature, method, uri, timestamp, nonce string, body []byte) bool {
|
||||
expected, err := Sign(secret, method, uri, timestamp, nonce, body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return subtle.ConstantTimeCompare([]byte(normalizeSignature(signature)), []byte(expected)) == 1
|
||||
}
|
||||
|
||||
func BuildSignedHeaders(service, secret, method, uri string, body []byte, now time.Time) (SignedHeaders, error) {
|
||||
service = strings.TrimSpace(service)
|
||||
if service == "" {
|
||||
return SignedHeaders{}, fmt.Errorf("%w: service", ErrMissingHeader)
|
||||
}
|
||||
timestamp := strconv.FormatInt(now.Unix(), 10)
|
||||
nonce, err := NewNonce()
|
||||
if err != nil {
|
||||
return SignedHeaders{}, err
|
||||
}
|
||||
signature, err := Sign(secret, method, uri, timestamp, nonce, body)
|
||||
if err != nil {
|
||||
return SignedHeaders{}, err
|
||||
}
|
||||
return SignedHeaders{
|
||||
Service: service,
|
||||
Timestamp: timestamp,
|
||||
Nonce: nonce,
|
||||
Signature: signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h SignedHeaders) Apply(req *http.Request) {
|
||||
req.Header.Set(HeaderService, h.Service)
|
||||
req.Header.Set(HeaderTimestamp, h.Timestamp)
|
||||
req.Header.Set(HeaderNonce, h.Nonce)
|
||||
req.Header.Set(HeaderSignature, h.Signature)
|
||||
}
|
||||
|
||||
func NewNonce() (string, error) {
|
||||
buf := make([]byte, 16)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
func normalizeSignature(signature string) string {
|
||||
signature = strings.TrimSpace(signature)
|
||||
if strings.HasPrefix(signature, SignaturePrefix) {
|
||||
return signature
|
||||
}
|
||||
return SignaturePrefix + signature
|
||||
}
|
||||
|
||||
func secretValue(secret string) string {
|
||||
secret = strings.TrimSpace(secret)
|
||||
if _, value, ok := strings.Cut(secret, ":"); ok {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
return secret
|
||||
}
|
||||
Reference in New Issue
Block a user