110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
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)
|
|
}
|