/*
* Copyright (c) 2023 Proton AG
*
* This file is part of ProtonVPN.
*
* ProtonVPN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonVPN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonVPN. If not, see .
*/
using System.Collections.Specialized;
using System.Web;
using ProtonVPN.Api.Contracts;
using ProtonVPN.Api.Contracts.Auth;
using ProtonVPN.Client.Logic.Auth.Contracts;
using ProtonVPN.Client.Logic.Auth.Contracts.Models;
using ProtonVPN.Common.Core.Extensions;
using ProtonVPN.Configurations.Contracts;
using ProtonVPN.Logging.Contracts;
using ProtonVPN.Logging.Contracts.Events.AppLogs;
using ProtonVPN.StatisticalEvents.Contracts;
namespace ProtonVPN.Client.Logic.Auth;
public class WebAuthenticator : IWebAuthenticator
{
private const string UPGRADE_INTENT = "upgrade";
private const string MANAGE_SUBSCRIPTION_INTENT = "manage-subscription";
private const string SUBSCRIBE_ACCOUNT_ACTION = "subscribe-account";
private const string REFRESH_ACCOUNT_REDIRECT = "refresh-account";
private readonly IApiClient _apiClient;
private readonly IConfiguration _config;
private readonly ILogger _logger;
protected string ActivationProtocol { get; }
public WebAuthenticator(IApiClient apiClient, IConfiguration config, ILogger logger)
{
_apiClient = apiClient;
_config = config;
_logger = logger;
ActivationProtocol = $"{_config.ProtocolActivationScheme}://";
}
public Task GetMyAccountUrlAsync()
{
return Task.FromResult(_config.Urls.AccountUrl);
}
public async Task GetUpgradeAccountUrlAsync(ModalSource? modalSource, string? notificationReference = null)
{
string redirectUrl = GetRedirectUrl(modalSource, notificationReference);
AuthUrlParameters parameters = new()
{
Action = SUBSCRIBE_ACCOUNT_ACTION,
Fullscreen = "off",
Redirect = redirectUrl,
Start = "compare",
Type = UPGRADE_INTENT,
};
string url = await GetAuthUrlAsync(parameters);
return url;
}
public async Task GetAuthUrlAsync(AuthUrlParameters parameters)
{
string selector = await GetSelectorAsync();
return selector.IsNullOrEmpty()
? _config.Urls.AccountUrl
: GetAutoLoginUrl(parameters, selector);
}
public async Task GetAuthUrlAsync(string url, ModalSource? modalSource, string notificationReference)
{
Uri uri = new(url);
NameValueCollection uriQuery = HttpUtility.ParseQueryString(uri.Query);
if (uriQuery.AllKeys.Contains("redirect") && uriQuery["redirect"] is not null)
{
string separator = uriQuery["redirect"]?.Contains('?') ?? false ? "&" : "?";
if (modalSource.HasValue)
{
uriQuery["redirect"] += $"{separator}modal-source={modalSource}";
separator = "&";
}
if (!string.IsNullOrEmpty(notificationReference))
{
uriQuery["redirect"] += $"{separator}notification-reference={notificationReference}";
}
}
url = $"{uri.GetLeftPart(UriPartial.Path)}?{uriQuery}";
string selector = await GetSelectorAsync();
return selector.IsNullOrEmpty()
? _config.Urls.AccountUrl
: url + $"#selector={selector}";
}
private string GetRedirectUrl(ModalSource? modalSource, string? notificationReference)
{
string url = GetRedirectUrl();
string separator = "?";
if (modalSource.HasValue)
{
url += $"{separator}modal-source={modalSource}";
separator = "&";
}
if (!string.IsNullOrWhiteSpace(notificationReference))
{
url += $"{separator}notification-reference={notificationReference}";
}
return url;
}
private string GetRedirectUrl()
{
return REFRESH_ACCOUNT_REDIRECT;
}
private string GetAutoLoginUrl(AuthUrlParameters parameters, string selector)
{
return _config.Urls.AutoLoginBaseUrl
+ $"?action={parameters.Action}"
+ $"&fullscreen={parameters.Fullscreen}"
+ $"&redirect={ActivationProtocol + parameters.Redirect}"
+ $"&start={parameters.Start}"
+ $"&type={parameters.Type}"
+ $"&app=vpn"
+ $"#selector={selector}";
}
private async Task GetSelectorAsync()
{
try
{
AuthForkSessionRequest request = new()
{
ChildClientId = "web-account-lite",
Independent = 0,
};
ApiResponseResult result = await _apiClient.ForkAuthSessionAsync(request);
if (result.Success)
{
return result.Value.Selector;
}
}
catch (Exception e)
{
_logger.Error("Failed to update auto login selector.", e);
}
return string.Empty;
}
}