Skip to content

Commit

Permalink
v1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
firesharkstudios committed Nov 6, 2018
1 parent 41daf6d commit 5a5a800
Show file tree
Hide file tree
Showing 212 changed files with 7,459 additions and 1,997 deletions.
12 changes: 6 additions & 6 deletions Butterfly.Aws/Butterfly.Aws.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<Authors>Kent Johnson</Authors>
<Copyright>Copyright 2017-2018 Fireshark Studios, LLC</Copyright>
<Description>Implementation of Butterfly.Core.Notify.INotifyMessageSender for AWS SES</Description>
<Version>1.0.5</Version>
<Version>1.1.0</Version>
<PackageReleaseNotes>Various improvements and bug fixes</PackageReleaseNotes>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
</PropertyGroup>
Expand All @@ -22,11 +22,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="3.3.28.1" />
<PackageReference Include="AWSSDK.S3" Version="3.3.26.1" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.7.1" />
<PackageReference Include="AWSSDK.SimpleNotificationService" Version="3.3.2.1" />
<PackageReference Include="MimeKitLite" Version="2.0.6" />
<PackageReference Include="AWSSDK.Core" Version="3.3.29.3" />
<PackageReference Include="AWSSDK.S3" Version="3.3.26.5" />
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.7.5" />
<PackageReference Include="AWSSDK.SimpleNotificationService" Version="3.3.2.5" />
<PackageReference Include="MimeKitLite" Version="2.0.7" />
<PackageReference Include="NLog" Version="4.5.10" />
</ItemGroup>

Expand Down
1 change: 1 addition & 0 deletions Butterfly.Client.Web/lib/butterfly-client.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Butterfly.Client.Web/lib/butterfly-client.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Butterfly.Client.Web/lib/butterfly-client.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Butterfly.Client.Web/lib/butterfly-client.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Butterfly.Client.Web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Butterfly.Client.Web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "butterfly-client",
"version": "2.0.4",
"version": "2.0.5",
"description": "Client library for Butterfly Realtime Web App Server",
"main": "lib/butterfly-client.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions Butterfly.Client.Web/src/web-socket-channel-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export default class {
this._subscribing();
}
else if (message.messageType === 'UNAUTHENTICATED') {
if (this._options.onUnauthenticated) this._options.onUnauthenticated(message.data);
this.disconnect();
}
}
Expand Down
4 changes: 2 additions & 2 deletions Butterfly.Core.Test/AuthTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ public async Task SimpleAuthTest() {
logger.Debug($"onForgotPassword():user={user}");
return Task.FromResult(0);
});
AuthToken registerAuthToken = await authManager.RegisterAsync(new {
UserRefToken registerAuthToken = await authManager.RegisterAsync(new {
username = "johnsmith",
first_name = "John",
last_name = "Smith",
email = "[email protected]",
password = "test123"
});
AuthToken authenticateAuthToken = await authManager.AuthenticateAsync(registerAuthToken.id);
AuthToken authToken = await authManager.AuthenticateAsync(UserRefTokenAuthenticator.AUTH_TYPE, registerAuthToken.id);

await authManager.ForgotPasswordAsync("johnsmith");

Expand Down
4 changes: 2 additions & 2 deletions Butterfly.Core.Test/NotifyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ public static async Task SendEmailNotifyMessage(INotifyMessageSender notifyMessa
}

[TestMethod]
public static async Task SendPhoneTextNotifyMessage(INotifyMessageSender notifyMessageSender) {
public static async Task SendPhoneNotifyMessage(INotifyMessageSender notifyMessageSender) {
IDatabase database = new Butterfly.Core.Database.Memory.MemoryDatabase();
await database.CreateFromResourceFileAsync(Assembly.GetExecutingAssembly(), "Butterfly.Notify.Test.db.sql");
database.SetDefaultValue("id", tableName => Guid.NewGuid().ToString());
database.SetDefaultValue("created_at", tableName => DateTime.Now);

var notifyMessageManager = new NotifyManager(database, phoneTextNotifyMessageSender: notifyMessageSender);
var notifyMessageManager = new NotifyManager(database, phoneNotifyMessageSender: notifyMessageSender);
notifyMessageManager.Start();
var notifyMessage = new NotifyMessage("+1 316 712 7412", "+1 316 555 1212", null, "Just testing", null);
using (ITransaction transaction = await database.BeginTransactionAsync()) {
Expand Down
139 changes: 60 additions & 79 deletions Butterfly.Core/Auth/AuthManager.cs

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions Butterfly.Core/Auth/IAuthenticator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Butterfly.Core.Auth {
public interface IAuthenticator {
Task<AuthToken> AuthenticateAsync(string authType, string authValue);
}
}
65 changes: 65 additions & 0 deletions Butterfly.Core/Auth/ShareCodeAuthenticator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Threading.Tasks;

using NLog;

using Butterfly.Core.Database;
using Butterfly.Core.Util;

using Dict = System.Collections.Generic.Dictionary<string, object>;

namespace Butterfly.Core.Auth {
public class ShareCodeAuthenticator : IAuthenticator {
protected static readonly Logger logger = LogManager.GetCurrentClassLogger();

public const string AUTH_TYPE = "Share-Code";

protected readonly IDatabase database;

protected readonly string accountTableName;
protected readonly string accountTableIdFieldName;
protected readonly string accountTableShareCodeFieldName;

/// <summary>
///
/// </summary>
/// <param name="database"></param>
/// <param name="accountTableName">Table name of the auth token table (default is "auth_token")</param>
/// <param name="accountTableIdFieldName">Field name of the id field on the auth token table (default is "id")</param>
/// <param name="accountTableShareCodeFieldName">Field name of the user id field on the auth token table (default is "user_id")</param>
public ShareCodeAuthenticator(
IDatabase database,
string accountTableName = "account",
string accountTableIdFieldName = "id",
string accountTableShareCodeFieldName = "share_code"
) {
this.database = database;
this.accountTableName = accountTableName;
this.accountTableIdFieldName = accountTableIdFieldName;
this.accountTableShareCodeFieldName = accountTableShareCodeFieldName;
}

/// <summary>
/// Validates the auth token id returning an <see cref="AuthToken"/> instance
/// </summary>
/// <param name="authType"></param>
/// <param name="authValue"></param>
/// <returns>An <see cref="AuthToken"/> instance</returns>
public async Task<AuthToken> AuthenticateAsync(string authType, string authValue) {
string accountId = await this.database.SelectValueAsync<string>($"SELECT {this.accountTableIdFieldName} FROM {this.accountTableName} WHERE {this.accountTableShareCodeFieldName}=@shareCode", new {
shareCode = authValue
});
logger.Debug($"Authenticate():accountId={accountId}");
if (string.IsNullOrEmpty(accountId)) throw new UnauthorizedException();
return new ShareCodeToken(accountId, null);
}
}

/// <summary>
/// Represents the result of a successful <see cref="AuthManager.LoginAsync(Dict)"/> or <see cref="AuthManager.RegisterAsync(dynamic, Dict)"/>
/// </summary>
public class ShareCodeToken : AuthToken {
public ShareCodeToken(string accountId, string role) : base(ShareCodeAuthenticator.AUTH_TYPE, accountId, role) {
}
}

}
126 changes: 126 additions & 0 deletions Butterfly.Core/Auth/UserRefTokenAuthenticator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using NLog;

using Butterfly.Core.Database;
using Butterfly.Core.Util;

using Dict = System.Collections.Generic.Dictionary<string, object>;

namespace Butterfly.Core.Auth {
public class UserRefTokenAuthenticator : IAuthenticator {
protected static readonly Logger logger = LogManager.GetCurrentClassLogger();

public const string AUTH_TYPE = "User-Ref-Token";

protected readonly IDatabase database;
protected readonly string authTokenTableName;
protected readonly string authTokenIdFieldName;
protected readonly string authTokenTableUserIdFieldName;
protected readonly string authTokenTableExpiresAtFieldName;

protected readonly string userTableName;
protected readonly string userTableUsernameFieldName;
protected readonly string userTableAccountIdFieldName;
protected readonly string userTableRoleFieldName;

/// <summary>
///
/// </summary>
/// <param name="database"></param>
/// <param name="authTokenTableName">Table name of the auth token table (default is "auth_token")</param>
/// <param name="authTokenIdFieldName">Field name of the id field on the auth token table (default is "id")</param>
/// <param name="authTokenTableUserIdFieldName">Field name of the user id field on the auth token table (default is "user_id")</param>
/// <param name="authTokenTableExpiresAtFieldName">Field name of the expires at field on the auth token table (default is "expires_at")</param>
/// <param name="userTableName">Table name of the user table (default is "user")</param>
/// <param name="userTableUsernameFieldName">Field name of the username field on the user table (default is "username")</param>
/// <param name="userTableAccountIdFieldName">Field name of the account id field on the user table (default is "account_id")</param>
/// <param name="userTableRoleFieldName">Field name of the role field on the user table (default is "role")</param>
public UserRefTokenAuthenticator(
IDatabase database,
string authTokenTableName = "auth_token",
string authTokenIdFieldName = "id",
string authTokenTableUserIdFieldName = "user_id",
string authTokenTableExpiresAtFieldName = "expires_at",
string userTableName = "user",
string userTableUsernameFieldName = "username",
string userTableAccountIdFieldName = "account_id",
string userTableRoleFieldName = "role"
) {
this.database = database;
this.authTokenTableName = authTokenTableName;
this.authTokenIdFieldName = authTokenIdFieldName;
this.authTokenTableUserIdFieldName = authTokenTableUserIdFieldName;
this.authTokenTableExpiresAtFieldName = authTokenTableExpiresAtFieldName;

this.userTableName = userTableName;
this.userTableUsernameFieldName = userTableUsernameFieldName;
this.userTableAccountIdFieldName = userTableAccountIdFieldName;
this.userTableRoleFieldName = userTableRoleFieldName;
}

/// <summary>
/// Validates the auth token id returning an <see cref="AuthToken"/> instance
/// </summary>
/// <param name="authType"></param>
/// <param name="authValue"></param>
/// <returns>An <see cref="AuthToken"/> instance</returns>
public async Task<AuthToken> AuthenticateAsync(string authType, string authValue) {
List<string> fieldList = new List<string>();
if (!string.IsNullOrEmpty(this.authTokenIdFieldName)) fieldList.Add($"at.{ this.authTokenIdFieldName}");
if (!string.IsNullOrEmpty(this.authTokenTableUserIdFieldName)) fieldList.Add($"at.{ this.authTokenTableUserIdFieldName}");
if (!string.IsNullOrEmpty(this.userTableAccountIdFieldName)) fieldList.Add($"u.{ this.userTableAccountIdFieldName}");
if (!string.IsNullOrEmpty(this.userTableUsernameFieldName)) fieldList.Add($"u.{ this.userTableUsernameFieldName}");
if (!string.IsNullOrEmpty(this.userTableRoleFieldName)) fieldList.Add($"u.{ this.userTableRoleFieldName}");
if (!string.IsNullOrEmpty(this.authTokenTableExpiresAtFieldName)) fieldList.Add($"at.{ this.authTokenTableExpiresAtFieldName}");
Dict authTokenDict = await this.database.SelectRowAsync($"SELECT {string.Join(",", fieldList)} FROM {this.authTokenTableName} at INNER JOIN {this.userTableName} u ON at.user_id=u.id WHERE at.id=@authTokenId", new {
authTokenId = authValue
});
logger.Debug($"Authenticate():authTokenDict={authTokenDict}");
if (authTokenDict == null) throw new UnauthorizedException();

var authToken = UserRefToken.FromDict(authTokenDict, this.authTokenIdFieldName, this.authTokenTableUserIdFieldName, this.userTableUsernameFieldName, this.userTableRoleFieldName, this.userTableAccountIdFieldName, this.authTokenTableExpiresAtFieldName);
logger.Debug($"Authenticate():authToken.expiresAt={authToken.expiresAt}");
if (authToken.expiresAt == DateTime.MinValue || authToken.expiresAt < DateTime.Now) throw new UnauthorizedException();

return authToken;
}

public Task<string> InsertAsync(ITransaction transaction, string userId, DateTime expiresAt) {
return transaction.InsertAsync<string>(this.authTokenTableName, new Dict {
{ this.authTokenTableUserIdFieldName, userId },
{ this.authTokenTableExpiresAtFieldName, expiresAt },
});
}
}

/// <summary>
/// Represents the result of a successful <see cref="AuthManager.LoginAsync(Dict)"/> or <see cref="AuthManager.RegisterAsync(dynamic, Dict)"/>
/// </summary>
public class UserRefToken : AuthToken {
public readonly string id;
public readonly string userId;
public readonly string username;
public readonly DateTime expiresAt;

public UserRefToken(string id, string userId, string username, string role, string accountId, DateTime expiresAt) : base(UserRefTokenAuthenticator.AUTH_TYPE, accountId, role) {
this.id = id;
this.userId = userId;
this.username = username;
this.expiresAt = expiresAt;
}

public static UserRefToken FromDict(Dict dict, string idFieldName, string userIdFieldName, string usernameFieldName, string roleFieldName, string accountIdFieldName, string expiresAtFieldName) {
string id = string.IsNullOrEmpty(idFieldName) ? null : dict.GetAs(idFieldName, (string)null);
string userId = string.IsNullOrEmpty(userIdFieldName) ? null : dict.GetAs(userIdFieldName, (string)null);
string username = string.IsNullOrEmpty(usernameFieldName) ? null : dict.GetAs(usernameFieldName, (string)null);
string role = string.IsNullOrEmpty(roleFieldName) ? null : dict.GetAs(roleFieldName, (string)null);
string accountId = string.IsNullOrEmpty(accountIdFieldName) ? null : dict.GetAs(accountIdFieldName, (string)null);
DateTime expiresAt = string.IsNullOrEmpty(expiresAtFieldName) ? DateTime.MaxValue : dict.GetAs(expiresAtFieldName, DateTime.MinValue);
return new UserRefToken(id, userId, username, role, accountId, expiresAt);
}
}

}
2 changes: 1 addition & 1 deletion Butterfly.Core/Butterfly.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Company>Fireshark Studios, LLC</Company>
<Product>Butterfly Server .NET</Product>
<Copyright>Copyright 2017-2018 Fireshark Studios, LLC</Copyright>
<Version>1.0.5</Version>
<Version>1.1.0</Version>
<PackageReleaseNotes>Various improvements and bug fixes</PackageReleaseNotes>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(MSBuildProjectName).xml</DocumentationFile>
</PropertyGroup>
Expand Down
19 changes: 12 additions & 7 deletions Butterfly.Core/Channel/BaseChannelConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,20 @@ internal void Heartbeat() {
/// <param name="channelKey">The value to be sent to the client (will be converted to JSON)</param>
/// <param name="messageType">The value to be sent to the client (will be converted to JSON)</param>
/// <param name="data">The value to be sent to the client (will be converted to JSON)</param>
public void QueueMessage(string channelKey = null, string messageType = null, object data = null) {
public void QueueMessage(string channelKey = null, string messageType = null, object data = null, bool immediate = false) {
Dict payload = new Dict();
if (!string.IsNullOrEmpty(channelKey)) payload["channelKey"] = channelKey;
if (!string.IsNullOrEmpty(messageType)) payload["messageType"] = messageType;
if (data!=null) payload["data"] = data;

string text = JsonUtil.Serialize(payload);
this.buffer.Enqueue(text);
this.monitor.PulseAll();
if (immediate) {
this.SendAsync(text);
}
else {
this.buffer.Enqueue(text);
this.monitor.PulseAll();
}
}

protected bool started = false;
Expand Down Expand Up @@ -133,10 +138,10 @@ public async Task ReceiveMessageAsync(string text) {
try {
var authenticationHeaderValue = string.IsNullOrWhiteSpace(value) ? null : AuthenticationHeaderValue.Parse(value);
await this.subscriptionApi.AuthenticateAsync(authenticationHeaderValue?.Scheme, authenticationHeaderValue?.Parameter, this);
this.QueueMessage(messageType: "AUTHENTICATED");
this.QueueMessage(messageType: "AUTHENTICATED", immediate: true);
}
catch (Exception e) {
this.QueueMessage(messageType: "UNAUTHENTICATED", data: e.Message);
this.QueueMessage(messageType: "UNAUTHENTICATED", data: e.Message, immediate: true);
}
}
else if (name == "Subscribe") {
Expand Down Expand Up @@ -216,10 +221,10 @@ protected void UnsubscribeAll() {

protected void Unsubscribe(ICollection<string> channelKeys) {
try {
logger.Debug($"UnsubscribeAsync()");
logger.Debug($"Unsubscribe()");
foreach (var channelKey in channelKeys) {
if (this.channelByKey.TryGetValue(channelKey, out Channel existingChannel)) {
logger.Debug($"SubscribeAsync():Removing channel key '{channelKey}'");
logger.Debug($"Unsubscribe():Removing channel key '{channelKey}'");
existingChannel.Dispose();
this.channelByKey.Remove(channelKey);
}
Expand Down
2 changes: 1 addition & 1 deletion Butterfly.Core/Channel/IChannelConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ public interface IChannelConnection : IDisposable {
DateTime Created { get; }
DateTime LastHeartbeat { get; }
void Start(object authToken, string id);
void QueueMessage(string channelKey = null, string messageType = null, object data = null);
void QueueMessage(string channelKey = null, string messageType = null, object data = null, bool immediate = false);
}
}
9 changes: 5 additions & 4 deletions Butterfly.Core/Database/BaseDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,16 +168,17 @@ public async Task<DataEvent[]> GetInitialDataEventsAsync(string dataEventName, s

public async Task<T> SelectValueAsync<T>(string sql, dynamic vars = null, T defaultValue = default(T)) {
Dict row = await this.SelectRowAsync(sql, vars);
if (row == null || !row.TryGetValue(row.Keys.First(), out object value) || value==null) return defaultValue;
if (row == null) return defaultValue;
else return row.GetAs(row.Keys.First(), defaultValue);

return (T)Convert.ChangeType(value, typeof(T));
//if (row == null || !row.TryGetValue(row.Keys.First(), out object value) || value==null) return defaultValue;
//return (T)Convert.ChangeType(value, typeof(T));
}

public async Task<T[]> SelectValuesAsync<T>(string sql, dynamic vars = null) {
Dict[] rows = await this.SelectRowsAsync(sql, vars);
return rows.Select(row => {
row.TryGetValue(row.Keys.First(), out object value);
return (T)Convert.ChangeType(value, typeof(T));
return row.GetAs(row.Keys.First(), default(T));
}).ToArray();
}

Expand Down
Loading

0 comments on commit 5a5a800

Please sign in to comment.