Files
VeillaxandClaude Sonnet 5.5 2259580adf Add the shared packages for the sharks stack
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>
2026-10-26 02:41:44 -06:00

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
}