From 1678a8879705153cf0ad5cd835aabfa3fa41eeef Mon Sep 17 00:00:00 2001 From: "j.w.jonkers" Date: Mon, 11 May 2026 13:11:33 +0200 Subject: [PATCH 1/2] api: PasswordEncoder honours {noop} for OAuth client secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EncoderConfig was returning a bare BCryptPasswordEncoder. Spring Authorization Server uses whatever PasswordEncoder bean it finds in the application context to verify the client_secret presented at /oauth2/token; BCryptPasswordEncoder doesn't recognise prefix-id strings, so a registered client whose secret is stored as "{noop}" fails to match against the plain string Vault sends as Basic-auth password — SAS responds invalid_client and Vault's UI surfaces "oauth2: invalid_client". Personal-stack's auth-api uses PasswordEncoderFactories.createDelegatingPasswordEncoder() with setDefaultPasswordEncoderForMatches(BCryptPasswordEncoder()) — that's the canonical pattern that lets {noop}/{bcrypt}/etc. prefixes work for OAuth clients while still matching the project's existing raw BCrypt user-password hashes (stored without an "{id}" prefix). Mirror that here. No data migration needed: new user passwords will encode with the "{bcrypt}" prefix going forward; older prefix-less hashes still match via the fallback encoder. Fixes the regression that started failing once SAS landed in PR #129. --- .../blueshell/api/platform/config/EncoderConfig.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt b/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt index 45c3529c8..8b6cfc991 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt @@ -3,12 +3,19 @@ package net.blueshell.api.platform.config import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.factory.PasswordEncoderFactories +import org.springframework.security.crypto.password.DelegatingPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder @Configuration class EncoderConfig { @Bean - fun passwordEncoder(): PasswordEncoder { - return BCryptPasswordEncoder() - } + fun passwordEncoder(): PasswordEncoder = + (PasswordEncoderFactories.createDelegatingPasswordEncoder() as DelegatingPasswordEncoder).apply { + // User passwords are stored as raw BCrypt hashes without an "{id}" prefix, + // while OAuth client secrets registered with Spring Authorization Server + // use prefixes like "{noop}" — DelegatingPasswordEncoder + // needs both to interoperate. + setDefaultPasswordEncoderForMatches(BCryptPasswordEncoder()) + } } From 6d30a8a21854f808b017296a4f240d3a731e615c Mon Sep 17 00:00:00 2001 From: "j.w.jonkers" Date: Mon, 11 May 2026 13:13:27 +0200 Subject: [PATCH 2/2] api: shorten the EncoderConfig fallback comment to one line --- .../net/blueshell/api/platform/config/EncoderConfig.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt b/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt index 8b6cfc991..e48eab56d 100644 --- a/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt +++ b/services/api/src/main/kotlin/net/blueshell/api/platform/config/EncoderConfig.kt @@ -12,10 +12,7 @@ class EncoderConfig { @Bean fun passwordEncoder(): PasswordEncoder = (PasswordEncoderFactories.createDelegatingPasswordEncoder() as DelegatingPasswordEncoder).apply { - // User passwords are stored as raw BCrypt hashes without an "{id}" prefix, - // while OAuth client secrets registered with Spring Authorization Server - // use prefixes like "{noop}" — DelegatingPasswordEncoder - // needs both to interoperate. + // Fallback so existing prefix-less BCrypt user-password hashes keep matching. setDefaultPasswordEncoderForMatches(BCryptPasswordEncoder()) } }