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>`