mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-10 15:05:40 +00:00
feat(server): expose password_set on /auth/me + TS User type (#819)
The delete-account UI must branch between a password prompt (self-host or any user with a password) and a confirm-only flow (OAuth-only users with no password). The client had no signal for this: oauth_providers is not a valid proxy since a user can have both a password and linked OAuth. Add "password_set": user.HasPassword() to the /auth/me response map and password_set?: boolean to the TS User interface. A handler test asserts the field for both a password user (true, via bootstrap) and an OAuth-only user (false, via CreateOAuthUser). Closes TASK-1957. Claude-Session: https://claude.ai/code/session_01HxBkAMiFBtCRJ2tKSCt3ST
This commit is contained in:
@@ -904,6 +904,7 @@ func (s *Server) handleGetCurrentUser(w http.ResponseWriter, r *http.Request) {
|
||||
"role": user.Role,
|
||||
"avatar_url": user.AvatarURL,
|
||||
"totp_enabled": user.TOTPEnabled,
|
||||
"password_set": user.HasPassword(),
|
||||
"created_at": user.CreatedAt,
|
||||
"updated_at": user.UpdatedAt,
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/PerpetualSoftware/pad/internal/models"
|
||||
)
|
||||
@@ -410,6 +411,44 @@ func TestAuthMeEndpoint(t *testing.T) {
|
||||
if user["email"] != "me@test.com" {
|
||||
t.Errorf("expected email 'me@test.com', got %v", user["email"])
|
||||
}
|
||||
// A bootstrapped user set a password, so password_set must be true. The
|
||||
// delete-account UI keys off this to show a password prompt vs a
|
||||
// confirm-only flow (TASK-1957).
|
||||
if set, ok := user["password_set"].(bool); !ok || !set {
|
||||
t.Errorf("expected password_set=true for a password user, got %v", user["password_set"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestAuthMeReportsPasswordSetForOAuthUser pins the OAuth-only branch of
|
||||
// TASK-1957: a user created via OAuth (no password) must report
|
||||
// password_set=false so the client can offer confirm-only account deletion.
|
||||
func TestAuthMeReportsPasswordSetForOAuthUser(t *testing.T) {
|
||||
srv := testServer(t)
|
||||
// Bootstrap a first user so the instance is initialized and auth is enforced.
|
||||
bootstrapFirstUser(t, srv, "admin@test.com", "Admin")
|
||||
|
||||
oauthUser, err := srv.store.CreateOAuthUser("oauth@test.com", "OAuth Only", "")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateOAuthUser: %v", err)
|
||||
}
|
||||
if oauthUser.HasPassword() {
|
||||
t.Fatal("expected freshly created OAuth user to have no password")
|
||||
}
|
||||
token, err := srv.store.CreateSession(oauthUser.ID, "go-test", "192.0.2.1", "", 24*time.Hour)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSession: %v", err)
|
||||
}
|
||||
|
||||
rr := doRequestWithCookie(srv, "GET", "/api/v1/auth/me", nil, token)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("me: expected 200, got %d: %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
|
||||
var user map[string]interface{}
|
||||
parseJSON(t, rr, &user)
|
||||
if set, ok := user["password_set"].(bool); !ok || set {
|
||||
t.Errorf("expected password_set=false for an OAuth-only user, got %v", user["password_set"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateRegistration(t *testing.T) {
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface User {
|
||||
avatar_url?: string;
|
||||
oauth_providers?: string[];
|
||||
totp_enabled?: boolean;
|
||||
password_set?: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user