first commit
This commit is contained in:
commit
fccfe2a4c8
26 files changed
+1819
No files matched your search
@@ -0,0 +1,109 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"ts3monolith/internal/ts3"
|
||||
)
|
||||
|
||||
type tokenManager struct{ secret []byte }
|
||||
|
||||
type tokenPayload struct {
|
||||
Kind string `json:"k"`
|
||||
IP string `json:"ip"`
|
||||
Exp int64 `json:"exp"`
|
||||
Nonce string `json:"n"`
|
||||
ClID int `json:"clid,omitempty"`
|
||||
DBID int `json:"dbid,omitempty"`
|
||||
Nick string `json:"nick,omitempty"`
|
||||
UID string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
func newTokenManager(secret string) *tokenManager { return &tokenManager{secret: []byte(secret)} }
|
||||
|
||||
func (m *tokenManager) sign(p tokenPayload) (string, error) {
|
||||
raw, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
body := base64.RawURLEncoding.EncodeToString(raw)
|
||||
mac := hmac.New(sha256.New, m.secret)
|
||||
_, _ = mac.Write([]byte(body))
|
||||
sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
|
||||
return body + "." + sig, nil
|
||||
}
|
||||
|
||||
func (m *tokenManager) verify(tok, kind, ip string) (tokenPayload, error) {
|
||||
var p tokenPayload
|
||||
body, sig, ok := splitToken(tok)
|
||||
if !ok {
|
||||
return p, errors.New("invalid token")
|
||||
}
|
||||
mac := hmac.New(sha256.New, m.secret)
|
||||
_, _ = mac.Write([]byte(body))
|
||||
got, err := base64.RawURLEncoding.DecodeString(sig)
|
||||
if err != nil || !hmac.Equal(got, mac.Sum(nil)) {
|
||||
return p, errors.New("invalid token signature")
|
||||
}
|
||||
raw, err := base64.RawURLEncoding.DecodeString(body)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
if err = json.Unmarshal(raw, &p); err != nil {
|
||||
return p, err
|
||||
}
|
||||
if p.Kind != kind || p.IP != ip || time.Now().Unix() > p.Exp {
|
||||
return p, errors.New("expired or mismatched token")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func splitToken(s string) (string, string, bool) {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == '.' {
|
||||
return s[:i], s[i+1:], true
|
||||
}
|
||||
}
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
func randomNonce() string {
|
||||
b := make([]byte, 18)
|
||||
_, _ = rand.Read(b)
|
||||
return base64.RawURLEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func (m *tokenManager) formToken(ip string) string {
|
||||
t, _ := m.sign(tokenPayload{Kind: "form", IP: ip, Exp: time.Now().Add(10 * time.Minute).Unix(), Nonce: randomNonce()})
|
||||
return t
|
||||
}
|
||||
|
||||
func (m *tokenManager) clientToken(ip string, c ts3.Client) string {
|
||||
t, _ := m.sign(tokenPayload{Kind: "client", IP: ip, Exp: time.Now().Add(10 * time.Minute).Unix(), Nonce: randomNonce(), ClID: c.ID, DBID: c.DatabaseID, Nick: c.Nick, UID: c.UID})
|
||||
return t
|
||||
}
|
||||
|
||||
func (m *tokenManager) challengeToken(ip string, c ts3.Client) (token string, code string, err error) {
|
||||
p := tokenPayload{Kind: "challenge", IP: ip, Exp: time.Now().Add(5 * time.Minute).Unix(), Nonce: randomNonce(), ClID: c.ID, DBID: c.DatabaseID, Nick: c.Nick, UID: c.UID}
|
||||
token, err = m.sign(p)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return token, m.challengeCode(p), nil
|
||||
}
|
||||
|
||||
func (m *tokenManager) challengeCode(p tokenPayload) string {
|
||||
mac := hmac.New(sha256.New, m.secret)
|
||||
_, _ = mac.Write([]byte("verify|" + p.Nonce + "|" + strconv.Itoa(p.ClID) + "|" + p.IP))
|
||||
sum := mac.Sum(nil)
|
||||
n := (uint32(sum[0])<<24 | uint32(sum[1])<<16 | uint32(sum[2])<<8 | uint32(sum[3])) % 1000000
|
||||
return fmt.Sprintf("%06d", n)
|
||||
}
|
||||
Reference in new issue
Block a user