dkforest

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

commit a380ebd50901d5978555f53a95eb9c557500bf83
parent 2b458a6ebd52958e9b88045c2c739517f44bbd68
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Mon, 12 Jun 2023 20:35:47 -0700

move code

Diffstat:
Mpkg/web/handlers/handlers.go | 295-------------------------------------------------------------------------------
Mpkg/web/handlers/login.go | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpkg/web/handlers/signup.go | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 301 insertions(+), 295 deletions(-)

diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -9,7 +9,6 @@ import ( v1 "dkforest/pkg/web/handlers/api/v1" "dkforest/pkg/web/handlers/streamModals" "encoding/base64" - "encoding/json" "fmt" _ "golang.org/x/image/bmp" _ "golang.org/x/image/webp" @@ -39,7 +38,6 @@ import ( "github.com/alecthomas/chroma/formatters/html" "github.com/alecthomas/chroma/lexers" "github.com/alecthomas/chroma/styles" - "github.com/asaskevich/govalidator" humanize "github.com/dustin/go-humanize" "github.com/labstack/echo" "github.com/pquerna/otp" @@ -79,124 +77,6 @@ func HomeHandler(c echo.Context) error { return loginHandler(c) } -func protectHomeHandler(c echo.Context) error { - if c.Request().Method == http.MethodPost { - return c.NoContent(http.StatusNotFound) - } - captchaQuery := c.QueryParam("captcha") - loginQuery := c.QueryParam("login") - signupQuery := c.QueryParam("signup") - if captchaQuery != "" { - if len(captchaQuery) > 6 || len(loginQuery) > 1 || len(signupQuery) > 1 || - !govalidator.IsASCII(captchaQuery) || !govalidator.IsASCII(loginQuery) || !govalidator.IsASCII(signupQuery) { - time.Sleep(utils.RandSec(3, 7)) - return c.NoContent(http.StatusOK) - } - redirectTo := "/login/" + captchaQuery - if signupQuery == "1" { - redirectTo = "/signup/" + captchaQuery - } - time.Sleep(utils.RandSec(1, 2)) - return c.Redirect(http.StatusFound, redirectTo) - } - loginLink, found := tempLoginCache.Get("login_link") - if !found { - loginLink.ID, loginLink.Img = captcha.NewWithParams(captcha.Params{Store: tempLoginStore}) - loginLink.ValidUntil = time.Now().Add(3 * time.Minute) - tempLoginCache.SetD("login_link", loginLink) - } - - waitTime := int64(time.Until(loginLink.ValidUntil).Seconds()) - - // Generate css frames - frames := generateCssFrames(waitTime, func(i int64) string { - return utils.ShortDur(time.Duration(i) * time.Second) - }, true) - - time.Sleep(utils.RandSec(1, 2)) - bufTmp := make([]byte, 0, 1024*4) - buf := bytes.NewBuffer(bufTmp) - buf.Write([]byte(`<!DOCTYPE html><html lang="en"><head> - <link href="/public/img/favicon.ico" rel="icon" type="image/x-icon" /> - <meta charset="UTF-8" /> - <meta name="author" content="n0tr1v"> - <meta name="language" content="English"> - <meta name="revisit-after" content="1 days"> - <meta http-equiv="expires" content="0"> - <meta http-equiv="pragma" content="no-cache"> - <title>DarkForest</title> - <style> - body, html { height: 100%; width:100%; display:table; background-color: #222; color: white; line-height: 25px; - font-family: Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; } - body { display:table-cell; vertical-align:middle; } - #parent { display: table; width: 100%; } - #form_login { display: table; margin: auto; } - .captcha-img { transition: transform .2s; } - .captcha-img:hover { transform: scale(2.5); } - #timer_countdown:before { - content: "`)) - buf.Write([]byte(utils.ShortDur(time.Duration(waitTime) * time.Second))) - buf.Write([]byte(`"; - animation: `)) - buf.Write([]byte(utils.FormatInt64(waitTime))) - buf.Write([]byte(`s 1s forwards timer_countdown_frames; - } - @keyframes timer_countdown_frames {`)) - for _, frame := range frames { - buf.Write([]byte(frame)) - } - buf.Write([]byte(` - } - </style> -</head> -<body class="bg"> - -<div id="parent"> - <div id="form_login"> - <div class="text-center"> - <p> - To login go to <code>/login/XXXXXX</code><br /> - To register go to <code>/signup/XXXXXX</code><br /> - (replace X by the numbers in the image)<br /> - Link valid for <strong><span id="timer_countdown"></span></strong> - </p> - <img src="data:image/png;base64,`)) - buf.Write([]byte(loginLink.Img)) - buf.Write([]byte(`" style="background-color: hsl(0, 0%, 90%);" class="captcha-img" /> - <form method="get"> - <input type="text" name="captcha" maxlength="6" autofocus /> - <button name="login" value="1" type="submit">Login</button> - <button name="signup" value="1" type="submit">Register</button> - </form> - </div> - </div> -</div> - -</body> -</html>`)) - return c.HTMLBlob(http.StatusOK, buf.Bytes()) -} - -const max2faAttempts = 4 - -// partialAuthCache keep track of partial auth token -> user id. -// When a user login and have 2fa enabled, we create a "partial" auth cookie. -// The token can be used to complete the 2fa authentication. -var partialAuthCache = cache.New[*PartialAuthItem](2*time.Minute, time.Hour) - -type PartialAuthItem struct { - UserID database.UserID - Step PartialAuthStep // Inform which type of 2fa the user is supposed to complete - SessionDuration time.Duration - Attempt int -} - -func NewPartialAuthItem(userID database.UserID, step PartialAuthStep, sessionDuration time.Duration) *PartialAuthItem { - return &PartialAuthItem{UserID: userID, Step: step, SessionDuration: sessionDuration} -} - -type PartialAuthStep string - func createSessionCookie(value string, sessionDuration time.Duration) *http.Cookie { return hutils.CreateCookie(hutils.AuthCookieName, value, int64(sessionDuration.Seconds())) } @@ -262,66 +142,6 @@ func metaCss(token string) []byte { return buf.Bytes() } -var signupCache = cache.New[SignupInfo](5*time.Minute, 5*time.Minute) - -type SignupInfo struct { - ScreenWidth string - ScreenHeight string - HelvaticaLoaded bool - - hasSolvedCaptcha bool - UpdatedAt string -} - -func SignalCss1(c echo.Context) error { - authUser := c.Get("authUser").(*database.User) - db := c.Get("database").(*database.DkfDB) - data := c.Param("data") - data = strings.TrimRight(data, ".png") - data = strings.TrimRight(data, ".ttf") - signalP := c.Param("signal") - var info SignupInfo - _ = json.Unmarshal([]byte(authUser.SignupMetadata), &info) - switch signalP { - case "sw": - info.ScreenWidth = data - case "sh": - info.ScreenHeight = data - case "Helvatica": - info.HelvaticaLoaded = true - } - info.UpdatedAt = time.Now().Format(time.RFC3339) - signupInfoEnc, _ := json.Marshal(info) - authUser.SignupMetadata = string(signupInfoEnc) - authUser.DoSave(db) - return c.NoContent(http.StatusOK) -} - -func SignalCss(c echo.Context) error { - data := c.Param("data") - data = strings.TrimRight(data, ".png") - data = strings.TrimRight(data, ".ttf") - token := c.Param("signupToken") - signalP := c.Param("signal") - var info SignupInfo - if val, found := signupCache.Get(token); found { - info = val - } else { - info = SignupInfo{} - } - switch signalP { - case "sw": - info.ScreenWidth = data - case "sh": - info.ScreenHeight = data - case "Helvatica": - info.HelvaticaLoaded = true - } - info.UpdatedAt = time.Now().Format(time.RFC3339) - signupCache.SetD(token, info) - return c.NoContent(http.StatusOK) -} - type WaitPageCookiePayload struct { Token string Count int64 @@ -379,121 +199,6 @@ func waitPageWrapper(c echo.Context, clb echo.HandlerFunc, cookieName string) er return clb(c) } -// The random wait time 0-15 seconds make sure the load is evenly distributed while under DDoS. -// Not all requests to the signup endpoint will get the captcha at the same time, -// so you cannot just refresh the page until you get a captcha that is easier to crack. -func signupHandler(c echo.Context) error { - db := c.Get("database").(*database.DkfDB) - start := c.Get("start").(int64) - signupToken := c.Get("signupToken").(string) - var data signupData - config.SignupPageLoad.Inc() - - data.PowEnabled = config.PowEnabled.Load() - data.CaptchaSec = 120 - data.Frames = generateCssFrames(data.CaptchaSec, nil, true) - - hbCookie, hbCookieErr := c.Cookie(hutils.HBCookieName) - hasHBCookie := hbCookieErr == nil && hbCookie.Value != "" - - signupInfo, _ := signupCache.Get(signupToken) - - data.HasSolvedCaptcha = signupInfo.hasSolvedCaptcha - if !signupInfo.hasSolvedCaptcha { - data.CaptchaID, data.CaptchaImg = captcha.New() - } - - if c.Request().Method == http.MethodPost { - data.Username = strings.TrimSpace(c.Request().PostFormValue("username")) - data.Password = c.Request().PostFormValue("password") - data.RePassword = c.Request().PostFormValue("repassword") - data.Pow = c.Request().PostFormValue("pow") - captchaID := c.Request().PostFormValue("captcha_id") - captchaInput := c.Request().PostFormValue("captcha") - captchaInputImg := c.Request().PostFormValue("captcha_img") - if !signupInfo.hasSolvedCaptcha { - if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil { - data.ErrCaptcha = err.Error() - config.SignupFailed.Inc() - return c.Render(http.StatusOK, "standalone.signup", data) - } - } - data.Captcha = captchaInput - data.CaptchaImg = captchaInputImg - - signupInfo.hasSolvedCaptcha = true - data.HasSolvedCaptcha = signupInfo.hasSolvedCaptcha - signupCache.SetD(signupToken, signupInfo) - - // verify POW - if config.PowEnabled.IsTrue() { - if !hutils.VerifyPow(data.Username, data.Pow, config.PowDifficulty) { - data.ErrPow = "invalid proof of work" - return c.Render(http.StatusOK, "standalone.signup", data) - } - } - - config.SignupSucceed.Inc() - - // If SignupFakeEnabled is enabled, we always say the account was created, but we do not create it. - if config.SignupFakeEnabled.IsTrue() { - c.SetCookie(hutils.DeleteCookie(hutils.WaitCookieName)) - return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"}) - } - - // Fuck with kicked users. Prevent them from registering again. - //authCookie, err := c.Cookie("auth-token") - //if err == nil && authCookie.Value != "" { - // return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"}) - //} - - signupInfoEnc, _ := json.Marshal(signupInfo) - - registrationDuration := time.Now().UnixMilli() - start - newUser, errs := db.CreateUser(data.Username, data.Password, data.RePassword, registrationDuration, string(signupInfoEnc)) - if errs.HasError() { - data.Errors = errs - return c.Render(http.StatusOK, "standalone.signup", data) - } - - // Fuck with hellbanned users. New account also hellbanned - if hasHBCookie { - newUser.IsHellbanned = true - newUser.DoSave(db) - } - - invitationToken := c.Param("invitationToken") - if invitationToken != "" { - if invitation, err := db.GetUnusedInvitationByToken(invitationToken); err == nil { - invitation.InviteeUserID = newUser.ID - invitation.DoSave(db) - } - } - - // If more than 10 users were created in the past minute, auto disable signup for the website - if db.GetRecentUsersCount() > 10 { - settings := db.GetSettings() - settings.SignupEnabled = false - settings.DoSave(db) - config.SignupEnabled.SetFalse() - if userNull, err := db.GetUserByUsername(config.NullUsername); err == nil { - db.NewAudit(userNull, fmt.Sprintf("auto turn off signup")) - - // Display message in chat - txt := fmt.Sprintf("auto turn off registrations") - if err := db.CreateSysMsg(txt, txt, "", config.GeneralRoomID, userNull.ID); err != nil { - logrus.Error(err) - } - } - } - - c.SetCookie(hutils.DeleteCookie(hutils.WaitCookieName)) - return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"}) - } - - return c.Render(http.StatusOK, "standalone.signup", data) -} - // RecaptchaResponse ... type RecaptchaResponse struct { Success bool `json:"success"` diff --git a/pkg/web/handlers/login.go b/pkg/web/handlers/login.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "dkforest/pkg/cache" "dkforest/pkg/captcha" "dkforest/pkg/config" @@ -9,6 +10,7 @@ import ( "dkforest/pkg/utils" hutils "dkforest/pkg/web/handlers/utils" "fmt" + "github.com/asaskevich/govalidator" "github.com/labstack/echo" "github.com/pquerna/otp/totp" "github.com/sirupsen/logrus" @@ -18,6 +20,26 @@ import ( "time" ) +const max2faAttempts = 4 + +// partialAuthCache keep track of partial auth token -> user id. +// When a user login and have 2fa enabled, we create a "partial" auth cookie. +// The token can be used to complete the 2fa authentication. +var partialAuthCache = cache.New[*PartialAuthItem](2*time.Minute, time.Hour) + +type PartialAuthItem struct { + UserID database.UserID + Step PartialAuthStep // Inform which type of 2fa the user is supposed to complete + SessionDuration time.Duration + Attempt int +} + +func NewPartialAuthItem(userID database.UserID, step PartialAuthStep, sessionDuration time.Duration) *PartialAuthItem { + return &PartialAuthItem{UserID: userID, Step: step, SessionDuration: sessionDuration} +} + +type PartialAuthStep string + const ( TwoFactorStep PartialAuthStep = "2fa" PgpSignStep PartialAuthStep = "pgp_sign_2fa" @@ -624,3 +646,101 @@ func forgotPasswordHandler(c echo.Context) error { return c.Render(http.StatusOK, "flash", FlashResponse{"should not go here", "/login", "alert-danger"}) } + +func protectHomeHandler(c echo.Context) error { + if c.Request().Method == http.MethodPost { + return c.NoContent(http.StatusNotFound) + } + captchaQuery := c.QueryParam("captcha") + loginQuery := c.QueryParam("login") + signupQuery := c.QueryParam("signup") + if captchaQuery != "" { + if len(captchaQuery) > 6 || len(loginQuery) > 1 || len(signupQuery) > 1 || + !govalidator.IsASCII(captchaQuery) || !govalidator.IsASCII(loginQuery) || !govalidator.IsASCII(signupQuery) { + time.Sleep(utils.RandSec(3, 7)) + return c.NoContent(http.StatusOK) + } + redirectTo := "/login/" + captchaQuery + if signupQuery == "1" { + redirectTo = "/signup/" + captchaQuery + } + time.Sleep(utils.RandSec(1, 2)) + return c.Redirect(http.StatusFound, redirectTo) + } + loginLink, found := tempLoginCache.Get("login_link") + if !found { + loginLink.ID, loginLink.Img = captcha.NewWithParams(captcha.Params{Store: tempLoginStore}) + loginLink.ValidUntil = time.Now().Add(3 * time.Minute) + tempLoginCache.SetD("login_link", loginLink) + } + + waitTime := int64(time.Until(loginLink.ValidUntil).Seconds()) + + // Generate css frames + frames := generateCssFrames(waitTime, func(i int64) string { + return utils.ShortDur(time.Duration(i) * time.Second) + }, true) + + time.Sleep(utils.RandSec(1, 2)) + bufTmp := make([]byte, 0, 1024*4) + buf := bytes.NewBuffer(bufTmp) + buf.Write([]byte(`<!DOCTYPE html><html lang="en"><head> + <link href="/public/img/favicon.ico" rel="icon" type="image/x-icon" /> + <meta charset="UTF-8" /> + <meta name="author" content="n0tr1v"> + <meta name="language" content="English"> + <meta name="revisit-after" content="1 days"> + <meta http-equiv="expires" content="0"> + <meta http-equiv="pragma" content="no-cache"> + <title>DarkForest</title> + <style> + body, html { height: 100%; width:100%; display:table; background-color: #222; color: white; line-height: 25px; + font-family: Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; } + body { display:table-cell; vertical-align:middle; } + #parent { display: table; width: 100%; } + #form_login { display: table; margin: auto; } + .captcha-img { transition: transform .2s; } + .captcha-img:hover { transform: scale(2.5); } + #timer_countdown:before { + content: "`)) + buf.Write([]byte(utils.ShortDur(time.Duration(waitTime) * time.Second))) + buf.Write([]byte(`"; + animation: `)) + buf.Write([]byte(utils.FormatInt64(waitTime))) + buf.Write([]byte(`s 1s forwards timer_countdown_frames; + } + @keyframes timer_countdown_frames {`)) + for _, frame := range frames { + buf.Write([]byte(frame)) + } + buf.Write([]byte(` + } + </style> +</head> +<body class="bg"> + +<div id="parent"> + <div id="form_login"> + <div class="text-center"> + <p> + To login go to <code>/login/XXXXXX</code><br /> + To register go to <code>/signup/XXXXXX</code><br /> + (replace X by the numbers in the image)<br /> + Link valid for <strong><span id="timer_countdown"></span></strong> + </p> + <img src="data:image/png;base64,`)) + buf.Write([]byte(loginLink.Img)) + buf.Write([]byte(`" style="background-color: hsl(0, 0%, 90%);" class="captcha-img" /> + <form method="get"> + <input type="text" name="captcha" maxlength="6" autofocus /> + <button name="login" value="1" type="submit">Login</button> + <button name="signup" value="1" type="submit">Register</button> + </form> + </div> + </div> +</div> + +</body> +</html>`)) + return c.HTMLBlob(http.StatusOK, buf.Bytes()) +} diff --git a/pkg/web/handlers/signup.go b/pkg/web/handlers/signup.go @@ -1,14 +1,31 @@ package handlers import ( + "dkforest/pkg/cache" "dkforest/pkg/captcha" "dkforest/pkg/config" "dkforest/pkg/database" hutils "dkforest/pkg/web/handlers/utils" + "encoding/json" + "fmt" "github.com/labstack/echo" + "github.com/sirupsen/logrus" "net/http" + "strings" + "time" ) +var signupCache = cache.New[SignupInfo](5*time.Minute, 5*time.Minute) + +type SignupInfo struct { + ScreenWidth string + ScreenHeight string + HelvaticaLoaded bool + + hasSolvedCaptcha bool + UpdatedAt string +} + // SignupHandler ... func SignupHandler(c echo.Context) error { if config.ProtectHome.IsTrue() { @@ -50,3 +67,167 @@ func tmpSignupHandler(c echo.Context) error { } return waitPageWrapper(c, signupHandler, hutils.WaitCookieName) } + +// The random wait time 0-15 seconds make sure the load is evenly distributed while under DDoS. +// Not all requests to the signup endpoint will get the captcha at the same time, +// so you cannot just refresh the page until you get a captcha that is easier to crack. +func signupHandler(c echo.Context) error { + db := c.Get("database").(*database.DkfDB) + start := c.Get("start").(int64) + signupToken := c.Get("signupToken").(string) + var data signupData + config.SignupPageLoad.Inc() + + data.PowEnabled = config.PowEnabled.Load() + data.CaptchaSec = 120 + data.Frames = generateCssFrames(data.CaptchaSec, nil, true) + + hbCookie, hbCookieErr := c.Cookie(hutils.HBCookieName) + hasHBCookie := hbCookieErr == nil && hbCookie.Value != "" + + signupInfo, _ := signupCache.Get(signupToken) + + data.HasSolvedCaptcha = signupInfo.hasSolvedCaptcha + if !signupInfo.hasSolvedCaptcha { + data.CaptchaID, data.CaptchaImg = captcha.New() + } + + if c.Request().Method == http.MethodPost { + data.Username = strings.TrimSpace(c.Request().PostFormValue("username")) + data.Password = c.Request().PostFormValue("password") + data.RePassword = c.Request().PostFormValue("repassword") + data.Pow = c.Request().PostFormValue("pow") + captchaID := c.Request().PostFormValue("captcha_id") + captchaInput := c.Request().PostFormValue("captcha") + captchaInputImg := c.Request().PostFormValue("captcha_img") + if !signupInfo.hasSolvedCaptcha { + if err := hutils.CaptchaVerifyString(c, captchaID, captchaInput); err != nil { + data.ErrCaptcha = err.Error() + config.SignupFailed.Inc() + return c.Render(http.StatusOK, "standalone.signup", data) + } + } + data.Captcha = captchaInput + data.CaptchaImg = captchaInputImg + + signupInfo.hasSolvedCaptcha = true + data.HasSolvedCaptcha = signupInfo.hasSolvedCaptcha + signupCache.SetD(signupToken, signupInfo) + + // verify POW + if config.PowEnabled.IsTrue() { + if !hutils.VerifyPow(data.Username, data.Pow, config.PowDifficulty) { + data.ErrPow = "invalid proof of work" + return c.Render(http.StatusOK, "standalone.signup", data) + } + } + + config.SignupSucceed.Inc() + + // If SignupFakeEnabled is enabled, we always say the account was created, but we do not create it. + if config.SignupFakeEnabled.IsTrue() { + c.SetCookie(hutils.DeleteCookie(hutils.WaitCookieName)) + return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"}) + } + + // Fuck with kicked users. Prevent them from registering again. + //authCookie, err := c.Cookie("auth-token") + //if err == nil && authCookie.Value != "" { + // return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"}) + //} + + signupInfoEnc, _ := json.Marshal(signupInfo) + + registrationDuration := time.Now().UnixMilli() - start + newUser, errs := db.CreateUser(data.Username, data.Password, data.RePassword, registrationDuration, string(signupInfoEnc)) + if errs.HasError() { + data.Errors = errs + return c.Render(http.StatusOK, "standalone.signup", data) + } + + // Fuck with hellbanned users. New account also hellbanned + if hasHBCookie { + newUser.IsHellbanned = true + newUser.DoSave(db) + } + + invitationToken := c.Param("invitationToken") + if invitationToken != "" { + if invitation, err := db.GetUnusedInvitationByToken(invitationToken); err == nil { + invitation.InviteeUserID = newUser.ID + invitation.DoSave(db) + } + } + + // If more than 10 users were created in the past minute, auto disable signup for the website + if db.GetRecentUsersCount() > 10 { + settings := db.GetSettings() + settings.SignupEnabled = false + settings.DoSave(db) + config.SignupEnabled.SetFalse() + if userNull, err := db.GetUserByUsername(config.NullUsername); err == nil { + db.NewAudit(userNull, fmt.Sprintf("auto turn off signup")) + + // Display message in chat + txt := fmt.Sprintf("auto turn off registrations") + if err := db.CreateSysMsg(txt, txt, "", config.GeneralRoomID, userNull.ID); err != nil { + logrus.Error(err) + } + } + } + + c.SetCookie(hutils.DeleteCookie(hutils.WaitCookieName)) + return c.Render(http.StatusOK, "flash", FlashResponse{"Your account has been created", "/login", "alert-success"}) + } + + return c.Render(http.StatusOK, "standalone.signup", data) +} + +func SignalCss1(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + db := c.Get("database").(*database.DkfDB) + data := c.Param("data") + data = strings.TrimRight(data, ".png") + data = strings.TrimRight(data, ".ttf") + signalP := c.Param("signal") + var info SignupInfo + _ = json.Unmarshal([]byte(authUser.SignupMetadata), &info) + switch signalP { + case "sw": + info.ScreenWidth = data + case "sh": + info.ScreenHeight = data + case "Helvatica": + info.HelvaticaLoaded = true + } + info.UpdatedAt = time.Now().Format(time.RFC3339) + signupInfoEnc, _ := json.Marshal(info) + authUser.SignupMetadata = string(signupInfoEnc) + authUser.DoSave(db) + return c.NoContent(http.StatusOK) +} + +func SignalCss(c echo.Context) error { + data := c.Param("data") + data = strings.TrimRight(data, ".png") + data = strings.TrimRight(data, ".ttf") + token := c.Param("signupToken") + signalP := c.Param("signal") + var info SignupInfo + if val, found := signupCache.Get(token); found { + info = val + } else { + info = SignupInfo{} + } + switch signalP { + case "sw": + info.ScreenWidth = data + case "sh": + info.ScreenHeight = data + case "Helvatica": + info.HelvaticaLoaded = true + } + info.UpdatedAt = time.Now().Format(time.RFC3339) + signupCache.SetD(token, info) + return c.NoContent(http.StatusOK) +}