dkforest

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

signup.go (9267B)


      1 package handlers
      2 
      3 import (
      4 	"bytes"
      5 	"dkforest/pkg/cache"
      6 	"dkforest/pkg/captcha"
      7 	"dkforest/pkg/config"
      8 	"dkforest/pkg/database"
      9 	dutils "dkforest/pkg/database/utils"
     10 	"dkforest/pkg/utils"
     11 	hutils "dkforest/pkg/web/handlers/utils"
     12 	"encoding/json"
     13 	"fmt"
     14 	"github.com/labstack/echo"
     15 	"net/http"
     16 	"strings"
     17 	"time"
     18 )
     19 
     20 var signupCache = cache.New[SignupInfo](5*time.Minute, 5*time.Minute)
     21 
     22 type SignupInfo struct {
     23 	ScreenWidth     string
     24 	ScreenHeight    string
     25 	HelvaticaLoaded bool
     26 
     27 	hasSolvedCaptcha bool
     28 	UpdatedAt        string
     29 }
     30 
     31 // SignupHandler ...
     32 func SignupHandler(c echo.Context) error {
     33 	if config.ProtectHome.IsTrue() {
     34 		return c.NoContent(http.StatusNotFound)
     35 	}
     36 	return tmpSignupHandler(c)
     37 }
     38 
     39 func SignupAttackHandler(c echo.Context) error {
     40 	key := c.Param("signupToken")
     41 	loginLink, found := tempLoginCache.Get("login_link")
     42 	if !found {
     43 		return c.NoContent(http.StatusNotFound)
     44 	}
     45 	if err := captcha.VerifyStringDangerous(tempLoginStore, loginLink.ID, key); err != nil {
     46 		return c.NoContent(http.StatusNotFound)
     47 	}
     48 
     49 	return tmpSignupHandler(c)
     50 }
     51 
     52 // SignupInvitationHandler ...
     53 func SignupInvitationHandler(c echo.Context) error {
     54 	db := c.Get("database").(*database.DkfDB)
     55 	invitationToken := c.Param("invitationToken")
     56 	invitationTokenQuery := c.QueryParam("invitationToken")
     57 	if invitationTokenQuery != "" {
     58 		invitationToken = invitationTokenQuery
     59 	}
     60 	if _, err := db.GetUnusedInvitationByToken(invitationToken); err != nil {
     61 		return c.Redirect(http.StatusFound, "/")
     62 	}
     63 	return waitPageWrapper(c, signupHandler, hutils.WaitCookieName)
     64 }
     65 
     66 func tmpSignupHandler(c echo.Context) error {
     67 	if config.SignupFakeEnabled.IsFalse() && config.SignupEnabled.IsFalse() {
     68 		return c.Render(http.StatusOK, "standalone.signup-invite", nil)
     69 	}
     70 	return waitPageWrapper(c, signupHandler, hutils.WaitCookieName)
     71 }
     72 
     73 // The random wait time 0-15 seconds make sure the load is evenly distributed while under DDoS.
     74 // Not all requests to the signup endpoint will get the captcha at the same time,
     75 // so you cannot just refresh the page until you get a captcha that is easier to crack.
     76 func signupHandler(c echo.Context) error {
     77 	db := c.Get("database").(*database.DkfDB)
     78 	start := c.Get("start").(int64)
     79 	signupToken := c.Get("signupToken").(string)
     80 	var data signupData
     81 	config.SignupPageLoad.Inc()
     82 
     83 	data.Redirect = c.QueryParam("redirect")
     84 	data.PowEnabled = config.PowEnabled.Load()
     85 	data.CaptchaSec = 120
     86 	data.Frames = generateCssFrames(data.CaptchaSec, nil, true)
     87 
     88 	hbCookie, hbCookieErr := c.Cookie(hutils.HBCookieName)
     89 	hasHBCookie := hbCookieErr == nil && hbCookie.Value != ""
     90 
     91 	pokerReferralCookie, _ := hutils.GetPokerReferralCookie(c)
     92 
     93 	signupInfo, _ := signupCache.Get(signupToken)
     94 
     95 	data.HasSolvedCaptcha = signupInfo.hasSolvedCaptcha
     96 	if !signupInfo.hasSolvedCaptcha {
     97 		data.CaptchaID, data.CaptchaImg = captcha.New()
     98 	}
     99 
    100 	if c.Request().Method == http.MethodGet {
    101 		return c.Render(http.StatusOK, "standalone.signup", data)
    102 	}
    103 
    104 	// POST
    105 	data.Username = strings.TrimSpace(c.Request().PostFormValue("username"))
    106 	data.Password = c.Request().PostFormValue("password")
    107 	data.RePassword = c.Request().PostFormValue("repassword")
    108 	data.Pow = c.Request().PostFormValue("pow")
    109 	captchaID := c.Request().PostFormValue("captcha_id")
    110 	captchaInput := c.Request().PostFormValue("captcha")
    111 	captchaInputImg := c.Request().PostFormValue("captcha_img")
    112 	if !signupInfo.hasSolvedCaptcha {
    113 		if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil {
    114 			data.ErrCaptcha = err.Error()
    115 			config.SignupFailed.Inc()
    116 			return c.Render(http.StatusOK, "standalone.signup", data)
    117 		}
    118 	}
    119 	data.Captcha = captchaInput
    120 	data.CaptchaImg = captchaInputImg
    121 
    122 	signupInfo.hasSolvedCaptcha = true
    123 	data.HasSolvedCaptcha = signupInfo.hasSolvedCaptcha
    124 	signupCache.SetD(signupToken, signupInfo)
    125 
    126 	// verify POW
    127 	if config.PowEnabled.IsTrue() {
    128 		if !hutils.VerifyPow(data.Username, data.Pow, config.PowDifficulty) {
    129 			data.ErrPow = "invalid proof of work"
    130 			return c.Render(http.StatusOK, "standalone.signup", data)
    131 		}
    132 	}
    133 
    134 	config.SignupSucceed.Inc()
    135 
    136 	// If SignupFakeEnabled is enabled, we always say the account was created, but we do not create it.
    137 	if config.SignupFakeEnabled.IsTrue() {
    138 		c.SetCookie(hutils.DeleteCookie(hutils.WaitCookieName))
    139 		return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"})
    140 	}
    141 
    142 	// Fuck with kicked users. Prevent them from registering again.
    143 	//authCookie, err := c.Cookie("auth-token")
    144 	//if err == nil && authCookie.Value != "" {
    145 	//	return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"})
    146 	//}
    147 
    148 	signupInfoEnc, _ := json.Marshal(signupInfo)
    149 
    150 	registrationDuration := time.Now().UnixMilli() - start
    151 	newUser, errs := db.CreateUser(data.Username, data.Password, data.RePassword, registrationDuration, string(signupInfoEnc))
    152 	if errs.HasError() {
    153 		data.Errors = errs
    154 		return c.Render(http.StatusOK, "standalone.signup", data)
    155 	}
    156 
    157 	// Fuck with hellbanned users. New account also hellbanned
    158 	if hasHBCookie {
    159 		newUser.IsHellbanned = true
    160 		newUser.DoSave(db)
    161 	}
    162 
    163 	if pokerReferralCookie != nil {
    164 		if referredByUser, err := db.GetUserByPokerReferralToken(pokerReferralCookie.Value); err == nil {
    165 			newUser.PokerReferredBy = &referredByUser.ID
    166 			newUser.DoSave(db)
    167 			c.SetCookie(hutils.DeleteCookie(hutils.PokerReferralName))
    168 		}
    169 	}
    170 
    171 	invitationToken := c.Param("invitationToken")
    172 	if invitationToken != "" {
    173 		if invitation, err := db.GetUnusedInvitationByToken(invitationToken); err == nil {
    174 			invitation.InviteeUserID = newUser.ID
    175 			invitation.DoSave(db)
    176 		}
    177 	}
    178 
    179 	// If more than 10 users were created in the past minute, auto disable signup for the website
    180 	if db.GetRecentUsersCount() > 10 {
    181 		settings := db.GetSettings()
    182 		settings.SignupEnabled = false
    183 		settings.DoSave(db)
    184 		config.SignupEnabled.SetFalse()
    185 		userNull := dutils.GetZeroUser(db)
    186 		db.NewAudit(userNull, fmt.Sprintf("auto turn off signup"))
    187 
    188 		// Display message in chat
    189 		txt := fmt.Sprintf("auto turn off registrations")
    190 		utils.LogErr(db.CreateSysMsg(txt, txt, "", config.GeneralRoomID, userNull.ID))
    191 	}
    192 
    193 	c.SetCookie(hutils.DeleteCookie(hutils.WaitCookieName))
    194 	return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"})
    195 }
    196 
    197 func SignalCss1(c echo.Context) error {
    198 	authUser := c.Get("authUser").(*database.User)
    199 	db := c.Get("database").(*database.DkfDB)
    200 	data := c.Param("data")
    201 	data = strings.TrimRight(data, ".png")
    202 	data = strings.TrimRight(data, ".ttf")
    203 	signalP := c.Param("signal")
    204 	var info SignupInfo
    205 	_ = json.Unmarshal([]byte(authUser.SignupMetadata), &info)
    206 	switch signalP {
    207 	case "sw":
    208 		info.ScreenWidth = data
    209 	case "sh":
    210 		info.ScreenHeight = data
    211 	case "Helvatica":
    212 		info.HelvaticaLoaded = true
    213 	}
    214 	info.UpdatedAt = time.Now().Format(time.RFC3339)
    215 	signupInfoEnc, _ := json.Marshal(info)
    216 	authUser.SetSignupMetadata(db, string(signupInfoEnc))
    217 	return c.NoContent(http.StatusOK)
    218 }
    219 
    220 func SignalCss(c echo.Context) error {
    221 	data := c.Param("data")
    222 	data = strings.TrimRight(data, ".png")
    223 	data = strings.TrimRight(data, ".ttf")
    224 	token := c.Param("signupToken")
    225 	signalP := c.Param("signal")
    226 	var info SignupInfo
    227 	if val, found := signupCache.Get(token); found {
    228 		info = val
    229 	} else {
    230 		info = SignupInfo{}
    231 	}
    232 	switch signalP {
    233 	case "sw":
    234 		info.ScreenWidth = data
    235 	case "sh":
    236 		info.ScreenHeight = data
    237 	case "Helvatica":
    238 		info.HelvaticaLoaded = true
    239 	}
    240 	info.UpdatedAt = time.Now().Format(time.RFC3339)
    241 	signupCache.SetD(token, info)
    242 	return c.NoContent(http.StatusOK)
    243 }
    244 
    245 func MetaCss(c echo.Context) error {
    246 	return c.Blob(http.StatusOK, "text/css; charset=utf-8", metaCss("signal"))
    247 }
    248 
    249 func SignupCss(c echo.Context) error {
    250 	return c.Blob(http.StatusOK, "text/css; charset=utf-8", metaCss(c.Param("signupToken")))
    251 }
    252 
    253 func metaCss(token string) []byte {
    254 	lb := "" // \n
    255 	var buf bytes.Buffer
    256 	sizes := []float64{0, 320, 350, 390, 430, 470, 520, 570, 620, 690, 750, 830, 910, 1000, 1100, 1220, 1340, 1470, 1620, 1780, 1960, 2150, 2370, 2600}
    257 	for i := 1; i < len(sizes); i++ {
    258 		prev := sizes[i-1]
    259 		size := sizes[i]
    260 		_, _ = fmt.Fprintf(&buf, "@media(min-device-width:%.0fpx) and (max-device-width:%.5fpx){.div_1{background:url('/public/img/%s/sw/%.0fx%.0f.png')}}%s", prev, size-0.00001, token, prev, size, lb)
    261 	}
    262 	_, _ = fmt.Fprintf(&buf, "@media(min-device-width:%.0fpx){.div_1{background:url('/public/img/%s/sw/%.0fx.png')}}%s", 2600.0, token, 2600.0, lb)
    263 	for i := 1; i < len(sizes); i++ {
    264 		prev := sizes[i-1]
    265 		size := sizes[i]
    266 		_, _ = fmt.Fprintf(&buf, "@media(min-device-height:%.0fpx) and (max-device-height:%.5fpx){.div_2{background:url('/public/img/%s/sh/%.0fx%.0f.png')}}%s", prev, size-0.00001, token, prev, size, lb)
    267 	}
    268 	_, _ = fmt.Fprintf(&buf, "@media(min-device-height:%.0fpx){.div_2{background:url('/public/img/%s/sh/%.0fx.png')}}%s", 2600.0, token, 2600.0, lb)
    269 	fonts := []string{"Helvatica"}
    270 	for idx, font := range fonts {
    271 		_, _ = fmt.Fprintf(&buf, `@font-face{font-family:'%s';src:local('%s'),url('/public/img/%s/%s/%s.ttf')format('truetype');}%s`, font, font, token, font, font, lb)
    272 		_, _ = fmt.Fprintf(&buf, `.div_f%d{font-family:'%s';position:absolute;top:-100px}%s`, idx, font, lb)
    273 	}
    274 	return buf.Bytes()
    275 }