From e3ae001ac5eec18cd753c89628c09dbd1e5b27cd Mon Sep 17 00:00:00 2001 From: simonredfern Date: Wed, 28 Jan 2026 17:00:00 +0100 Subject: [PATCH] Added POST /users/verify-credentials --- .../main/scala/code/api/util/ApiRole.scala | 3 + .../scala/code/api/v6_0_0/APIMethods600.scala | 72 ++++++++++++++++++- .../code/api/v6_0_0/JSONFactory6.0.0.scala | 6 ++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 2140d58b6..94225d7ab 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -200,6 +200,9 @@ object ApiRole extends MdcLoggable{ case class CanGetAnyUser (requiresBankId: Boolean = false) extends ApiRole lazy val canGetAnyUser = CanGetAnyUser() + case class CanVerifyUserCredentials(requiresBankId: Boolean = false) extends ApiRole + lazy val canVerifyUserCredentials = CanVerifyUserCredentials() + case class CanCreateAnyTransactionRequest(requiresBankId: Boolean = true) extends ApiRole lazy val canCreateAnyTransactionRequest = CanCreateAnyTransactionRequest() diff --git a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala index 1ffe22eef..ae57d904a 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala @@ -30,7 +30,7 @@ import code.api.v5_0_0.{ViewJsonV500, ViewsJsonV500} import code.api.v5_1_0.{JSONFactory510, PostCustomerLegalNameJsonV510} import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo} import code.api.v6_0_0.JSONFactory600.{AddUserToGroupResponseJsonV600, DynamicEntityDiagnosticsJsonV600, DynamicEntityIssueJsonV600, GroupEntitlementJsonV600, GroupEntitlementsJsonV600, GroupJsonV600, GroupsJsonV600, PostGroupJsonV600, PostGroupMembershipJsonV600, PostResetPasswordUrlJsonV600, PutGroupJsonV600, ReferenceTypeJsonV600, ReferenceTypesJsonV600, ResetPasswordUrlJsonV600, RoleWithEntitlementCountJsonV600, RolesWithEntitlementCountsJsonV600, ScannedApiVersionJsonV600, UpdateViewJsonV600, UserGroupMembershipJsonV600, UserGroupMembershipsJsonV600, ValidateUserEmailJsonV600, ValidateUserEmailResponseJsonV600, ViewJsonV600, ViewPermissionJsonV600, ViewPermissionsJsonV600, ViewsJsonV600, createAbacRuleJsonV600, createAbacRulesJsonV600, createActiveRateLimitsJsonV600, createCallLimitJsonV600, createRedisCallCountersJson} -import code.api.v6_0_0.{AbacRuleJsonV600, AbacRuleResultJsonV600, AbacRulesJsonV600, CacheConfigJsonV600, CacheInfoJsonV600, CacheNamespaceInfoJsonV600, CreateAbacRuleJsonV600, CreateDynamicEntityRequestJsonV600, CurrentConsumerJsonV600, DynamicEntityDefinitionJsonV600, DynamicEntityDefinitionWithCountJsonV600, DynamicEntitiesWithCountJsonV600, DynamicEntityLinksJsonV600, ExecuteAbacRuleJsonV600, InMemoryCacheStatusJsonV600, MyDynamicEntitiesJsonV600, RedisCacheStatusJsonV600, RelatedLinkJsonV600, UpdateAbacRuleJsonV600, UpdateDynamicEntityRequestJsonV600} +import code.api.v6_0_0.{AbacRuleJsonV600, AbacRuleResultJsonV600, AbacRulesJsonV600, CacheConfigJsonV600, CacheInfoJsonV600, CacheNamespaceInfoJsonV600, CreateAbacRuleJsonV600, CreateDynamicEntityRequestJsonV600, CurrentConsumerJsonV600, DynamicEntityDefinitionJsonV600, DynamicEntityDefinitionWithCountJsonV600, DynamicEntitiesWithCountJsonV600, DynamicEntityLinksJsonV600, ExecuteAbacRuleJsonV600, InMemoryCacheStatusJsonV600, MyDynamicEntitiesJsonV600, PostVerifyUserCredentialsJsonV600, RedisCacheStatusJsonV600, RelatedLinkJsonV600, UpdateAbacRuleJsonV600, UpdateDynamicEntityRequestJsonV600} import code.api.v6_0_0.OBPAPI6_0_0 import code.abacrule.{AbacRuleEngine, MappedAbacRuleProvider} import code.metrics.APIMetrics @@ -7084,6 +7084,76 @@ trait APIMethods600 { } } + staticResourceDocs += ResourceDoc( + verifyUserCredentials, + implementedInApiVersion, + nameOf(verifyUserCredentials), + "POST", + "/users/verify-credentials", + "Verify User Credentials", + s"""Verify a user's credentials (username, password, provider) and return user information if valid. + | + |This endpoint validates the provided credentials without creating a token or session. + |It can be used to verify user credentials in external systems. + | + |${userAuthenticationMessage(true)} + | + |""", + PostVerifyUserCredentialsJsonV600( + username = "username", + password = "password", + provider = Constant.localIdentityProvider + ), + userJsonV200, + List( + $AuthenticatedUserIsRequired, + UserHasMissingRoles, + InvalidJsonFormat, + InvalidLoginCredentials, + UsernameHasBeenLocked, + UnknownError + ), + List(apiTagUser), + Some(List(canVerifyUserCredentials)) + ) + + lazy val verifyUserCredentials: OBPEndpoint = { + case "users" :: "verify-credentials" :: Nil JsonPost json -> _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- authenticatedAccess(cc) + _ <- NewStyle.function.hasEntitlement("", u.userId, canVerifyUserCredentials, callContext) + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the PostVerifyUserCredentialsJsonV600", 400, callContext) { + json.extract[PostVerifyUserCredentialsJsonV600] + } + // Validate credentials using the existing AuthUser mechanism + resourceUserIdBox = code.model.dataAccess.AuthUser.getResourceUserId(postedData.username, postedData.password) + // Check if account is locked + _ <- Helper.booleanToFuture(UsernameHasBeenLocked, 401, callContext) { + resourceUserIdBox != Full(code.model.dataAccess.AuthUser.usernameLockedStateCode) + } + // Check if credentials are valid + resourceUserId <- Future { + resourceUserIdBox + } map { + x => unboxFullOrFail(x, callContext, InvalidLoginCredentials, 401) + } + // Get the user object + user <- Future { + Users.users.vend.getUserByResourceUserId(resourceUserId) + } map { + x => unboxFullOrFail(x, callContext, InvalidLoginCredentials, 401) + } + // Verify provider matches if specified and not empty + _ <- Helper.booleanToFuture(InvalidLoginCredentials, 401, callContext) { + postedData.provider.isEmpty || user.provider == postedData.provider + } + } yield { + (JSONFactory200.createUserJSON(user), HttpCode.`200`(callContext)) + } + } + } + } } diff --git a/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala b/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala index 97ac5265d..863fa4651 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala @@ -206,6 +206,12 @@ case class CreateUserJsonV600( validating_application: Option[String] = None ) +case class PostVerifyUserCredentialsJsonV600( + username: String, + password: String, + provider: String +) + case class MigrationScriptLogJsonV600( migration_script_log_id: String, name: String,