Skip to content

Commit

Permalink
Add mfa, possible fix for #140 (#144) [skip ci]
Browse files Browse the repository at this point in the history
* Add optional MFA token to login requests
  • Loading branch information
xbenjii authored and gpailler committed Apr 25, 2020
1 parent 72b724a commit 123eb30
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 12 deletions.
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

0 comments on commit 123eb30

Please sign in to comment.