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 }