Replace the header "Byt lösenord" link with an Inställningar menu for
changing email or password. Email changes are two-step: request with
password, confirmation link to the new address, then password again on
confirm so a wrong inbox cannot take over the account.
- Backend: EmailChangeService, V10 email_change_tokens, confirm API
- Frontend: ChangeEmailPage, ConfirmEmailChangePage, header dropdown
- E2E: account-settings round-trips, Mailpit verification, wrong-password guard
- Flyway: V9 restore for dev DBs, CI migration checks, V10 for email tokens
Co-authored-by: Cursor <cursoragent@cursor.com>
Operators can fix prod admin passwords without email via Byt lösenord;
end users can use forgot-password when SMTP is configured. Local and CI
use Mailpit to capture outbound mail and verify reset links end-to-end.
- Backend: V8 password_reset_tokens, PasswordResetService, EmailService,
POST /api/auth/forgot-password, reset-password, change-password
- Optional testToken in forgot-password response (docker profile only, for E2E)
- Frontend: ForgotPasswordPage, ResetPasswordPage, ChangePasswordPage,
routes, login link, header Byt lösenord
- Mailpit (ghcr.io/axllent/mailpit:v1.28) in docker-compose + e2e stack
- E2E: password-reset.spec.ts + Mailpit API helper tests SMTP delivery
- Separate dev/e2e Docker image names to avoid overwriting bilhej-frontend
- Docs: README email section, production-email-checklist, .env.example
- Unit/integration tests for reset, change password, and Vitest page specs
Co-authored-by: Cursor <cursoragent@cursor.com>
Production must not ship test users, demo orders, or test1234. Dev and CI
still need seeded users for e2e. Prod creates one admin from deploy secrets.
- Move V2/V4/V6 seed migrations to db/dev-migration
- Add application-prod.yml with schema-only Flyway and ignore-missing for moved seeds
- Add AdminBootstrap to create admin from ADMIN_EMAIL and ADMIN_PASSWORD
- Wire docker,prod profile, deploy secrets, and localhost:5433 for SSH DB access
- Add hashPassword Gradle task for optional manual bcrypt generation
- Add @ManyToOne(fetch = LAZY) + @JoinColumn(name = "user_id",
insertable = false, updatable = false) to Order entity so ORM can
navigate order.getUser().getEmail() for admin responses
- Keep userId as writable UUID field; the relationship is read-only
to preserve backward compatibility with existing setUserId() calls
- Add getUser() / setUser() accessors
- Replace handwritten @Query JOIN FETCH with Spring Data derived method
findAllByOrderByCreatedAtDesc() annotated with @EntityGraph(attributePaths
= {"user"}) — same eager-load behavior, zero custom JPQL
- No database schema change: user_id FK already exists
- V1__create_users_table.sql replaces placeholder: creates users table with
id UUID PK, email UNIQUE NOT NULL, password_hash NOT NULL, subscription
VARCHAR(20) DEFAULT 'none' with CHECK constraint (none/basic/pro),
created_at/updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP.
Compatible with both H2 and PostgreSQL.
- SecurityConfig: minimal @Configuration providing BCryptPasswordEncoder
bean. Required because Spring Boot 4 no longer auto-configures a
PasswordEncoder.
- Subscription enum: NONE, BASIC, PRO with string values matching the DB
CHECK constraint.
- User entity: @PrePersist generates UUID and timestamps in application
code, @PreUpdate refreshes updated_at. Email setter normalizes to
lowercase for case-insensitive uniqueness. Explicit getters/setters
(no Lombok per guidelines).
- UserRepository: Spring Data JPA extending JpaRepository<User, UUID>.
findByEmail(Optional) and existsByEmail for duplicate checks.
- UserService: @RequiredArgsConstructor with constructor-injected
UserRepository and PasswordEncoder. createUser normalizes email,
checks duplicates via existsByEmail, throws EmailAlreadyExistsException,
hashes password with BCrypt, saves. findByEmail returns Optional<User>.
- EmailAlreadyExistsException: custom RuntimeException for duplicate
registration attempts. ControllerAdvice handler deferred to auth ticket.
Verification: ./gradlew test passes (Flyway + H2 context loads).
docker compose up -d succeeds, Flyway applies V1 against PostgreSQL 16.
\d users confirms all columns, constraints, defaults, and indexes.