Repository: supabase/gotrue (go)

Issue:
Password is truncuated when sign up
# Bug report

<!-- 

⚠️ We receive a lot of bug reports which have already been solved or discussed. If you are looking for help, please try these first:

- Docs: https://docs.supabase.com
- Discussions: https://github.com/supabase/supabase/discussions
- Discord: https://discord.supabase.com

Before opening a bug report, please verify the following: 

-->

- [x] I confirm this is a bug with Supabase, not with my own application. 
- [x] I confirm I have searched the [Docs](https://docs.supabase.com), GitHub [Discussions](https://github.com/supabase/supabase/discussions), and [Discord](https://discord.supabase.com). 

## Describe the bug

Only the first 72 characters of password are significant

## To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

1. Start gotrue & postgres
2. Create a new user a long password
3. Sign in with a shorter password
4. Observe successful login

## Expected behavior

Passwords must not be truncated 

## Screenshots

If applicable, add screenshots to help explain your problem.

## System information

- OS: **N/A**
- Browser (if applies) [e.g. chrome, safari] **N/A**
- Version of supabase-js: [e.g. 6.0.2] **N/A**
- Version of Node.js: [e.g. 10.10.0] **N/A**
- Gotrue image used `ghcr.io/supabase/gotrue:v2.64.0`

## Additional context

**Configuration used for testing**

<details><summary>.env</summary>

```
SITE_URL=http://localhost:3000
ADDITIONAL_REDIRECT_URLS=
JWT_EXPIRY=3600
DISABLE_SIGNUP=false
API_EXTERNAL_URL=http://localhost:8000

## Mailer Config
MAILER_URLPATHS_CONFIRMATION="/auth/v1/verify"
MAILER_URLPATHS_INVITE="/auth/v1/verify"
MAILER_URLPATHS_RECOVERY="/auth/v1/verify"
MAILER_URLPATHS_EMAIL_CHANGE="/auth/v1/verify"

## Email auth
ENABLE_EMAIL_SIGNUP=true
ENABLE_EMAIL_AUTOCONFIRM=true
SMTP_ADMIN_EMAIL=admin@example.com
SMTP_HOST=supabase-mail
SMTP_PORT=2500
SMTP_USER=fake_mail_user
SMTP_PASS=fake_mail_password
SMTP_SENDER_NAME=fake_sender

## Phone auth
ENABLE_PHONE_SIGNUP=true
ENABLE_PHONE_AUTOCONFIRM=true
```

</details> 

<details><summary>docker-compose.yml (gotrue)</summary>

```yml
version: '3.8'

networks:
  supa:
    external: true

services:
  auth:
    container_name: supabase-auth
    image: supabase/gotrue:v2.60.7
    healthcheck:
      test:
        [
          'CMD',
          'wget',
          '--no-verbose',
          '--tries=1',
          '--spider',
          'http://localhost:9999/health'
        ]
      timeout: 5s
      interval: 5s
      retries: 3
    restart: unless-stopped
    ports:
      - 9999:9999
    networks:
      - supa
    environment:
      GOTRUE_API_HOST: 0.0.0.0
      GOTRUE_API_PORT: 9999
      API_EXTERNAL_URL: ${API_EXTERNAL_URL}
      
      GOTRUE_DB_DRIVER: postgres
      GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
      
      GOTRUE_SITE_URL: ${SITE_URL}
      GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS}
      GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}
      
      GOTRUE_JWT_ADMIN_ROLES: service_role
      GOTRUE_JWT_AUD: authenticated
      GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
      GOTRUE_JWT_EXP: ${JWT_EXPIRY}
      GOTRUE_JWT_SECRET: ${JWT_SECRET}
      
      GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
      GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
      # GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: true
      # GOTRUE_SMTP_MAX_FREQUENCY: 1s
      GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
      GOTRUE_SMTP_HOST: ${SMTP_HOST}
      GOTRUE_SMTP_PORT: ${SMTP_PORT}
      GOTRUE_SMTP_USER: ${SMTP_USER}
      GOTRUE_SMTP_PASS: ${SMTP_PASS}
      GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
      GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE}
      GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION}
      GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY}
      GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE}
      
      GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP}
      GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM}
      MFA_ENABLED: ${MFA_ENABLED}
```

</details> 

<details><summary>docker-compose.yml (postgres)</summary>

```yml
version: "3"

networks:
  supa:
    external: true
  host:
    external: true

services:
  db:
    container_name: supabase-db
    image: supabase/postgres:15.1.0.54-rc0
    healthcheck:
      test: pg_isready -U postgres -h localhost
      interval: 5s
      timeout: 5s
      retries: 10
    command:
      - postgres
      - -c
      - config_file=/etc/postgresql/postgresql.conf
      - -c
      - log_min_messages=fatal # prevents Realtime polling queries from appearing in logs
    restart: unless-stopped
    networks:
      - supa
    ports:
      - 5432:5432
    environment:
      POSTGRES_HOST: /var/run/postgresql
      PGPORT: ${POSTGRES_PORT}
      PGPASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      PGDATABASE: ${POSTGRES_DB}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - ./volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z
      # Must be superuser to create event trigger
      - ./volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z
      # Must be superuser to alter reserved role
      - ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
```

</details> 

> **Note**: postgres and gotrue are in separate docker-compose files to decouple the database from supabase. See [Managing your database](https://supabase.com/docs/guides/self-hosting#managing-your-database)

<details><summary>HTTP requests</summary>

```http
### Send POST request to sign up
POST localhost:9999/signup
Content-Type: application/json

{
    "email": "email123@example.com",
    "password": "qoqytjmwobnycqkxjjgjrlaxnbcjdyayibxryvuffrbmnhrhfijuabheqzxsbudryghoebqlnycazcniewbmlblhmtxh"
}

### Send POST request to sign in
POST localhost:9999/token?grant_type=password
Content-Type: application/json

{
    "email": "email123@example.com",
    "password": "qoqytjmwobnycqkxjjgjrlaxnbcjdyayibxryvuffrbmnhrhfijuabheqzxsbudryghoebql"
}
```

</details> 



Ref: https://github.com/hamza1311/firestorm/issues/1

Pull request description:
fix:  add check for max password length
## What kind of change does this PR introduce?

Fixes #1101 

## What is the current behavior?

Currently any password longer than 72 characters is automatically truncated without any notification. This allows a user to sign in with a shorter password of 72 characters, even if their password was intially longer.

## What is the new behavior?

Let's add a check if the user exceeds the maximum password length and return a clear error message to reject passwords that exceed that length.


Patch:
diff --git a/internal/crypto/password.go b/internal/crypto/password.go
index 93a2d97d..d494eaab 100644
--- a/internal/crypto/password.go
+++ b/internal/crypto/password.go
@@ -3,6 +3,7 @@ package crypto
 import (
 	"context"
 	"errors"
+	"fmt"
 
 	"github.com/supabase/auth/internal/observability"
 	"go.opentelemetry.io/otel/attribute"
@@ -21,6 +22,9 @@ const (
 	// hashing cost for any hashing algorithm,
 	// useful for tests only.
 	QuickHashCost HashCost = iota
+
+	// BCrypt hashed passwords have a 72 character limit
+	MaxPasswordLength = 72
 )
 
 // PasswordHashCost is the current pasword hashing cost
@@ -73,6 +77,10 @@ func CompareHashAndPassword(ctx context.Context, hash, password string) error {
 func GenerateFromPassword(ctx context.Context, password string) (string, error) {
 	var hashCost int
 
+	if len(password) > MaxPasswordLength {
+		return "", fmt.Errorf("password cannot be longer than %d characters", MaxPasswordLength)
+	}
+
 	switch PasswordHashCost {
 	case QuickHashCost:
 		hashCost = bcrypt.MinCost


Test patch:
diff --git a/internal/models/user_test.go b/internal/models/user_test.go
index 15c07cdf..4371a690 100644
--- a/internal/models/user_test.go
+++ b/internal/models/user_test.go
@@ -1,6 +1,7 @@
 package models
 
 import (
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -366,3 +367,15 @@ func (ts *UserTestSuite) TestUpdateUserEmailFailure() {
 	require.ErrorIs(ts.T(), userA.UpdateUserEmail(ts.db), UserEmailUniqueConflictError{})
 	require.Equal(ts.T(), primaryIdentity.GetEmail(), userA.GetEmail())
 }
+
+func (ts *UserTestSuite) TestSetPasswordTooLong() {
+	user, err := NewUser("", "", strings.Repeat("a", crypto.MaxPasswordLength), "", nil)
+	require.NoError(ts.T(), err)
+	require.NoError(ts.T(), ts.db.Create(user))
+
+	err = user.SetPassword(ts.db.Context(), strings.Repeat("a", crypto.MaxPasswordLength+1))
+	require.Error(ts.T(), err)
+
+	err = user.SetPassword(ts.db.Context(), strings.Repeat("a", crypto.MaxPasswordLength))
+	require.NoError(ts.T(), err)
+}