Skip to content

Commit

Permalink
Добавлена двухфакторная авторизация (#12)
Browse files Browse the repository at this point in the history
* Add support netstandard2.0

* Add support netstandard2.0

* Fix build

* Fix build

* Fix build

* Update auth

* Fix SplitByCapitalLetter

* Fix build

* Fix login by User / password

* Login improvements

* Added 2F auth

* Fix summary

* Rollback changes

* Fix PR comments

* Added missing file

* refactoring
  • Loading branch information
martin211 committed Jan 8, 2023
1 parent d318259 commit 996395b
Show file tree
Hide file tree
Showing 31 changed files with 895 additions and 134 deletions.
215 changes: 210 additions & 5 deletions src/Yandex.Music.Api/API/YUserAPI.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Net.Http;
using System.Security.Authentication;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

using Yandex.Music.Api.Common;
Expand All @@ -12,7 +15,7 @@ public class YUserAPI : YCommonAPI
{
#region Вспомогательные функции

private Task<YAuth> AuthPassport(AuthStorage storage, string login, string password)
private Task<YAccessToken> AuthPassport(AuthStorage storage, string login, string password)
{
return new YAuthorizeBuilder(api, storage)
.Build((login, password))
Expand All @@ -23,10 +26,10 @@ private Task<YAuth> AuthPassport(AuthStorage storage, string login, string passw

#region Основные функции

public YUserAPI(YandexMusicApi yandex): base(yandex)
public YUserAPI(YandexMusicApi yandex) : base(yandex)
{
}
}

/// <summary>
/// Авторизация
/// </summary>
Expand Down Expand Up @@ -75,8 +78,10 @@ public void Authorize(AuthStorage storage, string token)
/// <returns></returns>
public async Task AuthorizeAsync(AuthStorage storage, string login, string password)
{
YAuth result = await AuthPassport(storage, login, password);
YAccessToken result = await AuthPassport(storage, login, password);

storage.AccessToken = result;

await AuthorizeAsync(storage, result.AccessToken);
}

Expand Down Expand Up @@ -114,6 +119,206 @@ public YResponse<YAccountResult> GetUserAuth(AuthStorage storage)
return GetUserAuthAsync(storage).GetAwaiter().GetResult();
}

/// <summary>
/// Создает севнс входа и возвращает доступные методы авторизации
/// </summary>
public async Task<YAuthTypes> LoginUserAsync(AuthStorage storage, string userName)
{
if (!await GetCsrfTokenAsync(storage))
{
throw new Exception("Не возможно инициализировать сессию входа.");
}

var response = await new YAuthLoginUserBuilder(api, storage)
.Build((storage.AuthToken.CsfrToken, userName))
.GetResponseAsync();

storage.AuthToken.TrackId = response.TrackId;

return response;
}

/// <summary>
/// Получить ссылку на QR код
/// </summary>
public async Task<string> GetAuthQRLinkAsync(AuthStorage storage)
{
if (!await GetCsrfTokenAsync(storage))
{
throw new Exception("Не возможно инициализировать сессию входа.");
}

var response = await new YAuthQRBuilder(api, storage)
.Build(null)
.GetResponseAsync();

if (response.Status.Equals("ok"))
{
storage.AuthToken = new YAuthToken
{
TrackId = response.TrackId,
CsfrToken = response.CsrfToken
};

return $"https://passport.yandex.ru/auth/magic/code/?track_id={response.TrackId}";
}

return string.Empty;
}

/// <summary>
/// Выполнить вход после подтверждения QR кода.
/// </summary>
public async Task<bool> LoginQRAsync(AuthStorage storage)
{
if (storage.AuthToken == null)
{
throw new Exception("Не выполнен запрос на авторизацию по QR.");
}

using var response = await new YAuthLoginQRBuilder(api, storage)
.Build(null)
.GetResponseAsync();

var responseData = await storage.Provider.GetDataFromResponseAsync<YAuthQRStatus>(api, response);

if (responseData == null || !responseData.Status.Equals("ok"))
{
throw new Exception("Не выполнен запрос на авторизация по QR.");
}

await LoginByCookiesAsync(storage);

return true;
}

/// <summary>
/// Получить <see cref="YAuthCaptcha"/>
/// </summary>
public Task<YAuthCaptcha> GetCaptchaAsync(AuthStorage storage)
{
if (storage.AuthToken == null ||
string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
{
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(LoginUserAsync)} перед использованием");
}

return new Requests.Account.YAuthCaptchaBuilder(api, storage)
.Build(null)
.GetResponseAsync();
}

/// <summary>
/// Выполнить вход после получения captcha
/// </summary>
public Task<YAuthBase> LoginByCaptchaAsync(AuthStorage storage, string captchaValue)
{
if (storage.AuthToken == null ||
string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
{
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(LoginUserAsync)} перед использованием");
}

return new YAuthLoginCaptchaBuilder(api, storage)
.Build(captchaValue)
.GetResponseAsync();
}

/// <summary>
/// Получить письмо авторизации, на почту пользователя
/// </summary>
public Task<YAuthLetter> GetAuthLetterAsync(AuthStorage storage)
{
return new YAuthLetterBuilder(api, storage)
.Build(null)
.GetResponseAsync();
}

/// <summary>
/// Авторизоваться после подтвеждения входа через письмо
/// </summary>
public async Task<YAccessToken> AuthLetterAsync(AuthStorage storage)
{
var status = await new YAuthLoginLetterBuilder(api, storage)
.Build(null)
.GetResponseAsync();

if (status.Status.Equals("ok") && !status.MagicLinkConfirmed)
{
throw new Exception("Не подтвержден вход посредством mail.");
}

return await LoginByCookiesAsync(storage);
}

/// <summary>
/// Войти с помощью пароля из yandex прложения
/// </summary>
public async Task<YAccessToken> AuthAppPassword(AuthStorage storage, string password)
{
if (storage.AuthToken == null || string.IsNullOrWhiteSpace(storage.AuthToken.CsfrToken))
{
throw new AuthenticationException($"Не найдена сессия входа. Выполните {nameof(LoginUserAsync)} перед использованием");
}

var response = await new YAuthAppPasswordBuilder(api, storage)
.Build(password)
.GetResponseAsync();

if (!response.Status.Equals("ok") || !string.IsNullOrWhiteSpace(response.RedirectUrl))
{
throw new Exception("Ошибка авторизации.");
}

return await LoginByCookiesAsync(storage);
}

private async Task<YAccessToken> LoginByCookiesAsync(AuthStorage storage)
{
if (storage.AuthToken == null)
{
throw new Exception("Не возможно инициализировать сессию входа.");
}

var auth = await new YAuthCookiesBuilder(api, storage)
.Build(null)
.GetResponseAsync();

storage.IsAuthorized = !string.IsNullOrEmpty(auth.AccessToken);

storage.AccessToken = auth;
storage.Token = auth.AccessToken;

return auth;
}

private async Task<bool> GetCsrfTokenAsync(AuthStorage storage)
{
using var authMethodsResponse = await new YGetAuthMethodsBuilder(api, storage)
.Build(null)
.GetResponseAsync();

if (!authMethodsResponse.IsSuccessStatusCode)
{
throw new HttpRequestException("Не возможно получить CFRF токен.");
}

var responseString = await authMethodsResponse.Content.ReadAsStringAsync();
var match = Regex.Match(responseString, "\"csrf_token\" value=\"([^\"]+)\"");

if (!match.Success || match.Groups.Count < 2)
{
return false;
}

storage.AuthToken = new YAuthToken
{
CsfrToken = match.Groups[1].Value
};

return true;
}

#endregion Основные функции
}
}
9 changes: 8 additions & 1 deletion src/Yandex.Music.Api/Common/AuthStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ public class AuthStorage
/// <summary>
/// Провайдер запросов
/// </summary>
public IRequestProvider Provider { get; }
public IRequestProvider Provider { get; }

/// <summary>
/// Gets or sets the access token.
/// </summary>
public YAccessToken AccessToken { get; set; }

internal YAuthToken AuthToken { get; set; }

#endregion

Expand Down

0 comments on commit 996395b

Please sign in to comment.