diff --git a/backend/server/auth/captcha.go b/backend/server/auth/captcha.go index e3a44a8..8a981bc 100644 --- a/backend/server/auth/captcha.go +++ b/backend/server/auth/captcha.go @@ -10,15 +10,19 @@ import ( "image" "image/color" "image/jpeg" + "math/rand" ) // GenerateCaptchaImage takes a verification code string and generates an image // to use for verifying non-email users. -func GenerateCaptchaImage(code string) string { +func GenerateCaptchaImage(code string, isCLI bool) string { // Add spaces to code for legibility //code = strings.Join(strings.Split(code, ""), " ") img := image.NewRGBA(image.Rect(0, 0, 45, 25)) + + addNoise(img, isCLI) addLabel(img, 2, 17, code) + addNoise(img, isCLI) // Resize dst := image.NewRGBA(image.Rect(0, 0, 250, 100)) @@ -33,7 +37,7 @@ func GenerateCaptchaImage(code string) string { } func addLabel(img *image.RGBA, x, y int, label string) { - col := color.RGBA{R: 255, G: 255, B: 255, A: 255} + col := color.RGBA{G: 255, A: 255} point := fixed.Point26_6{X: fixed.I(x), Y: fixed.I(y)} d := &font.Drawer{ @@ -44,3 +48,24 @@ func addLabel(img *image.RGBA, x, y int, label string) { } d.DrawString(label) } + +func addNoise(img *image.RGBA, isCLI bool) { + if isCLI { + return + } + + bounds := img.Bounds() + draw.Draw(img, bounds, img, bounds.Min, draw.Src) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + if rand.Float64() < 0.07 { + noiseColor := color.RGBA{G: 100, B: 255, A: 255} + if rand.Intn(2) == 0 { + noiseColor = color.RGBA{G: 100, R: 255, A: 255} + } + + img.Set(x, y, noiseColor) + } + } + } +} diff --git a/backend/server/auth/handlers.go b/backend/server/auth/handlers.go index 2a33262..24eed14 100644 --- a/backend/server/auth/handlers.go +++ b/backend/server/auth/handlers.go @@ -15,6 +15,7 @@ import ( "yeetfile/backend/server/transfer/vault" "yeetfile/backend/utils" "yeetfile/shared" + "yeetfile/shared/constants" ) // LoginHandler handles a POST request to /login to log the user in. @@ -78,7 +79,8 @@ func SignupHandler(w http.ResponseWriter, req *http.Request) { if len(signupData.Identifier) == 0 { // No email, so this is an account ID only signup - id, captcha, err := SignupAccountIDOnly() + isCLI := req.UserAgent() == constants.CLIUserAgent + id, captcha, err := SignupAccountIDOnly(isCLI) if err != nil { status = http.StatusBadRequest response = shared.SignupResponse{ diff --git a/backend/server/auth/signup.go b/backend/server/auth/signup.go index 0beda9f..c67086a 100644 --- a/backend/server/auth/signup.go +++ b/backend/server/auth/signup.go @@ -37,7 +37,7 @@ func SignupWithEmail(signup shared.Signup) error { // SignupAccountIDOnly creates a new user with only an account ID as the user's // login credential. Returns the user's (temporary) account ID, an image // of their captcha code, and an error. -func SignupAccountIDOnly() (string, string, error) { +func SignupAccountIDOnly(isCLI bool) (string, string, error) { id := db.CreateUniqueUserID() code, err := db.NewVerification(shared.Signup{Identifier: id}, nil, false) @@ -45,6 +45,6 @@ func SignupAccountIDOnly() (string, string, error) { return "", "", err } - captchaBase64 := GenerateCaptchaImage(code) + captchaBase64 := GenerateCaptchaImage(code, isCLI) return id, captchaBase64, nil } diff --git a/backend/server/server.go b/backend/server/server.go index de135ad..4cc6698 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -63,7 +63,7 @@ func Run(addr string) { // Auth (signup, login/logout, account mgmt, etc) {POST, endpoints.VerifyEmail, auth.VerifyEmailHandler}, - {POST, endpoints.VerifyAccount, auth.VerifyAccountHandler}, + {POST, endpoints.VerifyAccount, LimiterMiddleware(auth.VerifyAccountHandler)}, {GET, endpoints.Session, session.SessionHandler}, {GET, endpoints.Logout, auth.LogoutHandler}, {POST, endpoints.Login, auth.LoginHandler}, diff --git a/cli/requests/requests.go b/cli/requests/requests.go index c6e8bdf..f3b5c2b 100644 --- a/cli/requests/requests.go +++ b/cli/requests/requests.go @@ -3,6 +3,7 @@ package requests import ( "bytes" "net/http" + "yeetfile/shared/constants" ) func GetRequest(session, url string) (*http.Response, error) { @@ -34,6 +35,8 @@ func sendRequest(session, method, url string, data []byte) (*http.Response, erro }) } + req.Header.Set("User-Agent", constants.CLIUserAgent) + resp, err := new(http.Transport).RoundTrip(req) if err != nil { return nil, err diff --git a/shared/constants/constants.go b/shared/constants/constants.go index 4a0915d..4bc2c3f 100644 --- a/shared/constants/constants.go +++ b/shared/constants/constants.go @@ -4,6 +4,8 @@ const VERSION = "1.0.0" const JSRandomSessionKey = "YEETFILE_RANDOM_SESSION_KEY" +const CLIUserAgent = "yeetfile-cli" + const Argon2Mem uint32 = 64 * 1024 // 64MB const Argon2Iter uint32 = 2