From 43cca7841171c04ac284ff0d63524a1a42473cae Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Tue, 30 Jul 2024 10:50:15 +0100 Subject: [PATCH] feat: Add Number Verification API --- CHANGELOG.md | 1 + README.md | 1 + .../vonage/client/kt/NumberVerification.kt | 33 +++++++ .../kotlin/com/vonage/client/kt/Vonage.kt | 1 + .../client/kt/NumberVerificationTest.kt | 94 +++++++++++++++++++ .../com/vonage/client/kt/SimSwapTest.kt | 2 +- 6 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/vonage/client/kt/NumberVerification.kt create mode 100644 src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 90237ab..e64a034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - SIM Swap API +- Number Verification API ## [0.5.0] - 2024-07-25 diff --git a/README.md b/README.md index 247165d..a3f884e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ You'll need to have [created a Vonage account](https://dashboard.nexmo.com/sign- - [Verify](https://developer.vonage.com/en/verify/overview) - [Voice](https://developer.vonage.com/en/voice/voice-api/overview) - [SIM Swap](https://developer.vonage.com/en/sim-swap/overview) +- [Number Verification](https://developer.vonage.com/en/number-verification/overview) - [Number Insight](https://developer.vonage.com/en/number-insight/overview) - [SMS](https://developer.vonage.com/en/messaging/sms/overview) - [Conversion](https://developer.vonage.com/en/messaging/conversion-api/overview) diff --git a/src/main/kotlin/com/vonage/client/kt/NumberVerification.kt b/src/main/kotlin/com/vonage/client/kt/NumberVerification.kt new file mode 100644 index 0000000..80e51d3 --- /dev/null +++ b/src/main/kotlin/com/vonage/client/kt/NumberVerification.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Vonage + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.vonage.client.kt + +import com.vonage.client.camara.numberverification.* +import java.net.URI + +class NumberVerification(private val nvClient: NumberVerificationClient) { + private var redirectUri: URI? = null + + fun createVerificationUrl(phoneNumber: String, redirectUrl: String, state: String? = null): URI { + redirectUri = URI.create(redirectUrl) + return nvClient.initiateVerification(phoneNumber, redirectUri, state) + } + + fun verifyNumberWithCode(phoneNumber: String, code: String, redirectUrl: String? = null): Boolean { + if (redirectUrl != null) redirectUri = URI.create(redirectUrl) + return nvClient.verifyNumber(phoneNumber, redirectUri, code) + } +} diff --git a/src/main/kotlin/com/vonage/client/kt/Vonage.kt b/src/main/kotlin/com/vonage/client/kt/Vonage.kt index 872fdd7..9688632 100644 --- a/src/main/kotlin/com/vonage/client/kt/Vonage.kt +++ b/src/main/kotlin/com/vonage/client/kt/Vonage.kt @@ -29,6 +29,7 @@ class Vonage(init: VonageClient.Builder.() -> Unit) { val verifyLegacy = VerifyLegacy(vonageClient.verifyClient) val numberInsight = NumberInsight(vonageClient.insightClient) val simSwap = SimSwap(vonageClient.simSwapClient) + val numberVerification = NumberVerification(vonageClient.numberVerificationClient) } fun VonageClient.Builder.authFromEnv(): VonageClient.Builder { diff --git a/src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt b/src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt new file mode 100644 index 0000000..d2e439e --- /dev/null +++ b/src/test/kotlin/com/vonage/client/kt/NumberVerificationTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2024 Vonage + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.vonage.client.kt + +import java.net.URI +import java.net.URLEncoder +import kotlin.test.* + +class NumberVerificationTest : AbstractTest() { + private val nvClient = vonage.numberVerification + private val nvCheckUrl = "/camara/number-verification/v031/verify" + private val clientAuthUrl = "https://oidc.idp.vonage.com/oauth2/auth" + private val redirectUrl = "$exampleUrlBase/nv/redirect" + private val code = "65536" + private val state = "nv-$testUuidStr" + + private fun assertVerifyNumber(invocation: NumberVerification.() -> Boolean) { + mockPostQueryParams( + expectedUrl = "/oauth2/token", + authType = AuthType.JWT, + expectedRequestParams = mapOf( + "grant_type" to "authorization_code", + "code" to code, + "redirect_uri" to redirectUrl + ), + expectedResponseParams = mapOf( + "access_token" to accessToken, + "token_type" to "Bearer", + "expires" to 5400 + ) + ) + for (result in listOf(true, false, null)) { + mockPost( + expectedUrl = nvCheckUrl, + authType = AuthType.ACCESS_TOKEN, + expectedRequestParams = mapOf("phoneNumber" to "+$toNumber"), + expectedResponseParams = if (result != null) + mapOf("devicePhoneNumberVerified" to result) else mapOf() + ) + assertEquals(result ?: false, invocation.invoke(nvClient)) + } + } + + @Test + fun `create verification url with and without state`() { + val expectedUrlStr = "$clientAuthUrl?login_hint=tel%3A%2B$toNumber&scope="+ + "openid+dpv%3AFraudPreventionAndDetection%23number-verification-verify-read" + + "&client_id=$applicationId&redirect_uri=${ + URLEncoder.encode(redirectUrl, "UTF-8") + }&response_type=code" + + val expectedUrlWithoutState = URI.create("$expectedUrlStr&state=null") + assertEquals(expectedUrlWithoutState, nvClient.createVerificationUrl(toNumber, redirectUrl)) + + val expectedUrlWithState = URI.create("$expectedUrlStr&state=$state") + assertEquals(expectedUrlWithState, nvClient.createVerificationUrl(toNumber, redirectUrl, state)) + } + + @Test + fun `verify number all parameters`() { + assertVerifyNumber { + verifyNumberWithCode(toNumber, code, redirectUrl) + } + } + + @Test + fun `verify number with redirect url`() { + assertVerifyNumber { + verifyNumberWithCode(toNumber, code, redirectUrl) + } + } + + @Test + fun `verify number relying on cached redirect url`() { + nvClient.createVerificationUrl(altNumber, redirectUrl) + + assertVerifyNumber { + verifyNumberWithCode(toNumber, code) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt b/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt index 729d7ac..6029618 100644 --- a/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/SimSwapTest.kt @@ -53,7 +53,7 @@ class SimSwapTest : AbstractTest() { "refresh_token" to "xyz789012ghi", "token_type" to "Bearer", "expires" to 3600 - ), + ) ) }