Ways to persist SignalR connection - javascript

I am creating web application, which let's users to communicate with so called chat.
In order to enable such communication, I use SignalR library. I create connection at first visit to my page (main page). So JS code, which creates connection, creates variable used to configure connection.
Then user enters chat room, which is different page, so new JSs are loaded etc. The one, which held connection variable is now unavailable. But now I need that connection to send messages in my chat room.
So this variable must be "transfered" over to next scripts.
So, what would be the way to actually persist connection through whole session on the website?

Finally I got my answer.
It was suggested that I should use ASP NET Identity, which is very valid, but I already created simple authentication and users management. It isn't safe and as half good as ASP NET Identity (I looked throught it and got my head around it), but it's just personal project, not production one, so if it evolves I might switch to Identity or even implement one myself ;) But it's not the case.
It required little bit of extra steps, so:
I needed to enable sessions in ASP.NET Core, I used this article for this purpose. Having that, I can persist my user login and provide it to user ID provider for signalR
Adding cutsom user ID provider for SignalR in .NET:
I needed to create such class
public class UserIdProvider : IUserIdProvider
{
public static readonly string SESSION_LOGIN_KEY = "loggedUser";
private readonly IHttpContextAccessor _httpContextAccessor;
public UserIdProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string GetUserId(HubConnectionContext connection)
{
var session = _httpContextAccessor.HttpContext.Session;
session.TryGetValue(SESSION_LOGIN_KEY, out byte[] loginBA);
if (loginBA == null)
{
return null;
}
return new string(loginBA.Select(b => (char)b).ToArray());
}
}
So, after logging I can set login in Session, so it becomes "state" variable, and use it in above class.
Also, one need to add it in ASP services, like below (in Startup.ConfigureServices):
services.AddSingleton<IUserIdProvider, UserIdProvider>();
There also one thing, which still need to be set: in UserIdProvider we need to access Session through HttpContext. In order to use HttpContextwe need to specify it like below (also in Startup.ConfigureServices):
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
which passes HttpContextAccessor to serices constructors.
After all this, you can access users in SignalR Hub with their logins, which is set in Context.UserIdnentifier
This also enables sending messages to specific user, just by passing their login (frontend client just chooses user), like below:
public async Task SendMessage(string message, string user)
{
await Clients.User(user).SendAsync("ReceiveMessage", message).ConfigureAwait(false);
}
NOTE There was one problem though. Browsers on computer didn't persist sessions, which I solved by (also in Startup.ConfigureServices):
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false; // <~~~~ This needs to be set to false
options.MinimumSameSitePolicy = SameSiteMode.None;
});
Without it, you need to be carefull with cookies, if you don't accept them on a site, it won't work, as user's login won't be persisted.

On the server side, you can send the message to specific user via user id:
var user = await _userManager.GetUserAsync(Context.User);
await Clients.User(user.Id).SendCoreAsync("msg", new object[] { user.Id, user.Email });
On the client side, whenever the connection is started and the hub msg is listened, the user will receive the message:
connection.on('msg', function (...data) {
console.log('data:', data);
});
By sending message(s) via user id, you don't need to care where the target user is.
public class ChatHub : Hub
{
private IUserManager<ApplicationUser> _userManager;
public ChatHub(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetInfo()
{
var user = await _userManager.GetUserAsync(Context.User);
await Clients.User(user.Id).SendCoreAsync("msg", new object[] { user.Id, user.Email });
}
}

Related

Can I generate a transaction on the server and send it to the client for payment

I have built a smart contract method to which I pass some sensitive data that needs to be stored on the blockchain and alter the state of the contract. I, the creator of the contract don't want to be the one paying for the fees of that transaction. I want the user on the browser to approve and pay for it.
However, I do not want to generate the transaction object on the browser as I want some of the data that will be passed to the contract to be hidden from the client. If I understand the web3 syntax correctly, in the code below, I'm doing just that
web3.eth.sendTransaction({
from: walletAddressOfTheUserThatWillPayForTheTransaction,
data: myContract.methods.changeState(..sensitive data...).encodeABI()
})
However I do not want the above to happen on the browser. In my head, the sequence of events should look like this (pseudocode):
// server
let transactionObject = {
from: walletAddressOfTheUserThatWillPayForTheTransaction,
data: myContract.methods.changeState(..sensitive data...).encodeABI()
}
sendToClient(encrypt(transactionObject))
// client
let encryptedTransactionObject = await fetchEncryptedTransactionObjectFromServer()
// this should open up Metamask for the user so that they may approve and finalise the transaction on the browser
web3.eth.sendTransaction(encryptedTransactionObject)
Is this possible ? Is there some other way of achieving this? Could you provide me with some hints about the actual syntax to be used?
However, I do not want to generate the transaction object on the browser as I want some of the data that will be passed to the contract to be hidden from the client.
Then you should not be using public blockchains in the first place, as all data on public blockchains, by definition, is public. Anyone can read it.

Using global variable as database cache?

Site is working with nodejs+socketio+mysql.
Is it normal to create a global object just before starting my app to store everything I have in the database? Something like user's password hashes for a very quick authentication process, compare the given token + userid.
var GS= {
users: {
user1: {
token: "Djaskdjaklsdjklasjd"
}
,
user555: {
token: "zxczxczxczxc"
}
,
user1239: {
token: "ertertertertertret"
}
}
};
On connect, node check user with gived user_id.
if (GS.hasOwnPropery("user"+user_id)) {
//compare gived token GS["user"+user_id].token
} else {
//go to database to get unknown id and then store it in GS
GS["user"+user_id] = { token: database_result };
}
And with everything else the same thing, using object property instead of querying the database. So if someone go to url /gameinfo/id/1, I just look in variable GS["game"+url_param] = GS["game"+1] = GS.game1
And of course, we don't talk about millions of rows in the database. 50-70k max.Don't really want to use something like Redis or Tarantool.
You can have a global object to store these info, but there are something to consider:
If you app are running by more than one machine (instance), this object won't be shared between these them.
This leads to some functional downsides, like:
you would need sticky session to make sure request from one particular client always directed to one particular instance
you can not check status of an user having data stored in another instance ...
Basically, anything that requires you to access user session data, will be hard, if not impossible, to do
In case your server goes down, all session data will be lost
Having a big, deep nested object is dangerously easy to mess up
If you are confident that you can handle these downsides, or you will not encounter them in your application, then go ahead. Otherwise, you should consider using a real cache library, framework.

Retrieve values from existing open tab in Chrome using C#

I am trying to build a console application using c# .net.. I need one particular functionality which is to retrieve values from an external website. The thing is we need to sign in to that website. I am able to open the page that i need using process.start in chrome signed in with the values i need.. but problem is when retrieving the value from page.. i thought of getting source code but every way i try it does not take the session and hence i am getting just error page source code as i am just entering the URL n not accessing the already opened tab? Is there any other way available either using JavaScript or c#?
Use WebClient API to login and download page data. You will need to make it cookie aware in order to maintain session. You will need to add reference to System.Web Assembly to use this API in console application.
public class CookieAwareWebClient : WebClient
{
public CookieAwareWebClient()
{
CookieContainer = new CookieContainer();
}
public CookieContainer CookieContainer { get; private set; }
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
request.CookieContainer = CookieContainer;
return request;
}
}
Now use it like
using (var client = new CookieAwareWebClient())
{
var values = new NameValueCollection
{
{ "username", "john" },
{ "password", "secret" },
};
client.UploadValues("http://domain.loc/logon.aspx", values);
// If the previous call succeeded we now have a valid authentication cookie
// so we could download the protected page
string result = client.DownloadString("http://domain.loc/testpage.aspx");
}
I borrowed this piece of code from WebClient accessing page with credentials

Can client mock a cookie?

I'm using ASP.NET and create a cookie on server, like this:
public void LoginCookie(string id, string name, string loc)
{
HttpCookie cookie = new HttpCookie("login");
cookie["name"] = HttpContext.Current.Server.UrlEncode(name); // user name
cookie["avatar"] = loc; // user avatar location
cookie["accountId"] = id; // user id
cookie.Expires = DateTime.Now.AddDays(180); // default expiring
HttpContext.Current.Response.Cookies.Add(cookie);
}
And here is the code to check if user is login:
if (Request.Cookies["login"] != null)
{
// login successful
}
My trouble is: Any client can view the cookie values easily. So, what's happen if he mocks a new cookie with the same name login and same values name, avatar, accountId?
If he does that, he needn't be login (same meaning with login without a password).
Is there any way to do that?
The way to do this is to store a hash of the username and the username in the cookie. Since the client does not know how you generate the hash (and you should generate with a cryptographically strong method) and can't generate one you can verify the user was the one you authenticated by checking the hash. To be really safe you probably want to include a time stamp in the hash.
Or you could encrypt the username (or more often userid) after authentication and store that. This is the most common method.
You could also use SSL or TLS and the cookies will be encrypted. However, this does not stop the client just people spying on the client.

Lock web object among users in asp.net mvc

I have an asp.net mvc site where users can select a number of objects, they are identified by a unique id.
What I would like to do is:
User A opens an object
When user B tries to open the same object, a message will appear saying this is already open by user A, therefore is inaccessible.
I guess the server would have a sort of 'shared' session that can be queried in every Ajax request, to check if the object is already open by a different user. When the object is closed then that id should be removed from the shared list of opened objects. What would be the best approach for this?
In a typical scenario, assuming that you are working with plain CLR objects, this could be a solution
// User1 and User2 are trying to access objectA
private static int objectACounter; // Counter to decide whether object A is occupied
if(objectACounter != 0) // Object A is occupied
return user message object A is occupied
lock(objectA)
{
objectACounter ++;
// User logic processing
objectACounter --;
}
Above mentioned solution can be further refined by using a static dictionary to suggest which user has currently locked the object A and at what time, which can be returned to the incoming user.
In this case you plan to use something like Shared session, in my understanding there's nothing like a shared session, since session is user specific, above mentioned mechanism is a way to create one. You can plan to use Application Cache instead, where for each object you can define a key for each object and add the details in same way as you would do to the static dictionary, since Cache is a key value pair and it persist across the multiple user requests on the server.
Logic will be something like:
if(Cache.ContainsKey(objectAKey))
// Return error to the incoming user with details of object holding user
lock(objectA)
{
Cache.Insert(objectAKey, UserValues);
// User1 logic processing
Cache.Remove(objectAKey, UserValues);
}
Please let me know if I have misunderstood your question and you are expecting a different solution, We can adapt the current solution in multiple scenarios
I had to do something similar recently, but what I ended up doing was to not prevent a second person from accessing a submitted application, but to notify them that someone else is currently working on it when they view it (passive feedback) and if they attempt to modify it, prevent them from doing so, either after the postback occurs and possibly rendering the UI in a readonly fashion (active feedback).
I tracked the activity of who was using what with SignalR, this way I could dynamically update the list of applications when someone accessed one for the first time, or release an application when they leave the page, without forcing the user viewing the list of projects to refresh the page to be made aware of the change.
I setup a signalR Hub in my code to track which project was claimed by whom. I implemented the tracking using ConcurrentDictionary class to tackle any sort of concurrency problems of modifying the collection.
public class RetainedApplicationsHub : Hub
{
private static readonly ConcurrentDictionary<Guid, RetainedApplication> RetainedApplications = new ConcurrentDictionary<Guid, RetainedApplication>();
public static bool IsApplicationRetained(Guid applicationId)
{
return RetainedApplications.ContainsKey(applicationId);
}
public void RetainApplication(Guid applicationId, Guid personId)
{
var retainedSuccessfully = RetainedApplications.TryAdd(applicationId, new RetainedApplication{ConnectionId = Context.ConnectionId, PersonId = personId});
if(retainedSuccessfully)
Clients.All.applicationRetained(applicationId);
}
public void ReleaseApplication(Guid applicationId)
{
RetainedApplication temp;
RetainedApplications.TryRemove(applicationId, out temp);
Clients.All.applicationReleased(applicationId);
}
public ICollection<Guid> GetRetainedApplicationIds()
{
return RetainedApplications.Keys;
}
public override Task OnDisconnected(bool stopCalled)
{
RetainedApplication temp;
var applicationId = RetainedApplications.Single(x => x.Value.ConnectionId == Context.ConnectionId).Key;
RetainedApplications.TryRemove(applicationId, out temp);
Clients.All.applicationReleased(applicationId);
return base.OnDisconnected(stopCalled);
}
}
public class RetainedApplication
{
public string ConnectionId { get; set; }
public Guid PersonId { get; set; }
}
On my Application list page
<script src="~/signalr/hubs"></script>
<script>
$(function () {
'use strict';
var retainedApplicationsHub = $.connection.retainedApplicationsHub;
retainedApplicationsHub.client.applicationRetained = function(applicationId) {
$("td:contains('" + applicationId + "')").parent().addClass("active text-muted");
};
retainedApplicationsHub.client.applicationReleased = function(applicationId) {
$("td:contains('" + applicationId + "')").parent().removeClass();
};
$.connection.hub.start().done(function() {
retainedApplicationsHub.server.getRetainedApplicationIds()
.done(function(applicationIds) {
$.each(applicationIds, function() {
retainedApplicationsHub.client.applicationRetained(this);
});
});
});
});
</script>
On my application details page
<script src="~/signalr/hubs"></script>
<script>
$(function () {
'use strict';
var retainedApplicationsHub = $.connection.retainedApplicationsHub;
retainedApplicationsHub.client.applicationRetained = function(applicationId) {
//do nothing, must subscribe to at least one event to have the OnDisconnected event on the hub register correctly
};
$.connection.hub.start().done(function () {
retainedApplicationsHub.server.retainApplication("#Model.RegistrationApplication.ObjectId", "#User.GetIdentityId()");
});
});
</script>

Categories

Resources