dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

utils.go (7482B)


      1 package utils
      2 
      3 import (
      4 	"context"
      5 	"crypto/sha256"
      6 	"dkforest/pkg/captcha"
      7 	"encoding/base64"
      8 	"encoding/hex"
      9 	"encoding/json"
     10 	"errors"
     11 	"fmt"
     12 	"net"
     13 	"net/http"
     14 	"os"
     15 	"os/signal"
     16 	"strconv"
     17 	"strings"
     18 	"syscall"
     19 	"time"
     20 
     21 	"dkforest/pkg/config"
     22 	"dkforest/pkg/utils"
     23 	"github.com/labstack/echo"
     24 )
     25 
     26 const (
     27 	HBCookieName        = "dkft" // dkf troll
     28 	WaitCookieName      = "wait-token"
     29 	PokerReferralName   = "poker-referral-token"
     30 	AuthCookieName      = "auth-token"
     31 	AprilFoolCookieName = "april_fool"
     32 	ByteRoadCookieName  = "challenge_byte_road_session"
     33 )
     34 
     35 var ForumDisabledErr = errors.New("forum is temporarily disabled")
     36 var AccountTooYoungErr = errors.New("account must be at least 3 days old")
     37 
     38 func CreateCookie(name, value string, maxAge int64) *http.Cookie {
     39 	cookie := &http.Cookie{
     40 		Name:     name,
     41 		Value:    value,
     42 		Domain:   config.Global.CookieDomain.Get(),
     43 		Secure:   config.Global.CookieSecure.Get(),
     44 		Path:     "/",
     45 		HttpOnly: true,
     46 		MaxAge:   int(maxAge),
     47 		SameSite: http.SameSiteLaxMode,
     48 		Expires:  time.Now().Add(time.Duration(maxAge) * time.Second),
     49 	}
     50 	return cookie
     51 }
     52 
     53 // CreateEncCookie return a cookie where the value has been json marshaled, encrypted and base64 encoded
     54 func CreateEncCookie(name string, value any, maxAge int64) *http.Cookie {
     55 	by, err := json.Marshal(value)
     56 	if err != nil {
     57 		return nil
     58 	}
     59 	encryptedVal, err := utils.EncryptAESMaster(by)
     60 	if err != nil {
     61 		return nil
     62 	}
     63 	valB64 := base64.URLEncoding.EncodeToString(encryptedVal)
     64 	return CreateCookie(name, valB64, maxAge)
     65 }
     66 
     67 // EncCookie gets back the value of an encrypted cookie
     68 func EncCookie[T any](c echo.Context, name string) (*http.Cookie, T, error) {
     69 	var zero T
     70 	cc, err := c.Cookie(name)
     71 	if err != nil {
     72 		return nil, zero, err
     73 	}
     74 	val, err := base64.URLEncoding.DecodeString(cc.Value)
     75 	if err != nil {
     76 		return nil, zero, err
     77 	}
     78 	v, err := utils.DecryptAESMaster(val)
     79 	if err != nil {
     80 		return nil, zero, err
     81 	}
     82 	var out T
     83 	if err := json.Unmarshal(v, &out); err != nil {
     84 		return nil, zero, err
     85 	}
     86 	return cc, out, nil
     87 }
     88 
     89 func DeleteCookie(name string) *http.Cookie {
     90 	return CreateCookie(name, "", -1)
     91 }
     92 
     93 func getGistCookieName(gistUUID string) string {
     94 	return fmt.Sprintf("gist_%s_auth", gistUUID)
     95 }
     96 
     97 func getRoomCookieName(roomID int64) string {
     98 	return fmt.Sprintf("room_%d_auth", roomID)
     99 }
    100 
    101 func getRoomKeyCookieName(roomID int64) string {
    102 	return fmt.Sprintf("room_%d_key", roomID)
    103 }
    104 
    105 func GetRoomCookie(c echo.Context, roomID int64) (*http.Cookie, error) {
    106 	return c.Cookie(getRoomCookieName(roomID))
    107 }
    108 
    109 func GetRoomKeyCookie(c echo.Context, roomID int64) (*http.Cookie, error) {
    110 	return c.Cookie(getRoomKeyCookieName(roomID))
    111 }
    112 
    113 func DeleteRoomCookie(c echo.Context, roomID int64) {
    114 	c.SetCookie(DeleteCookie(getRoomCookieName(roomID)))
    115 	c.SetCookie(DeleteCookie(getRoomKeyCookieName(roomID)))
    116 }
    117 
    118 func CreateRoomCookie(c echo.Context, roomID int64, v, key string) {
    119 	c.SetCookie(CreateCookie(getRoomCookieName(roomID), v, utils.OneDaySecs))
    120 	c.SetCookie(CreateCookie(getRoomKeyCookieName(roomID), key, utils.OneDaySecs))
    121 }
    122 
    123 func GetGistCookie(c echo.Context, gistUUID string) (*http.Cookie, error) {
    124 	return c.Cookie(getGistCookieName(gistUUID))
    125 }
    126 
    127 func DeleteGistCookie(c echo.Context, gistUUID string) {
    128 	c.SetCookie(DeleteCookie(getGistCookieName(gistUUID)))
    129 }
    130 
    131 func CreateGistCookie(c echo.Context, gistUUID, v string) {
    132 	c.SetCookie(CreateCookie(getGistCookieName(gistUUID), v, utils.OneDaySecs))
    133 }
    134 
    135 func CreatePokerReferralCookie(c echo.Context, v string) {
    136 	c.SetCookie(CreateCookie(PokerReferralName, v, utils.OneDaySecs))
    137 }
    138 
    139 func GetPokerReferralCookie(c echo.Context) (*http.Cookie, error) {
    140 	return c.Cookie(PokerReferralName)
    141 }
    142 
    143 func GetAprilFoolCookie(c echo.Context) int {
    144 	v, err := c.Cookie(AprilFoolCookieName)
    145 	if err != nil {
    146 		return 0
    147 	}
    148 	vv, err := strconv.Atoi(v.Value)
    149 	if err != nil {
    150 		return 0
    151 	}
    152 	return vv
    153 }
    154 
    155 func CreateAprilFoolCookie(c echo.Context, v int) {
    156 	c.SetCookie(CreateCookie(AprilFoolCookieName, strconv.Itoa(v), utils.OneDaySecs))
    157 }
    158 
    159 // CaptchaVerifyString ensure that all captcha across the website makes HB life miserable.
    160 func CaptchaVerifyString(c echo.Context, id, answer string) error {
    161 	// Can bypass captcha in dev mode
    162 	if config.Development.IsTrue() && answer == "000000" {
    163 		return nil
    164 	}
    165 	if err := captcha.VerifyString(id, answer); err != nil {
    166 		return errors.New("invalid answer")
    167 	}
    168 	// HB has 50% chance of having the captcha fails for no reason
    169 	hbCookie, hbCookieErr := c.Cookie(HBCookieName)
    170 	hasHBCookie := hbCookieErr == nil && hbCookie.Value != ""
    171 	if hasHBCookie && utils.DiceRoll(50) {
    172 		return errors.New("invalid answer")
    173 	}
    174 	return nil
    175 }
    176 
    177 func KillCircuit(c echo.Context) {
    178 	if conn, ok := c.Request().Context().Value("conn").(net.Conn); ok {
    179 		config.ConnMap.Close(conn)
    180 	}
    181 }
    182 
    183 func VerifyPow(username, nonce string, difficulty int) bool {
    184 	h := sha256.Sum256([]byte(username + ":" + nonce))
    185 	hashed := hex.EncodeToString(h[:])
    186 	prefix := strings.Repeat("0", difficulty)
    187 	return strings.HasPrefix(hashed, prefix)
    188 }
    189 
    190 func setStreamingHeaders(c echo.Context) {
    191 	c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
    192 	c.Response().WriteHeader(http.StatusOK)
    193 	c.Response().Header().Set("Transfer-Encoding", "chunked")
    194 	c.Response().Header().Set("Connection", "keep-alive")
    195 }
    196 
    197 func closeSignalChan(c echo.Context) <-chan struct{} {
    198 	ctx, cancel := context.WithCancel(context.Background())
    199 	// Listen to the closing of HTTP connection via CloseNotifier
    200 	notify := c.Request().Context().Done()
    201 	notify1 := make(chan os.Signal, 1)
    202 	signal.Notify(notify1, syscall.SIGINT, syscall.SIGTERM)
    203 	utils.SGo(func() {
    204 		select {
    205 		case <-notify:
    206 		case <-notify1:
    207 		}
    208 		cancel()
    209 	})
    210 	return ctx.Done()
    211 }
    212 
    213 func SetStreaming(c echo.Context) <-chan struct{} {
    214 	setStreamingHeaders(c)
    215 	return closeSignalChan(c)
    216 }
    217 
    218 func GetReferer(c echo.Context) string {
    219 	return c.Request().Referer()
    220 }
    221 
    222 func RedirectReferer(c echo.Context) error {
    223 	return c.Redirect(http.StatusFound, GetReferer(c))
    224 }
    225 
    226 func MetaRefreshNow() string {
    227 	return MetaRefresh(0)
    228 }
    229 
    230 func MetaRefresh(delay int) string {
    231 	return MetaRedirect(delay, "")
    232 }
    233 
    234 func MetaRedirectNow(redirectURL string) string {
    235 	return MetaRedirect(0, redirectURL)
    236 }
    237 
    238 func MetaRedirect(delay int, redirectURL string) string {
    239 	content := fmt.Sprintf(`%d`, delay)
    240 	if redirectURL != "" {
    241 		content += fmt.Sprintf(`; URL='%s'`, redirectURL)
    242 	}
    243 	return fmt.Sprintf(`<meta http-equiv="refresh" content="%s" />`, content)
    244 }
    245 
    246 const CssReset = `html, body, div, span, applet, object, iframe,
    247 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
    248 a, abbr, acronym, address, big, cite, code,
    249 del, dfn, em, img, ins, kbd, q, s, samp,
    250 small, strike, strong, sub, sup, tt, var,
    251 b, u, i, center,
    252 dl, dt, dd, ol, ul, li,
    253 fieldset, form, label, legend,
    254 table, caption, tbody, tfoot, thead, tr, th, td,
    255 article, aside, canvas, details, embed,
    256 figure, figcaption, footer, header, hgroup,
    257 menu, nav, output, ruby, section, summary,
    258 time, mark, audio, video {
    259 	margin: 0;
    260 	padding: 0;
    261 	border: 0;
    262 	font-size: 100%;
    263 	font: inherit;
    264 	vertical-align: baseline;
    265 }
    266 
    267 article, aside, details, figcaption, figure,
    268 footer, header, hgroup, menu, nav, section {
    269 	display: block;
    270 }
    271 body {
    272 	line-height: 1;
    273 }
    274 ol, ul {
    275 	list-style: none;
    276 }
    277 blockquote, q {
    278 	quotes: none;
    279 }
    280 blockquote:before, blockquote:after,
    281 q:before, q:after {
    282 	content: '';
    283 	content: none;
    284 }
    285 table {
    286 	border-collapse: collapse;
    287 	border-spacing: 0;
    288 }`
    289 
    290 const HtmlCssReset = `<style>` + CssReset + `</style>`