001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client.camara.numberverification;
017
018import com.vonage.client.DynamicEndpoint;
019import com.vonage.client.HttpWrapper;
020import com.vonage.client.RestEndpoint;
021import com.vonage.client.VonageClient;
022import com.vonage.client.auth.camara.FrontendAuthRequest;
023import com.vonage.client.auth.camara.NetworkAuthMethod;
024import com.vonage.client.auth.camara.TokenRequest;
025import com.vonage.client.camara.CamaraResponseException;
026import com.vonage.client.camara.NetworkApiClient;
027import com.vonage.client.common.HttpMethod;
028import java.net.URI;
029import java.util.Objects;
030import java.util.UUID;
031
032/**
033 * A client for communicating with the Vonage Number Verification API. The standard way to obtain an instance
034 * of this class is to use {@link VonageClient#getNumberVerificationClient()}.
035 */
036public class NumberVerificationClient extends NetworkApiClient {
037    final RestEndpoint<VerifyNumberRequest, VerifyNumberResponse> verifyNumber;
038    private final UUID appId;
039    private VerifyNumberRequest cachedRequest;
040
041    /**
042     * Create a new NumberVerificationClient.
043     *
044     * @param wrapper Http Wrapper used to create requests.
045     */
046    @SuppressWarnings("unchecked")
047    public NumberVerificationClient(HttpWrapper wrapper) {
048        super(wrapper);
049        appId = wrapper.getApplicationId();
050
051        verifyNumber = DynamicEndpoint.<VerifyNumberRequest, VerifyNumberResponse> builder(VerifyNumberResponse.class)
052                .authMethod(NetworkAuthMethod.class).requestMethod(HttpMethod.POST)
053                .responseExceptionType(CamaraResponseException.class).pathGetter((de, req) -> {
054                    setNetworkAuth(new TokenRequest(req.redirectUrl, req.getCode()));
055                    return getCamaraBaseUri() + "number-verification/v031/verify";
056                })
057                .wrapper(wrapper).build();
058    }
059
060    /**
061     * Sets up the client for verifying the given phone number. This method will cache the provided
062     * number and redirect URL for verification when calling {@link #verifyNumber(String)}.
063     * The intended usage is to call this method first, and follow the returned URL on the target
064     * device which the phone number is supposed to be associated with. When the URL is followed,
065     * it will trigger an inbound request to the {@code redirectUrl} with two query parameters:
066     * {@code CODE} and {@code STATE} (if provided as a parameter to this method). The code should
067     * then be extracted from the query parameters and passed to {@link #verifyNumber(String)}.
068     *
069     * @param phoneNumber The MSISDN to verify.
070     * @param redirectUrl Redirect URL, as set in your Vonage application for Network APIs.
071     * @param state An optional string for identifying the request. For simplicity, this could
072     *              be set to the same value as {@code phoneNumber}, or it may be {@code null}.
073     *
074     * @return A link with appropriate parameters which should be followed on the end user's device.
075     * The link should be followed when using the SIM card associated the provided phone number.
076     * Therefore, on the target device, Wi-Fi should be disabled when doing this, otherwise the result
077     * of {@link #verifyNumber(String)} will be {@code false}.
078     */
079    public URI initiateVerification(String phoneNumber, URI redirectUrl, String state) {
080        cachedRequest = new VerifyNumberRequest(phoneNumber, redirectUrl);
081        return new FrontendAuthRequest(phoneNumber, redirectUrl, appId, state).buildOidcUrl();
082    }
083
084    /**
085     * Exchanges the code for an access token, makes the API call to the
086     * Number Verification API's "verify" endpoint and extracts the result.
087     *
088     * @param code The code obtained from the inbound callback's query parameters.
089     *
090     * @return {@code true} if the device that followed the link was using the SIM card associated
091     * with the phone number provided in {@linkplain #initiateVerification(String, URI, String)},
092     * {@code false} otherwise (e.g. it was unknown, the link was not followed, the device that followed
093     * the link didn't use the SIM card with that phone number when doing so).
094     *
095     * @throws com.vonage.client.auth.camara.NetworkAuthResponseException If there was an error
096     * exchanging the code for an access token when using the Vonage Network Auth API.
097     *
098     * @throws CamaraResponseException If there was an error in communicating with the Number Verification API.
099     */
100    public boolean verifyNumber(String code) {
101        if (cachedRequest == null) {
102            throw new IllegalStateException("You must first call initiateVerification using this client.");
103        }
104        return verifyNumber(cachedRequest.withCode(code));
105    }
106
107    /**
108     * Stateless implementation of {@link #verifyNumber(String)}, which creates a new request
109     * without having to call {@link #initiateVerification(String, URI, String)} first. This is
110     * useful in cases where concurrent verifications are required, or the URL is obtained another way.
111     *
112     * @param phoneNumber MSISDN of the target device to verify.
113     * @param redirectUri Redirect URL, as set in your Vonage application for Network APIs.
114     * @param code The code obtained from the inbound callback's query parameters.
115     *
116     * @return {@code true} if the device that followed the link was using the SIM card associated
117     * with the phone number provided in {@linkplain #initiateVerification(String, URI, String)},
118     * {@code false} otherwise (e.g. it was unknown, the link was not followed, the device that followed
119     * the link didn't use the SIM card with that phone number when doing so).
120     *
121     * @throws com.vonage.client.auth.camara.NetworkAuthResponseException If there was an error
122     * exchanging the code for an access token when using the Vonage Network Auth API.
123     *
124     * @throws CamaraResponseException If there was an error in communicating with the Number Verification API.
125     */
126    public boolean verifyNumber(String phoneNumber, URI redirectUri, String code) {
127        return verifyNumber(new VerifyNumberRequest(phoneNumber, redirectUri).withCode(code));
128    }
129
130    private boolean verifyNumber(VerifyNumberRequest request) {
131        try {
132            return verifyNumber.execute(request).getDevicePhoneNumberVerified();
133        }
134        finally {
135            cachedRequest = null;
136        }
137    }
138}