Skip to content

Commit

Permalink
Layer noise into captcha img
Browse files Browse the repository at this point in the history
This modifies the existing captcha img generation by layering random
noise below and on top of the generated code. This makes the captcha a
bit more "captcha-like" and not just a simple string of numbers.

The noise is not included for CLI users.
  • Loading branch information
benbusby committed Sep 3, 2024
1 parent 6f841a1 commit 1178044
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 6 deletions.
29 changes: 27 additions & 2 deletions backend/server/auth/captcha.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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{
Expand All @@ -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)
}
}
}
}
4 changes: 3 additions & 1 deletion backend/server/auth/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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{
Expand Down
4 changes: 2 additions & 2 deletions backend/server/auth/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ 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)
if err != nil {
return "", "", err
}

captchaBase64 := GenerateCaptchaImage(code)
captchaBase64 := GenerateCaptchaImage(code, isCLI)
return id, captchaBase64, nil
}
2 changes: 1 addition & 1 deletion backend/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
3 changes: 3 additions & 0 deletions cli/requests/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package requests
import (
"bytes"
"net/http"
"yeetfile/shared/constants"
)

func GetRequest(session, url string) (*http.Response, error) {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions shared/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 1178044

Please sign in to comment.