Lab log

Lab log

Evidence, not notes: what I predicted, what my own system actually did, and what that taught me. Three lines per entry, newest first.

Entries (8)

Users in Postgres with BCrypt and POST /register

2026-10-17 · Build step 6. Real users: database + BCrypt

Predictedworks well bcoz all tests green
Saw16 Block A tests green (10 + 6). In Postgres authdb every hash starts with {bcrypt} and is 68 characters. Token for nithin has sub=nithin, scope payment.read payment.write, 5 minute lifetime.
LearnedDaoAuthenticationProvider compares the typed password with the stored hash; my code only loads the user. The delegating encoder adds the {bcrypt} prefix, so the column needs length >= 68 (50 would fail). An 'enabled' field that is a primitive boolean defaults to false, so registered users came out disabled until I set it.

Deleting the in-memory users deleted too much

2026-10-17 · Build step 6. Real users: database + BCrypt

Predictedwill affect the DB users table too
SawAfter removing the userDetailsService block, SecurityConfig also lost the registered clients, both JWK sources, jwtDecoder and authorizationServerSettings. It still compiled.
LearnedA green compile says nothing about missing beans; they fail at startup. Check the diff of a delete (git diff) before moving on, and keep a backup file next to the old configs.

A wrong password is a 302, not a 401

2026-10-17 · Build step 6. Real users: database + BCrypt

Predictedwrong password gives 401
SawForm login answers a wrong password, an unknown user and an old in-memory user with 302 to /login?error. A 401 only appears on endpoints using Basic or bearer auth, e.g. /oauth2/token with a bad client secret.
LearnedThe status code depends on the entry point, not on the failure. Fix the wording on the Step 7 card.

POST /register is open to everyone

2026-10-17 · Build step 6. Real users: database + BCrypt

Predictedno only admin access
SawThe exercise contract (A2.5/A2.6) needs /register permitted, so anyone who reaches :9000 can create accounts. A duplicate username also returns 409, which tells an attacker the name exists.
LearnedDecision: only an admin should create users. Needs hasRole('ADMIN'), a seeded first admin from an environment variable, Basic auth for scripts, and updated A2 tests.

System test: 26 of 27, the failure was timing

2026-10-17 · Build step 8. Security tests for Services A and B

Predictednot sure what will happen
Sawtest-all.ps1 first failed at login: it used the password 'password' but nithin was registered with 'password1' and alice did not exist. After fixing the users: 26 passed. The one FAIL (B asked the notification service) checks B's log after 2 seconds, but the SMTP send takes about 12.5 seconds; both logs show the email was sent.
LearnedA failing test can be the test: check the log timestamps before blaming the service. Replace the fixed sleep with a wait-until loop (up to about 20 seconds).

Tests silently stop working when a base annotation disappears

2026-10-17 · Build step 8. Security tests for Services A and B

PredictedYes, the tests would stop working: without @SpringBootTest on the base class no Spring context starts, so the injected fields stay empty.
SawAuthServerTestBase lost its @SpringBootTest line (blank at line 19) after the 18:24 green run. Every test then fails with a NullPointerException on the @Autowired 'users' field, A tests included. The blank line was committed in 5491bd5.
LearnedNull @Autowired fields in a test mean no Spring context started. The fix for contextLoads belongs in PaymentAuthorizationServerApplicationTests (extend the base, drop its @SpringBootTest), not in the base.

Switched the auth server from Postgres to MySQL

2026-10-17 · Build step 6. Real users: database + BCrypt

PredictedYes, the switch would work by changing only the datasource URL and driver; the entity and the tests would stay the same.
SawPostgres 17 (service postgresql-x64-17) was running but psql was not on PATH; MySQL Server 8.0 (service MySQL80) was also running. Both pom.xml files already had the MySQL driver. Changed application.yaml to jdbc:mysql://localhost:3306/authdb?createDatabaseIfNotExist=true with DB_USER / DB_PASSWORD env vars, and removed the Postgres driver. mvnw test: 21 run, 20 passed (all Block A on H2). Starting the server failed with 'Access denied for user root@localhost': the default root/root password was a guess. With the real root password the next start failed on 'Could not resolve placeholder KEYSTORE_PATH' (no dev profile). Started with -Dspring-boot.run.profiles=dev: :9000 answered 200 on /.well-known/openid-configuration, and MySQL now has authdb with a users table (id, email, username, password varchar(100), roles, enabled, failed_attempts, locked_until).
LearnedThe datasource is the only thing that ties the app to a database; the entity and tests stay the same. Hibernate's follow-up error 'Unable to determine Dialect' is just the failed connection in disguise: read the first 'Access denied' line, not the last one. Users in the old Postgres authdb do not move; ddl-auto=update creates an empty users table in MySQL, so register users again.

Debug C3: cache key without a separator leaks payments

2026-10-17 · Build step 10. Redis: cache payments without leaking them

Predicteda bug in payments lookup test and need to modify
SawPaymentLookupTest.noLeakBetweenSimilarNames fails (the only failure of 21 tests). cacheKey is 'payment:' + owner + paymentId, so owner nithin + 123 and owner nithin1 + 23 both give 'payment:nithin123'. The second user receives the first user's payment from the cache.
LearnedAny cache key built from two free-text parts needs a delimiter that cannot occur in either part (or a structured key such as a record). Not fixed yet: find it in the debugger first.

Waiting to run (6)