feat: add NFT usernames and bot verification (#22)
Implements collectible usernames, official verification workflows, and third-party bot verification after maintainer protocol and migration review. The composite activity/moderation rating remains an admin-only read model; Telegram Stars Rating wire fields stay unset pending a dedicated official-semantics implementation. Reviewed-Head: 2796345775ea0f908fb7734601e5e1dee4b653b9 Original-Head: fa082b892fd5180c9c9bc53c81c21cf5d250a75b Co-authored-by: Egor Egorov <business.egor.sg@gmail.com>
This commit is contained in:
parent
b0fd3976f1
commit
fff8de783a
169 changed files with 55769 additions and 282 deletions
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
|
|
@ -13,10 +14,29 @@ import (
|
|||
|
||||
const sessionCookieName = "telesrv_admin_session"
|
||||
|
||||
// csrfCookieName is the double-submit cookie. It is deliberately NOT HttpOnly:
|
||||
// the panel's own JavaScript has to read it back to echo it in the X-CSRF-Token
|
||||
// header, which is the whole mechanism.
|
||||
const csrfCookieName = "telesrv_admin_csrf"
|
||||
|
||||
// csrfHeaderName is the header the panel echoes the cookie in.
|
||||
const csrfHeaderName = "X-CSRF-Token"
|
||||
|
||||
type sessionClaims struct {
|
||||
Actor string `json:"actor"`
|
||||
Exp int64 `json:"exp"`
|
||||
Nonce string `json:"nonce"`
|
||||
// Permissions is the right set granted to this session, taken from
|
||||
// TELESRV_ADMIN_UI_PERMISSIONS at login. It travels inside the signed cookie
|
||||
// rather than being re-read per request, so a session keeps the rights it was
|
||||
// issued with, and it cannot be edited by the browser: the HMAC covers it.
|
||||
Permissions []string `json:"permissions,omitempty"`
|
||||
// CSRF is the double-submit token bound to this session. Binding it into the
|
||||
// signed claims is what makes the cookie/header pair unforgeable by a sibling
|
||||
// origin that can only *write* cookies (a subdomain, say): such an attacker
|
||||
// can set both the cookie and the header to a value they know, but they cannot
|
||||
// produce a session cookie that agrees with it.
|
||||
CSRF string `json:"csrf,omitempty"`
|
||||
}
|
||||
|
||||
func signSession(key []byte, claims sessionClaims) (string, error) {
|
||||
|
|
@ -56,6 +76,28 @@ func verifySession(key []byte, value string, now time.Time) (sessionClaims, bool
|
|||
return claims, true
|
||||
}
|
||||
|
||||
// newCSRFToken mints a fresh double-submit token.
|
||||
func newCSRFToken() (string, error) {
|
||||
var raw [32]byte
|
||||
if _, err := rand.Read(raw[:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(raw[:]), nil
|
||||
}
|
||||
|
||||
// setCSRFCookie publishes the token to the browser.
|
||||
func setCSRFCookie(w http.ResponseWriter, token string, ttl time.Duration) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: csrfCookieName,
|
||||
Value: token,
|
||||
Path: "/",
|
||||
MaxAge: int(ttl.Seconds()),
|
||||
// Readable by the panel's script on purpose; see csrfCookieName.
|
||||
HttpOnly: false,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
||||
func clearSessionCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: sessionCookieName,
|
||||
|
|
@ -65,4 +107,12 @@ func clearSessionCookie(w http.ResponseWriter) {
|
|||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: csrfCookieName,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
HttpOnly: false,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue