Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mfa, possible fix for #140 #144

Merged
merged 5 commits into from
Apr 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion GlobalAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0-develop")]

20 changes: 20 additions & 0 deletions MegaApiClient.Tests/MegaApiClientAsyncWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public MegaApiClient.LogonSessionToken Login(string email, string password)
return this.UnwrapException(() => this.client.LoginAsync(email, password).Result);
}

public MegaApiClient.LogonSessionToken Login(string email, string password, string mfaKey)
{
return this.UnwrapException(() => this.client.LoginAsync(email, password, mfaKey).Result);
}

public MegaApiClient.LogonSessionToken Login(MegaApiClient.AuthInfos authInfos)
{
return this.UnwrapException(() => this.client.LoginAsync(authInfos).Result);
Expand Down Expand Up @@ -159,11 +164,21 @@ public MegaApiClient.AuthInfos GenerateAuthInfos(string email, string password)
return this.UnwrapException(() => this.client.GenerateAuthInfosAsync(email, password).Result);
}

public MegaApiClient.AuthInfos GenerateAuthInfos(string email, string password, string mfaKey)
{
return this.UnwrapException(() => this.client.GenerateAuthInfosAsync(email, password, mfaKey).Result);
}

public Task<MegaApiClient.LogonSessionToken> LoginAsync(string email, string password)
{
return this.client.LoginAsync(email, password);
}

public Task<MegaApiClient.LogonSessionToken> LoginAsync(string email, string password, string mfaKey)
{
return this.client.LoginAsync(email, password, mfaKey);
}

public Task<MegaApiClient.LogonSessionToken> LoginAsync(MegaApiClient.AuthInfos authInfos)
{
return this.client.LoginAsync(authInfos);
Expand Down Expand Up @@ -284,6 +299,11 @@ public Task<IEnumerable<INode>> GetNodesFromLinkAsync(Uri uri)
return this.client.GenerateAuthInfosAsync(email, password);
}

public Task<MegaApiClient.AuthInfos> GenerateAuthInfosAsync(string email, string password, string mfaKey)
{
return this.client.GenerateAuthInfosAsync(email, password, mfaKey);
}

private T UnwrapException<T>(Func<T> action)
{
try
Expand Down
8 changes: 5 additions & 3 deletions MegaApiClient/Interface/IMegaApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace CG.Web.MegaApiClient
namespace CG.Web.MegaApiClient
{
using System;
using System.Collections.Generic;
Expand All @@ -13,6 +13,8 @@ public partial interface IMegaApiClient

MegaApiClient.LogonSessionToken Login(string email, string password);

MegaApiClient.LogonSessionToken Login(string email, string password, string mfaKey);

MegaApiClient.LogonSessionToken Login(MegaApiClient.AuthInfos authInfos);

void Login(MegaApiClient.LogonSessionToken logonSessionToken);
Expand Down Expand Up @@ -59,6 +61,6 @@ public partial interface IMegaApiClient

INode Rename(INode node, string newName);

MegaApiClient.AuthInfos GenerateAuthInfos(string email, string password);
MegaApiClient.AuthInfos GenerateAuthInfos(string email, string password, string mfaKey = null);
}
}
}
4 changes: 3 additions & 1 deletion MegaApiClient/Interface/IMegaApiClientAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace CG.Web.MegaApiClient

public partial interface IMegaApiClient
{
Task<MegaApiClient.LogonSessionToken> LoginAsync(string email, string password);
Task<MegaApiClient.LogonSessionToken> LoginAsync(string email, string password, string mfaKey = null);

Task<MegaApiClient.LogonSessionToken> LoginAsync(MegaApiClient.AuthInfos authInfos);

Expand Down Expand Up @@ -58,6 +58,8 @@ public partial interface IMegaApiClient
Task<IEnumerable<INode>> GetNodesFromLinkAsync(Uri uri);

Task<MegaApiClient.AuthInfos> GenerateAuthInfosAsync(string email, string password);

Task<MegaApiClient.AuthInfos> GenerateAuthInfosAsync(string email, string password, string mfaKey);
}
}
#endif
51 changes: 48 additions & 3 deletions MegaApiClient/MegaApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ public MegaApiClient(Options options, IWebClient webClient)
/// </summary>
/// <param name="email">email</param>
/// <param name="password">password</param>
/// <param name="mfaKey"></param>
/// <returns><see cref="AuthInfos" /> object containing encrypted data</returns>
/// <exception cref="ArgumentNullException">email or password is null</exception>
public AuthInfos GenerateAuthInfos(string email, string password)
public AuthInfos GenerateAuthInfos(string email, string password, string mfaKey = null)
{
if (string.IsNullOrEmpty(email))
{
Expand Down Expand Up @@ -121,6 +122,14 @@ public AuthInfos GenerateAuthInfos(string email, string password)
}

// Derived key contains master key (0-16) and password hash (16-32)
if(!string.IsNullOrEmpty(mfaKey))
{
return new AuthInfos(
email,
derivedKeyBytes.Skip(16).ToArray().ToBase64(),
derivedKeyBytes.Take(16).ToArray(),
mfaKey);
}
return new AuthInfos(
email,
derivedKeyBytes.Skip(16).ToArray().ToBase64(),
Expand All @@ -136,7 +145,10 @@ public AuthInfos GenerateAuthInfos(string email, string password)

// Hash email and password to decrypt master key on Mega servers
string hash = GenerateHash(email.ToLowerInvariant(), passwordAesKey);

if (!string.IsNullOrEmpty(mfaKey))
{
return new AuthInfos(email, hash, passwordAesKey, mfaKey);
}
return new AuthInfos(email, hash, passwordAesKey);
}
else
Expand Down Expand Up @@ -165,6 +177,20 @@ public LogonSessionToken Login(string email, string password)
return this.Login(GenerateAuthInfos(email, password));
}

/// <summary>
/// Login to Mega.co.nz service using email/password credentials
/// </summary>
/// <param name="email">email</param>
/// <param name="password">password</param>
/// <param name="mfaKey"></param>
/// <exception cref="ApiException">Service is not available or credentials are invalid</exception>
/// <exception cref="ArgumentNullException">email or password is null</exception>
/// <exception cref="NotSupportedException">Already logged in</exception>
public LogonSessionToken Login(string email, string password, string mfaKey)
{
return this.Login(GenerateAuthInfos(email, password, mfaKey));
}

/// <summary>
/// Login to Mega.co.nz service using hashed credentials
/// </summary>
Expand All @@ -183,7 +209,15 @@ public LogonSessionToken Login(AuthInfos authInfos)
this.authenticatedLogin = true;

// Request Mega Api
LoginRequest request = new LoginRequest(authInfos.Email, authInfos.Hash);
LoginRequest request;
if (!string.IsNullOrEmpty(authInfos.MFAKey))
{
request = new LoginRequest(authInfos.Email, authInfos.Hash, authInfos.MFAKey);
}
else
{
request = new LoginRequest(authInfos.Email, authInfos.Hash);
}
LoginResponse response = this.Request<LoginResponse>(request);

// Decrypt master key using our password key
Expand Down Expand Up @@ -1214,6 +1248,14 @@ public AuthInfos(string email, string hash, byte[] passwordAesKey)
this.PasswordAesKey = passwordAesKey;
}

public AuthInfos(string email, string hash, byte[] passwordAesKey, string mfaKey)
{
this.Email = email;
this.Hash = hash;
this.PasswordAesKey = passwordAesKey;
this.MFAKey = mfaKey;
}

[JsonProperty]
public string Email { get; private set; }

Expand All @@ -1222,6 +1264,9 @@ public AuthInfos(string email, string hash, byte[] passwordAesKey)

[JsonProperty]
public byte[] PasswordAesKey { get; private set; }

[JsonProperty]
public string MFAKey { get; private set; }
}

public class LogonSessionToken : IEquatable<LogonSessionToken>
Expand Down
13 changes: 9 additions & 4 deletions MegaApiClient/MegaApiClientAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ namespace CG.Web.MegaApiClient

public partial class MegaApiClient : IMegaApiClient
{
#region Public async methods
#region Public async methods

public Task<LogonSessionToken> LoginAsync(string email, string password)
public Task<LogonSessionToken> LoginAsync(string email, string password, string mfaKey = null)
{
return Task.Run(() => this.Login(email, password));
return Task.Run(() => this.Login(email, password, mfaKey));
}

public Task<LogonSessionToken> LoginAsync(AuthInfos authInfos)
Expand Down Expand Up @@ -177,7 +177,12 @@ public Task<IEnumerable<INode>> GetNodesFromLinkAsync(Uri uri)
return Task.Run(() => this.GenerateAuthInfos(email, password));
}

#endregion
public Task<MegaApiClient.AuthInfos> GenerateAuthInfosAsync(string email, string password, string mfaKey)
{
return Task.Run(() => this.GenerateAuthInfos(email, password, mfaKey));
}

#endregion
}
}
#endif
11 changes: 11 additions & 0 deletions MegaApiClient/Serialization/Login.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ public LoginRequest(string userHandle, string passwordHash)
this.PasswordHash = passwordHash;
}

public LoginRequest(string userHandle, string passwordHash, string mfaKey)
: base("us")
{
this.UserHandle = userHandle;
this.PasswordHash = passwordHash;
this.MFAKey = mfaKey;
}

[JsonProperty("user")]
public string UserHandle { get; private set; }

[JsonProperty("uh")]
public string PasswordHash { get; private set; }

[JsonProperty("mfa")]
public string MFAKey { get; private set; }
}

internal class LoginResponse
Expand Down