mirror of
https://github.com/Alexander-D-Karpov/webring.git
synced 2026-03-16 22:07:41 +03:00
Introduce full user system and approval workflow
——————————————————————————————————————————
Login & sessions
Telegram one‑click login (/login → /auth/telegram) with HMAC verification
New users and sessions tables; telegram_id now optional, TTL‑based cleanup job
Secure session_id cookie (configurable TTL and Secure/SameSite flags)
User dashboard (/user)
Lists the member’s sites and their uptime status
Forms to submit new site or update requests; validation and slug/url sanitisation
View pending requests with change diff
Request storage
update_requests table captures create/update ops as JSONB “changed_fields”
Admin review
/admin/requests interface to approve / reject queued requests
Approval auto‑creates sites (with ordered display_order) or patches existing ones, then refreshes favicon
Super‑admin panel
/admin/setup lists all users, toggle is_admin and forcibly logs them out
Notifications
On every new request, all admins with a Telegram ID receive a Markdown summary via bot API
Public UI tweaks
Header shows login/logout, role‑aware links and call‑to‑action cards
/submit page creates a queued request
Config & env
Added TELEGRAM_BOT_TOKEN, TELEGRAM_BOT_USERNAME, SESSION_TTL_HOURS, SESSION_SECURE_COOKIE
.env.template updated accordingly
Migrations 004–010
Users, sessions, foreign key on sites, display_order, update_requests, telegram_id nullability
BREAKING CHANGE
Environment must supply Telegram bot credentials
Database must be migrated; existing “dashboard” auth remains but admin routes are now session‑protected where applicable
72 lines
1.6 KiB
Go
72 lines
1.6 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/url"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type TelegramUser struct {
|
|
ID int64 `json:"id"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name,omitempty"`
|
|
Username string `json:"username,omitempty"`
|
|
PhotoURL string `json:"photo_url,omitempty"`
|
|
AuthDate int64 `json:"auth_date"`
|
|
Hash string `json:"hash"`
|
|
}
|
|
|
|
func VerifyTelegramAuth(values url.Values, botToken string) (*TelegramUser, error) {
|
|
hash := values.Get("hash")
|
|
if hash == "" {
|
|
return nil, fmt.Errorf("missing hash parameter")
|
|
}
|
|
|
|
var dataStrings []string
|
|
for key, value := range values {
|
|
if key != "hash" && len(value) > 0 {
|
|
dataStrings = append(dataStrings, fmt.Sprintf("%s=%s", key, value[0]))
|
|
}
|
|
}
|
|
sort.Strings(dataStrings)
|
|
dataString := strings.Join(dataStrings, "\n")
|
|
|
|
// Create secret key
|
|
secretKey := sha256.Sum256([]byte(botToken))
|
|
|
|
// Create HMAC
|
|
h := hmac.New(sha256.New, secretKey[:])
|
|
h.Write([]byte(dataString))
|
|
expectedHash := hex.EncodeToString(h.Sum(nil))
|
|
|
|
if hash != expectedHash {
|
|
return nil, fmt.Errorf("invalid hash")
|
|
}
|
|
|
|
// Parse user data
|
|
id, err := strconv.ParseInt(values.Get("id"), 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid id")
|
|
}
|
|
|
|
authDate, err := strconv.ParseInt(values.Get("auth_date"), 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid auth_date")
|
|
}
|
|
|
|
return &TelegramUser{
|
|
ID: id,
|
|
FirstName: values.Get("first_name"),
|
|
LastName: values.Get("last_name"),
|
|
Username: values.Get("username"),
|
|
PhotoURL: values.Get("photo_url"),
|
|
AuthDate: authDate,
|
|
Hash: hash,
|
|
}, nil
|
|
}
|