From b7709fb7e4b9a0da6dfc1f056bf942a60753d6be Mon Sep 17 00:00:00 2001 From: krestar Date: Tue, 18 Aug 2026 14:22:17 +0900 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=C2=B7=EC=88=98=EC=A0=95=EC=97=90=20RLS=20ten?= =?UTF-8?q?ant=20context=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/auth/application/AuthService.java | 2 + .../PostgreSqlRestrictedRoleHttpE2ETest.java | 37 ++++++++++++++++++- ...stgreSqlRestrictedRoleHttpEnvironment.java | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/fowoco/server/auth/application/AuthService.java b/src/main/java/com/fowoco/server/auth/application/AuthService.java index 584454fb..cef4d933 100644 --- a/src/main/java/com/fowoco/server/auth/application/AuthService.java +++ b/src/main/java/com/fowoco/server/auth/application/AuthService.java @@ -171,6 +171,7 @@ public RefreshResult refresh(String rawRefreshToken) { @Transactional(readOnly = true) public ProfileSnapshot currentProfile(UUID userId, UUID companyId) { + tenantDatabaseContext.setCompanyIdForCurrentTransaction(companyId); UserAccount account = userAccountRepository.findByUserIdAndCompanyId(userId, companyId) .orElseThrow(() -> new IllegalStateException("authenticated user account was not found")); return withLoginHistory(account); @@ -178,6 +179,7 @@ public ProfileSnapshot currentProfile(UUID userId, UUID companyId) { @Transactional public ProfileSnapshot updateProfile(UUID userId, UUID companyId, String displayName, String phone) { + tenantDatabaseContext.setCompanyIdForCurrentTransaction(companyId); UserAccount current = userAccountRepository.findByUserIdAndCompanyIdWithLock(userId, companyId) .orElseThrow(() -> new IllegalStateException("authenticated user account was not found")); UserAccount updated = current.updateProfile(displayName, phone, clock.instant()); diff --git a/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpE2ETest.java b/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpE2ETest.java index d9489444..ada15f93 100644 --- a/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpE2ETest.java +++ b/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpE2ETest.java @@ -142,7 +142,7 @@ SELECT COUNT(*) )).isFalse(); assertTablePrivileges("company", true, false, false, false); - assertTablePrivileges("user_account", true, false, false, false); + assertTablePrivileges("user_account", true, false, true, false); assertTablePrivileges("refresh_token", true, true, true, false); assertTablePrivileges("worker", true, true, true, false); assertTablePrivileges("task", true, false, false, false); @@ -311,6 +311,41 @@ void workerLinkBootstrapAcceptsOnlyActiveRegisteredLinks() throws Exception { .isEqualTo(410); } + @Test + void profileReadAndUpdateBindTheAuthenticatedTenantWithRlsEnabled() throws Exception { + String accessToken = accessToken(login( + PostgreSqlRestrictedRoleHttpDataFixture.USER_A_EMAIL, + PostgreSqlRestrictedRoleHttpDataFixture.PASSWORD + )); + + HttpResponse currentProfile = get( + "/api/v1/auth/me/profile", + accessToken + ); + + assertThat(currentProfile.statusCode()).isEqualTo(200); + assertThat(JsonPath.read(currentProfile.body(), "$.role")).isEqualTo("HR"); + assertThat(JsonPath.read(currentProfile.body(), "$.account_status")) + .isEqualTo("ACTIVE"); + + HttpResponse updatedProfile = patch( + "/api/v1/auth/me/profile", + """ + { + "display_name":"Restricted Profile A", + "phone":"010-1234-5678" + } + """, + accessToken + ); + + assertThat(updatedProfile.statusCode()).isEqualTo(200); + assertThat(JsonPath.read(updatedProfile.body(), "$.display_name")) + .isEqualTo("Restricted Profile A"); + assertThat(JsonPath.read(updatedProfile.body(), "$.phone")) + .isEqualTo("010-1234-5678"); + } + @Test void authenticatedWorkerRequestsRemainTenantIsolatedAcrossConnectionReuse() throws Exception { diff --git a/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpEnvironment.java b/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpEnvironment.java index 3dd60c97..72556d7b 100644 --- a/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpEnvironment.java +++ b/src/test/java/com/fowoco/server/common/security/PostgreSqlRestrictedRoleHttpEnvironment.java @@ -23,7 +23,7 @@ final class PostgreSqlRestrictedRoleHttpEnvironment implements AutoCloseable { private static final Map TABLE_PRIVILEGES = Map.ofEntries( Map.entry("company", "SELECT"), - Map.entry("user_account", "SELECT"), + Map.entry("user_account", "SELECT, UPDATE"), Map.entry("refresh_token", "SELECT, INSERT, UPDATE"), Map.entry("worker", "SELECT, INSERT, UPDATE"), Map.entry("worker_archive", "SELECT, INSERT"),