You are not logged in.
With the current trunk i'm getting an error because LockedSessionDelete no longer exists.
I have a procedure to kill sessions with the following code:
procedure TDCS_ServerDB.sessions_kill(Ctxt: TSQLRestServerURIContext; arrSessions_kill: tArrayOfInt64);
var
i, i2: int64;
countDel: integer;
currSession: TAuthSession;
begin
countDel := 0;
if (self = nil) or (fSessions = nil) or (fSessions.Count = 0)
then exit;
fSessions.Safe.ReadWriteLock; // won't block the ReadOnlyLock methods
try
for i := fSessions.Count - 1 downto 0 do
begin
currSession := self.fSessions[i];
for i2 := 0 to High(arrSessions_kill) do
if currSession.ID = arrSessions_kill[i2] then
begin
if countDel = 0 then fSessions.Safe.WriteLock; // upgrade the lock (seldom)
LockedSessionDelete(i, nil);
inc(countDel);
end;
end;
finally
if countDel <> 0 then
fSessions.Safe.WriteUnlock;
fSessions.Safe.ReadWriteUnLock;
end;
end;LockedSessionDelete does not exist now.
Maybe there is some other way to do the same thing?
Thank you.
Appreciate it, @rvk. That’s what I had in mind. I usually refer to it as 'master' or 'main', my bad.
Sorry, how do I get that trunk?
I was getting the same problem when compiling our server application targeting 64bit.
I switch to "lts-2.3" and now it compiles fine.
But I'm getting 75.807 assertion failures when running mormot2tests:
1.7. Network protocols:
- DNS and LDAP: 3 / 1,593 FAILED 90.32ms
2.2. Sqlite file:
- TRestClientDB: 18,951 / 398,577 FAILED 653.65ms
2.3. Sqlite file WAL:
- TRestClientDB: 18,951 / 398,577 FAILED 671.65ms
2.4. Sqlite file memory map:
- TRestClientDB: 18,951 / 398,575 FAILED 618.40ms
2.5. Sqlite memory:
- TRestClientDB: 18,951 / 466,885 FAILED 762.23ms
So...Yeah. I'm missing something obvious for sure...
Hello, sorry for the late reply. The problem is solved.
Once again, thank you.
Hello and thank you once again for your prompt reply @ab.
We do not use Allow/Deny of group por service.. All groups have full access to all services.
We use groups to separate out clients, each client has its own group, the users of each group share session data when they are logged in.
I don’t know if that is the best approach, but it has been working great for us so far.
Hello,
We updated to mORMot v2 recently, very smooth transition so far.
Today, when deploying our first server using version 2 we started having this error saying "Unauthorized method" on every interface method call. After some debugging I found this code:
unit mormot.rest.server;
(...)
procedure TRestServerUriContext.InternalExecuteSoaByInterfaceComputeResult;
(...)
if (Session > CONST_AUTHENTICATION_NOT_USED) and
(ServiceExecution <> nil) and
((SessionGroup <= 0) or
(SessionGroup > 255) or
(byte(SessionGroup - 1) in ServiceExecution.Denied)) then
begin
Error('Unauthorized method', HTTP_NOTALLOWED);
exit;
end;Looks like I will get an error if I have more then 255 Groups (more precisely if I have Groups with ID greater then 255).
Is this by design?
I changed the 255 to 2000 to patch my server and it seems to work ok, but I don't know if this causes adverse side effects... I would remove the SessionGroup > 255 condition if possible...
Our use case requires lots of user groups, more than a thousand...
Please advise.
Thank you!
Ok, thank you!
The problem is solved, no more errors while debugging.
Is it safe to use this commit (e8293e7) in production?
Thank you once again Arnaud
I will try that commit and let you know.
It is a small annoying thing (it disrupts the debug a little) but it does not affect anything as far as I'm aware.
Side note: on mORMot 1 that didn't happen. Same server configuration (useHttpApiRegisteringURI).
I'm just creating it like that:
MSS_ServerDB.DB.Synchronous := smOff;
MSS_ServerDB.DB.LockingMode := lmExclusive;
TRestHttpServer.Create(K_conn_port_cloud, [MSS_ServerDB], '+', useHttpApiRegisteringURI, 32, secSSL, '', '')
If that's not the best way to create the server please advise...
I'm using the TRestHttpServer class to expose a TRestServerDB server (more precisely, a class that extends the TRestServerDB with some server methods, and some helper methods as well.. nothing too fancy).
Yes, on production there is no error.
Our TAuthGroup.SessionTimeout is 60.
But I get those errors after way less the 30 minutes of inactivity...
In this next case, only 7 minutes have passed:

I think there is something else going on...
When my app is not doing nothing I still get those errors, it could confirm this time dependent theory.
Erro and Call Stack:


Hello,
Using the commit above I get:



If I open and close the app rapidly I get no error... Seems to be time dependent, but I could be wong.
No, the server is a separate program altogether.
I can add the Call Stack if that helps...
I frequently have this error, only on debug (Delphi 12).
Error:
Project xpto.exe raised exception class ENetSock with message 'THttpClientSocket.SockSendFlush(127.0.0.1) len=292 [Fatal Error - #6]'
It doesn't happen every time and not in the same place (in the code)... It appears to be harmless.
I added the IRestOrm thing. This is a non critical part of the client code so I will let it stay this way (and it is working).
Thank you!
It is working now... Thank you ab.
Please just confirm that this is correct...
procedure TForm1.Button1Click(Sender: TObject);
var
MSC_mem: TRestServerFullMemory;
MSR_table: TOrmUser;
begin
MSC_mem := TRestServerFullMemory.CreateWithOwnModel([TOrmUser]);
MSR_table := TOrmUser.Create;
MSR_table.FillPrepare(MSC_mem.Orm, '');
// Add a Record
MSR_table.ClearProperties;
MSR_table.SetFieldVariant('Name', 'Paula');
MSR_table.SetFieldVariant('Age', 48);
MSC_mem.Add(MSR_table, true);
// Add another Record
MSR_table.ClearProperties;
MSR_table.SetFieldVariant('Name', 'Maria');
MSR_table.SetFieldVariant('Age', 48);
MSC_mem.Add(MSR_table, true);
// Save JSON
MSR_table.FillPrepare(MSC_mem.Orm, '');
Memo1.Lines.Text := MSR_table.FillTable.GetJSONValues(true);
MSR_table.Free;
MSC_mem.Free;
end;Hello!
The code below works on mORMot 1 but not on version 2...
procedure TForm1.Button1Click(Sender: TObject);
var
MSC_mem: TRestStorageInMemory;
MSR_table: TOrmUser;
begin
MSC_mem := TRestStorageInMemory.Create(TOrmUser, nil, '');
MSR_table := TOrmUser.Create;
MSR_table.FillPrepare(MSC_mem, '');
// Add a Record
MSR_table.SetFieldVariant('Name', 'Paula');
MSR_table.SetFieldVariant('Age', 48);
MSC_mem.Add(MSR_table, true, false, false);
// Save JSON
MSR_table.FillPrepare(MSC_mem, '');
Memo1.Lines.Text := MSR_table.FillTable.GetJSONValues(true);
MSC_mem.Free;
end;I get an error on line: MSC_mem.Add(MSR_table, true, false, false);
(Access violation error)
What is the problem?
I will use this to convert some data to JSON, multiple records.
Thank you.
Thanks igors233, now I can move on ![]()
Hello.. I still can't find the NamedPipe server, can someone please point me in the right direction or confirm it is not available anymore?
Thank you.
Thank you for this, it is working.
I did miss another thing... The NamedPipe server... It is not available anymore?
Same question here...
Did you found a solution AntonE?
Hello,
I need some help, probably something very basic...
I'm currently refactoring our code to use mORMot version 2 and I can't find the function CurrentServiceContext (previously found on the mORMot.pas unit) to get the TServiceRunningContext.
Thank you.
I will do these changes and use TSQLHttpClient (TSQLHttpClientWinHTTP), correct?
Thank you for your help.
The socket client ( TSQLHttpClientWinSock ? ) doesn't support SSL if I remember correctly...
We din't migrate to mORMot 2 yet...
Thank you ab..
I think some clients use a proxy, but the majority don't...
Some clients report problems when using their home internet provider also (very simple direct PC-Router connection with no proxy)...
We use TSQLHttpClient (TSQLHttpClientWinHTTP) and TSQLRestClientURINamedPipe.
The change between local/cloud uses TSQLRestClientRedirect.
The issues only happen when TSQLRestClientRedirect is Redirect To TSQLHttpClient.
Hello everyone,
Our application has been experiencing some connection issues lately. And we are having a hard time figuring out why...
It doesn't happen on our headquarter's computers at all (Win10 and Win11), it happens on the clients, but not always, they will have trouble connecting (or requests fail mid-session) randomly.
When a request fails we find two different errors (with the same error code):
- winhttp.dll error 12019 (The handle is in the wrong state for the requested operation)
- winhttp.dll error 12019 (00002EF3)
It doesn't help the fact we can't debug this because we are not having the issue on our computers, and we never know on what client's computer it will happen...
We now have a dedicated server on a hosting company running the server application, before that, we used a VPS on another hosting company, and the issues persisted on this brand new dedicated server with new config, new SSL certificate, new domain, and IP.
I did find this topic: https://synopse.info/forum/viewtopic.php?id=5550
In our case the issue is intermittent, I guess it rules out the Proxy configuration.
We don't have the {$R Vista.res} resource on our .dpr project files, can this be it?
We are at a loss here...
Ok... I confirm the OnAuthentificationFailed event is firing when the user enters wrong credentials and when a request is made after the session expired on the server.
It's doing what it is supposed, my bad...
Thank you for your help igors233 ![]()
Thank you igors233...
Is that not equal to implement the OnAuthentificationFailed event available on TSQLHttpClient?
When I tried that, OnAuthentificationFailed only fired when wrong credentials are inserted...
Hello everyone,
We are experiencing a problem regarding sessions (not mORMot's fault). We are using sicPerGroup instance life time.
It has to do with the way users use the software...
Some users (lots of them) let the client app (delphi) open all day, for quick access or something like that. When they let the computer hibernate, if 1 hour is passed (our session timeout), because the client app stops sending keep alive calls, the session is terminated by the server. When the client app tries to communicate with the server after that, it get's an error as expected.
Is there a way to re-connect (creating a new session), in such cases? Ideally the new login would be executed before the new request is sent, to avoid having to re-send that request after the new login is made.
I tried some events from TSQLHttpClient, OnFailed and OnAuthentificationFailed... but they do not seem to fire on http calls...
We are still on version 1... We will migrate to version 2 soon.
Thank you.
Hello! I did a reindex directly on the server and that solved all the issues I was having.
In Portugal we have all kinds of special characters and accents, and it seems TSQLRecordCaseSensitive does not support these.
I would prefer to have full compatibility with SQLite Studio, but I'm guessing it will not be possible. I was able to use SynDBExplorer and it works OK. It is not as polished as SQLite Studio of course.
Thank you very much for the replies, people in this forum are awesome.
Hello!
I'm experiencing a very strange problem..
First I started noticing that I was getting some extra results that shouldn't be on a particular table, they where deleted some time ago.
I did a Vacuum on SQLiteStudio, but the problem persisted. Then I did a Reindex (also on SQLiteStudio), the particular query started returning the correct results on SQLiteStudio but now, that same query returns no results using the ExecuteList function.
Then, when I add new records to that table I will get some results via mORMot (ExecuteList) and others on SQLiteStudio, both start missing some records... Very strange!
Is there any incompatibility when Reindex is ran?
Is there a way to do a reindex directly on the server using mORMot? (Ideally without stopping the server...)
Thank you!
This is all implemented now and working as we want, nice!
Thank you ![]()
For other people that may need something like this:
procedure TDCS_ServerDB.kill_otherGroupSessions(Ctxt: TSQLRestServerURIContext);
var
i: integer;
currSession: TAuthSession;
begin
self.fSessions.Safe.Lock;
for i := self.fSessions.Count - 1 downto 0 do
begin
currSession := (self.fSessions[i] as TAuthSession);
if (currSession.GroupID = Ctxt.SessionGroup) and
(currSession.IDCardinal <> Ctxt.Session) then
self.SessionDelete(i, Ctxt);
end;
self.fSessions.Safe.UnLock;
end;I think this is OK. Any suggestions ab?
Thank you for prompt help.
Ok, I think that's TSQLRestServer.fSessions.Safe.Lock / TSQLRestServer.fSessions.Safe.Unlock
![]()
Let's hope I don't need any additional help, Thank you.
Yes, that's right. But there is no Lock method on fSessions[] that I can find.
I'm inheriting my own TSQLRestServer class, I can call SessionDelete now like advised on this topic, but I can't call/find TSQLRestServer.fSessions.Lock.
Thank you!
SessionDelete is not public... I was trying to do this from an interface (TInjectableObjectRest), using self.Server
Hello!
I need to kill sessions from a specific user group on the server... Is this possible?
Thank you!
You're right, I found it mentioned.
Is there any way to execute a query without any conversion?
Hello!
I'm using direct SQL execution to do some tasks. Today I encountered a problem that seems to be a Bug, but need your opinion/confirmation.
I'm using firebird. I debugged the code and my suspect is the TSQLRestStorageExternal.AdaptSQLForEngineList function.
The SQL gets cutted in this example:
Input SQL: SELECT codigo FROM rec_prf WHERE (nome='' OR nome IS NULL) AND EscolaID='Example'
Output SQL: select Codigo from rec_prf where (Nome='' or Nome is nullIf no parentheses are used it works as expected.
No problem, repository created.
Here it is yet another implementation based on RangerX's code. I implemented it as a javascript class. It seems to be working just fine.
GitHub repository: https://github.com/imperyal/synopse-login
/**********************************************************************************************/
/* */
/* ======= Synopse login class ======= */
/* */
/* */
/* - Based on RangerX's code ( https://synopse.info/forum/viewtopic.php?pid=2995#p2995 ) */
/* - Requires JQuery */
/* - Requires sha256 (https://github.com/emn178/js-sha256) */
/* - crc32 code included (from https://stackoverflow.com/questions/18638900/javascript-crc32) */
/* - Uses Localstorage to store session data like in the original code */
/* */
/* */
/* Example usage: */
/* */
/* -> Config variables (set before using the class) */
/* */
/* var G_SERVER_URL = "http://127.0.0.1:8080"; // Server URL */
/* var G_SERVER_ROOT = "root"; // Server root */
/* var G_MAIN_URL = G_SERVER_URL + '/' + G_SERVER_ROOT; // Main URL */
/* */
/* */
/* -> Login */
/* */
/* const APP_Login = new SYN_login; */
/* APP_Login.login(userName, userPass, F_loginResult); */
/* */
/* function F_loginResult(result) { */
/* if (result) { alert("Login OK"); } */
/* else { alert("Login ERROR"); } */
/* } */
/* */
/* -> Use $.ajax to call your interfaces, etc.. */
/* */
/**********************************************************************************************/
class SYN_login {
login(userName, usarPass, callBack) {
let servnonce;
let currDate;
let clientnonce;
let dataString;
let password;
let charPlusPos;
let self = this;
this.setAjaxPrefilter();
this.CloseSession(); // try to close previously opened session
currDate = new Date();
clientnonce = currDate.getTime() / (1000 * 60 * 5); // valid for 5*60*1000 ms = 5 minutes;
clientnonce = sha256("" + clientnonce);
dataString = {'UserName': userName};
// First request, to get the servnonce for the user
$.ajax({
type: "GET",
dataType: "json",
url: G_MAIN_URL + '/auth',
data: dataString,
timeout: 2000,
success: function(data, textStatus, jqXHR) {
servnonce = data.result;
password = sha256(G_SERVER_ROOT + servnonce + clientnonce + userName + sha256('salt' + usarPass)); // Sha256(ModelRoot+Nonce+ClientNonce+UserName+Sha256('salt'+PassWord))
dataString = {'UserName': userName, 'Password': password, 'ClientNonce': clientnonce};
// Secound request, sending required data including the spicedup password, to get a session
$.ajax({
type: "GET",
dataType: "json",
url: G_MAIN_URL + '/auth',
data: dataString,
crossDomain: true,
timeout: 2000,
success: function(data, textStatus, jqXHR) {
charPlusPos = data.result.indexOf('+');
if (charPlusPos > -1) {
// ******************************************
// Save relevant session data on localstorage
self.setNameValue('SESSION_ID', data.result.substr(0, charPlusPos));
self.setNameValue('SESSION_PRIVATE_KEY', data.result + sha256('salt' + usarPass));
self.setNameValue('SESSION_USERNAME', userName);
callBack(true);
return true;
}
},
error: function (jqXHR, textStatus, errorThrown) {
callBack(false);
return false;
if (jqXHR.status == 404) {return false;} // Not used so far
}
});
},
error: function() {
callBack(false);
return false;
}
});
}
InitSession() {
localStorage.removeItem(self.getPrefixed('SESSION_ID'));
localStorage.removeItem(self.getPrefixed('SESSION_PRIVATE_KEY'));
localStorage.removeItem(self.getPrefixed('SESSION_LAST_TICK_COUNT'));
localStorage.removeItem(self.getPrefixed('SESSION_TICK_COUNT_OFFSET'));
localStorage.removeItem(self.getPrefixed('SESSION_USERNAME'));
return true;
}
CloseSession() {
self = this;
if (!this.getValue_FromNameAsInt('SESSION_ID')) return;
$.ajax({
type: "GET",
dataType: "json",
url: G_MAIN_URL + '/auth',
data: {'session': this.getValue_FromNameAsInt('SESSION_ID'), 'UserName': this.getValue_FromName('SESSION_USERNAME')},
timeout: 2000,
success: self.InitSession,
error: self.InitSession
});
}
// converted from TSQLRestClientURI.SessionSign function
// expected format is 'session_signature='Hexa8(SessionID)+Hexa8(TimeStamp)+
// Hexa8(crc32('SessionID+HexaSessionPrivateKey'+Sha256('salt'+PassWord)+
// Hexa8(TimeStamp)+url))
GetSessionSignature(url) {
let currDate;
let currMsecs;
let prefix;
let nonce;
let ss_id_hex;
let ss_keyNonceUrl_crc32;
let ss_keyNonceUrl_hex;
let final_SIGN;
currDate = new Date();
currMsecs = currDate.getTime();
prefix = '?';
if (currMsecs < this.getValue_FromNameAsInt('SESSION_LAST_TICK_COUNT')) // wrap around 0 after 49.7 days
this.setNameValue('SESSION_TICK_COUNT_OFFSET', this.getValue_FromNameAsInt('SESSION_TICK_COUNT_OFFSET') + 1 << (32 - 8)); // allows 35 years timing
this.setNameValue('SESSION_LAST_TICK_COUNT', currMsecs);
nonce = currMsecs >>> 8 + this.getValue_FromNameAsInt('SESSION_TICK_COUNT_OFFSET');
nonce = this.numToHex(nonce);
ss_id_hex = this.numToHex(this.getValue_FromNameAsInt('SESSION_ID'));
ss_keyNonceUrl_crc32 = this.getValue_FromName('SESSION_PRIVATE_KEY') + nonce + url;
ss_keyNonceUrl_crc32 = this.crc32( ss_keyNonceUrl_crc32);
ss_keyNonceUrl_hex = this.numToHex(ss_keyNonceUrl_crc32);
// Final signature
final_SIGN = ss_id_hex + nonce + ss_keyNonceUrl_hex;
// Change prefix if necessary (if the URL already has variables add "&" to set another, keep "?" is this is the only one)
if (url.indexOf('?') >= 0)
prefix = '&';
return prefix + 'session_signature=' + final_SIGN;
}
// Set ajaxPrefilter function - will run on every jQuery ajax call to add the SessionSignature */
setAjaxPrefilter() {
self = this;
$.ajaxPrefilter(function(options, _, jqXHR) {
let new_url;
let session_sign;
if (self.getValue_FromNameAsInt('SESSION_ID') > 0 && options.url.indexOf(G_MAIN_URL) > -1) { // User is authenticated
new_url = options.url;
if (options.data && options.type == "GET")
{
new_url = new_url + '?' + options.data;
options.data = null; // prevents jQuery from adding these to the URL
}
session_sign = self.GetSessionSignature(new_url.substr(G_SERVER_URL.length + 1));
options.url = new_url + session_sign;
options.cache = true; // we don't want anti-cache "_" JQuery-parameter
}
});
}
// Convert number to Hex with 8 caracters
numToHex(d) {
let hex = Number(d).toString(16); // Converts to Hex (base 16)
while (hex.length < 8) {
hex = "0" + hex;
}
return hex;
}
/****************************/
/* Local Storage */
/****************************/
getPrefixed(name) { return 'syn_' + name; }
getValue_FromName(name) { return localStorage.getItem(this.getPrefixed(name)); }
setNameValue(name, value) { return localStorage.setItem(this.getPrefixed(name), value); }
getValue_FromNameAsInt(name) { return Number(this.getValue_FromName(name)) ? this.getValue_FromName(name) : 0; } // Operator "?" = if then ":"" = else
/*****************************************************************/
/* crc32 functions */
/* https://stackoverflow.com/questions/18638900/javascript-crc32 */
/*****************************************************************/
makeCRCTable() {
let c;
let crcTable = [];
for(let n =0; n < 256; n++){
c = n;
for(let k =0; k < 8; k++){
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
crcTable[n] = c;
}
return crcTable;
}
crc32(str) {
let crcTable = window.crcTable || (window.crcTable = this.makeCRCTable());
let crc = 0 ^ (-1);
for (let i = 0; i < str.length; i++ ) {
crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
}
return (crc ^ (-1)) >>> 0;
};
}
/*********************************************/
/* Check for localstorage functionality */
/*********************************************/
$(function() {
if (typeof(localStorage) == 'undefined')
alert('You do not have HTML5 localStorage support in your browser. Please update or application cannot work as expected');
});Hello ab, thank you for your response.
Even on the delphi client, I get an authentication error if the user does not make any call to the server in the specified group timeout.
My simple tests is:
In the delphi client I call .SetUser and wait 5 minutes in this case. If I try to call any interface method I get 'Authentication Failed: Invalid signature (0)'.
== Edit ==
Another thing, if I set a timeout of 60 minutes, the data stored in my server classes will not be there if the user is idle for more then 30 minutes, despite his timeout of 60 minutes. Is this the normal behavior? Or is there a way to set a timeout for the memory retention too?
Thank you very much and keep on doing this amazing work.
Hello!
I'm trying to make sense of the meaning of the user group timeout value. For testing purposes I'm using a timeout of 5 (minutes) and my interface is in sicPerGroup mode.
I noticed that the session data stays for much longer then 5 minutes (user logged in or not), it seems that the data is being kept for 30 minutes if all the group's users are idling, then it is deleted.
What is the recommended way to ensure the data is kept as long as the groups's users are logged in (delphi client / ajax client)? Do I need to ping the server periodically or there is another/better way?
What does the timeout actually do?
Thank you!
That's what I thought. Today I started testing that, doing some validation on a interface class, SOA style like you said.
procedure TServiceOutroTeste.alterarCliente(var cli: TSQLClienteRecord);
var
FieldIndex: integer;
strError: string;
begin
FieldIndex := -1;
strError := cli.Validate(internalClient, ['Email'], @FieldIndex);
if strError <> '' then raise Exception.Create(strError)
else internalClient.Update(cli);
end;Very simple example. I don't know if this is the right way of doing this but it works as expected.
I did find the way to do validation on my services (newbie stuff):
class procedure TSQLClienteRecord.InternalDefineModel(Props: TSQLRecordProperties);
begin
AddFilterNotVoidText(['Nome','Email']);
AddFilterOrValidate('Email', TSynValidateEmail.Create);
end;And then call .Validate to do the actual validation.
But I still don't know how to do validation on the server side for my TSQLRecord's CRUD operations... And prevent invalid foreign key violation at the record level, when inserting a record with Postman for example.
Would appreciate some help, thank you.