Files
tapir/internal/adapters/dex/dex_test.go
T
mathias c812c71ecc
CI / Lint / Test / Vet (push) Successful in 26s
CI / Build & Import (push) Successful in 12s
fix(dex): store raw bcrypt hash in Password CR, not base64-encoded
The original NOTE claimed Dex's kubernetes storage types Hash as []byte,
requiring the bcrypt string to be base64-encoded before storage. This was
wrong: Dex v2.41 stores and compares the hash field as a plain string. The
base64-encoding caused every invite login to fail with 'Invalid credentials'
because Dex passed the base64 bytes (starting with 'J' not '$') directly to
bcrypt. Static passwords in the configmap always used raw bcrypt strings and
worked fine — confirming the dynamic CR encoding was the bug.
2026-06-07 09:28:11 +02:00

102 lines
3.3 KiB
Go

package dex
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
// newTestClient points a PasswordClient at an httptest server, using that
// server's TLS client so the in-cluster TLS path is exercised without a real CA.
func newTestClient(srv *httptest.Server) *PasswordClient {
return newClient(srv.URL, "test-token", srv.Client())
}
func TestCreatePasswordSuccess(t *testing.T) {
var gotAuth, gotPath, gotMethod string
var gotBody password
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth, gotPath, gotMethod = r.Header.Get("Authorization"), r.URL.Path, r.Method
b, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(b, &gotBody)
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"kind":"Password"}`))
}))
defer srv.Close()
err := newTestClient(srv).CreatePassword(context.Background(),
"New.User@Example.com", "$2a$12$abcdefghijklmnopqrstuv", "user-uuid-1")
require.NoError(t, err)
require.Equal(t, http.MethodPost, gotMethod)
require.Equal(t, passwordsPath, gotPath)
require.Equal(t, "Bearer test-token", gotAuth)
// Email/username carry the raw address; the CR name is sanitised + lowercased.
require.Equal(t, "New.User@Example.com", gotBody.Email)
require.Equal(t, "New.User@Example.com", gotBody.Username)
require.Equal(t, "user-uuid-1", gotBody.UserID)
require.Equal(t, "new-dot-user-at-example-dot-com", gotBody.Metadata["name"])
require.Equal(t, "auth", gotBody.Metadata["namespace"])
// Hash is stored as the raw bcrypt string — Dex compares it directly.
require.Equal(t, "$2a$12$abcdefghijklmnopqrstuv", gotBody.Hash)
}
func TestCreatePasswordConflict(t *testing.T) {
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusConflict)
}))
defer srv.Close()
err := newTestClient(srv).CreatePassword(context.Background(), "dup@example.com", "$2a$12$x", "u")
require.ErrorIs(t, err, ErrPasswordExists)
}
func TestCreatePasswordForbidden(t *testing.T) {
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusForbidden)
}))
defer srv.Close()
err := newTestClient(srv).CreatePassword(context.Background(), "x@example.com", "$2a$12$x", "u")
require.ErrorIs(t, err, ErrForbidden)
}
func TestCreatePasswordUnexpectedStatus(t *testing.T) {
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("boom"))
}))
defer srv.Close()
err := newTestClient(srv).CreatePassword(context.Background(), "x@example.com", "$2a$12$x", "u")
require.Error(t, err)
require.NotErrorIs(t, err, ErrPasswordExists)
require.NotErrorIs(t, err, ErrForbidden)
require.Contains(t, err.Error(), "500")
}
func TestNewPasswordClientNotInCluster(t *testing.T) {
// In the test environment the SA token mount does not exist.
_, err := NewPasswordClient()
require.ErrorIs(t, err, ErrNotInCluster)
}
func TestPasswordName(t *testing.T) {
cases := map[string]string{
"Alice@Example.com": "alice-at-example-dot-com",
"a.b+c@gmail.com": "a-dot-b-c-at-gmail-dot-com",
"UPPER@DOMAIN.IO": "upper-at-domain-dot-io",
}
for in, want := range cases {
require.Equal(t, want, passwordName(in), in)
}
}