model, tunnel, wg, authtoken, netguard, docker and dotenv, split out of the original single repository so Hammerhead, Blacktip and Bamboo can live in their own repos and import them. README extended; LICENSE unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
59 lines
1.6 KiB
Go
59 lines
1.6 KiB
Go
// Package authtoken signs the short tokens hammerhead hands to visitors and blacktip verifies.
|
|
package authtoken
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
KindCallback = "cb" // minted by hammerhead after a successful login, valid for minutes
|
|
KindSession = "sess" // minted by blacktip for the visitor's cookie
|
|
)
|
|
|
|
type Token struct {
|
|
Mapping string `json:"m"`
|
|
Sub string `json:"sub"` // who: an email, "password", "pin", "share:name"
|
|
Kind string `json:"k"`
|
|
Exp int64 `json:"exp"`
|
|
// Epoch is the resource's login generation when this was issued. Hammerhead
|
|
// bumps it to sign every visitor out (a member removed, a password changed);
|
|
// tokens from an older epoch are then refused without any per-token state.
|
|
Epoch int64 `json:"ep"`
|
|
}
|
|
|
|
var enc = base64.RawURLEncoding
|
|
|
|
func mac(key, payload string) string {
|
|
m := hmac.New(sha256.New, []byte(key))
|
|
m.Write([]byte(payload))
|
|
return enc.EncodeToString(m.Sum(nil))
|
|
}
|
|
|
|
func Sign(key string, t Token) string {
|
|
b, _ := json.Marshal(t)
|
|
p := enc.EncodeToString(b)
|
|
return p + "." + mac(key, p)
|
|
}
|
|
|
|
// Verify checks the signature, kind, mapping, epoch and expiry.
|
|
func Verify(key, tok, kind, mapping string, epoch int64) (Token, bool) {
|
|
var t Token
|
|
p, sig, found := strings.Cut(tok, ".")
|
|
if !found || !hmac.Equal([]byte(sig), []byte(mac(key, p))) {
|
|
return t, false
|
|
}
|
|
b, err := enc.DecodeString(p)
|
|
if err != nil || json.Unmarshal(b, &t) != nil {
|
|
return t, false
|
|
}
|
|
if t.Kind != kind || t.Mapping != mapping || t.Epoch != epoch || time.Now().Unix() > t.Exp {
|
|
return t, false
|
|
}
|
|
return t, true
|
|
}
|