package qryptinvite import ( "crypto/ed25519" "errors" "os" "strconv" "time" ) // Config is the resolved qrypt.chat invite-issuer configuration, read from the // environment (see docs/qrypt-invites.md). It is shared by the SSH plugin and // the ops CLI so they mint identical tokens. type Config struct { IssuerID string // AGENTBBS_QRYPT_ISSUER_ID (default "agentbbs") Key string // AGENTBBS_QRYPT_ISSUER_KEY (base64 seed/priv) TTL time.Duration // AGENTBBS_QRYPT_INVITE_TTL (default 168h) RedeemURL string // AGENTBBS_QRYPT_REDEEM_URL (default https://qrypt.chat/anon?invite=) Quota int // AGENTBBS_QRYPT_INVITE_QUOTA (default 5) } // DefaultIssuerID, DefaultRedeemURL, DefaultTTL and DefaultQuota are the // fallbacks when the corresponding env var is unset. const ( DefaultIssuerID = "agentbbs" DefaultRedeemURL = "https://qrypt.chat/anon?invite=" DefaultTTL = 168 * time.Hour DefaultQuota = 5 ) // ConfigFromEnv reads the AGENTBBS_QRYPT_* environment variables, applying // defaults. The key is not validated here; call PrivateKey to parse it. func ConfigFromEnv() Config { c := Config{ IssuerID: DefaultIssuerID, Key: os.Getenv("AGENTBBS_QRYPT_ISSUER_KEY"), TTL: DefaultTTL, RedeemURL: DefaultRedeemURL, Quota: DefaultQuota, } if v := os.Getenv("AGENTBBS_QRYPT_ISSUER_ID"); v != "" { c.IssuerID = v } if v := os.Getenv("AGENTBBS_QRYPT_REDEEM_URL"); v != "" { c.RedeemURL = v } if v := os.Getenv("AGENTBBS_QRYPT_INVITE_TTL"); v != "" { if d, err := time.ParseDuration(v); err == nil && d > 0 { c.TTL = d } } if v := os.Getenv("AGENTBBS_QRYPT_INVITE_QUOTA"); v != "" { if n, err := strconv.Atoi(v); err == nil && n >= 0 { c.Quota = n } } return c } // ErrNoKey means AGENTBBS_QRYPT_ISSUER_KEY is unset, so no tokens can be minted. var ErrNoKey = errors.New("qryptinvite: AGENTBBS_QRYPT_ISSUER_KEY is not set (run: agentbbs qrypt-issuer-keygen)") // PrivateKey parses the configured issuer key, or returns ErrNoKey if unset. func (c Config) PrivateKey() (ed25519.PrivateKey, error) { if c.Key == "" { return nil, ErrNoKey } return ParsePrivateKey(c.Key) } // RedeemLink returns the full URL a member opens to redeem token. func (c Config) RedeemURLFor(token string) string { return c.RedeemURL + token }