You are not logged in.
@albanirneves
Why you do not use Ext.Ajax.request instead of browser-specific XMLHttpRequest?
Offline
Hello Guys, i followed the code from RangerX and esmondb to implement the Authentication in typescript (for angular 4).
The login works as expected, and i get a signature, but i fail at the Session signature, the Server always answer wtih 403 : Forbidden
Code for the Authentication
public login(username, password): void {
this.Username = username;
this.PasswordHashHexa = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash("salt" + password)); //Password Hex Sha256 generate
this.http.get('http://localhost:8080/' + this.ServerAuthRoot + '/auth?UserName=' + username).map(this.extractData).catch(this.handleErrorObservable).subscribe(
response => {// We got a Nonce from the Server in response.result
this.gotNonce(response.result);
},
error => {// Some Error occured, we did not got a Nonce from the Server
alert(JSON.stringify(error));
}
);
}
private gotNonce(aNonce: string): void {
var shaPass: string;
var aClientNonce = "", s = "", d = new Date();
aClientNonce = d.getFullYear().toString();
s = d.getMonth().toString();
if (s.length === 1) {
s = '0' + s;
}
aClientNonce = aClientNonce + '-' + s;
s = d.getDate().toString();
if (s.length === 1) {
s = '0' + s;
}
aClientNonce = aClientNonce + '-' + s + ' ';
s = d.getHours().toString();
if (s.length === 1) {
s = '0' + s;
}
aClientNonce = aClientNonce + s;
s = d.getMinutes().toString();
if (s.length === 1) {
s = '0' + s;
}
aClientNonce = aClientNonce + ':' + s;
s = d.getSeconds().toString();
if (s.length === 1) {
s = '0' + s;
}
aClientNonce = aClientNonce + ':' + s;
aClientNonce = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(aClientNonce))
shaPass = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(this.ServerAuthRoot + aNonce + aClientNonce + this.Username + this.PasswordHashHexa));
s = 'http://localhost:8080/' + this.ServerAuthRoot + '/auth?UserName=' + this.Username + '&Password=' + shaPass + '&ClientNonce=' + aClientNonce;
// prompt('Resulting Call :', s);
this.http.get(s).map(this.extractData).catch(this.handleErrorObservable).subscribe(
response => {
alert(JSON.stringify(response));
this.gotSession(response.result);
},
error => {
alert(JSON.stringify(error));
}
)
}
private gotSession(aSessionKey: string) {
var i: number = aSessionKey.indexOf("+");
this.SessionID = parseInt(aSessionKey.slice(0, i));
this.SessionIDHexa8 = this.SessionID.toString(16);
while (this.SessionIDHexa8.length < 8) {
this.SessionIDHexa8 = '0' + this.SessionIDHexa8;
}
this.SessionPrivateKey = this.crc32(this.PasswordHashHexa, this.crc32(aSessionKey, 0));
};
and the Session Signature
public SessionSign(url: string): string {
var Tix: number;
var Nonce: string;
var s: any;
var ss: any;
var d: Date = new Date();
Tix = d.getTime();
if (this.LastSessionTickCount == Tix) {
Tix = Tix + 1;
}
this.LastSessionTickCount = Tix;
Nonce = Tix.toString(16);
while (Nonce.length < 8) {
Nonce = '0' + Nonce;
}
if (Nonce.length > 8) {
Nonce = Nonce.slice(Nonce.length - 8, Nonce.length);
}
ss = this.crc32(url, this.crc32(Nonce, this.SessionPrivateKey)).toString(16);
while (ss.length < 8) {
ss = '0' + ss;
}
s = url.indexOf("?") == -1 ? url + '?session_signature=' : url + '&session_signature=';
return s + this.SessionIDHexa8 + Nonce + ss;
}
and the crc32 function
private Crc32Tab = [ /* CRC polynomial 0xEDB88320 */
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D];
private Crc32Add(crc,c)
/*
'crc' should be initialized to 0xFFFFFFFF and after the computation it should be
complemented (inverted).
CRC-32 is also known as FCS-32.
If the FCS-32 is calculated over the data and over the complemented FCS-32, the
result will always be 0xDEBB20E3 (without the complementation).
*/
{
return this.Crc32Tab[(crc^c)&0xFF]^((crc>>8)&0xFFFFFF);
}
private crc32(str, crc)
{
var n, len;
len = str.length;
//var crc;
if (typeof(crc) == "undefined") { crc = 0xFFFFFFFF; }
else {
crc = crc^0xFFFFFFFF; //crc = ~crc;
if (crc < 0) {
crc = 4294967296 + crc;
}
}
//crc=0xFFFFFFFF;
for (n=0; n<len; n = n+1) {
crc = this.Crc32Add(crc,str.charCodeAt(n));
}
crc = crc^0xFFFFFFFF; //crc = ~crc;
if (crc < 0) {
crc = 4294967296 + crc;
}
return crc;//^0xFFFFFFFF;
}
}
I don't know why the Session Signature doesnt work as expected.
I hope you guy's can help me out.
Edit : And Sorry for reviving an old Post
Last edited by ImproSnake (2017-08-21 09:01:31)
Offline
I think this is not related with javascript authentication, but with server side authentication. When I started using javascript authentication I faced similar problems until really understand how authentication at server side works. I use "interface based" mode and faced this problem when implementing OnAuthenticationUserRetrieve....
Last edited by MagoSchmidt (2017-08-21 09:47:51)
Offline
I don't now why this should be an issue at all.
This is all i do on the server side
restModel := TSQLModel.Create([], ROOT_NAME);
try
// initialize Rest-Server
restServer := TSQLRestServerFullMemory.Create(restModel,false);
restServer.CreateMissingTables(0, [itoNoAutoCreateUsers]);
restServer.AuthenticationRegister(TSQLRestServerAuthenticationDefault).Options := [];
fillAuthFromDB(restServer, dbConProbs);
try
// register the Services on the Server
restServer.ServiceDefine(TServiceHello, [IHello], sicPerSession);
// launch HttpServer which publish the restServer
// useHttpApiRegisteringURI = AutoRegistering the URI specified by the restServer + PORT_NAME
httpServer := TSQLHttpServer.Create(PORT_NAME, [restServer], '+', useHttpApiRegisteringURI);
try
httpServer.AccessControlAllowOrigin := '*';
writeln(#10'Background server is running.'#10);
writeln('Press [Enter] to close the server.'#10);
readln;
finally
httpServer.Free;
end;
finally
restServer.Free;
end;
finally
restModel.Free;
end;
And the implementation of the services + Add the Users from my db to the sqlite tables to use the mormot framework auth of course.
And the session signature works as expected with a mormot client. There i can use the services after login, That's why i assume something is wrong in my javascript code
Last edited by ImproSnake (2017-08-21 09:58:52)
Offline
You disabled authentication on the server side.
So it is logical to be able to create sessions without any authentication...
Where i disabled the Authentication, i thought the Default Authentication is the Safe Authentication mode ???
Offline
@ab
Thank you for your help. I call the AuthenticationRegister now before i call CreateMissingTables. But i still get 403 Forbidden from the Webapplication but all work as expected wiht the mormot Client.
I can login successfully and get a Session but i cant get the signature to work or whatever goes wrong there.
after login
{"result":"1169656096+29EBAE633FDCDA4F25C690589C8CE3863A46063CBDBE9D969C790FA9EF93B64F","logonid":4,"logonname":"***********","logondisplay":"","logongroup":3,"timeout":60,"server":"Server","version":""}
And now after call a service
Response with status: 403 Forbidden for URL: http://localhost:8080/API/Hello.Hello?session_signature=45b789200450f02caf6a4349
{
"errorCode":403,
"errorText":"Authentication Failed: Invalid signature (0)"
}
I hope u can help me further.
Thank you
Last edited by ImproSnake (2017-08-21 10:24:03)
Offline
Hi ab, thank you again for your help.
I lack the experience to see that the signature is wrong by just looking at it
Thats how i compute the signature.
public SessionSign(url: string): string {
var Tix: number;
var Nonce: string;
var s: any;
var ss: any;
var d: Date = new Date();
Tix = d.getTime();
if (this.LastSessionTickCount == Tix) {
Tix = Tix + 1;
}
this.LastSessionTickCount = Tix;
Nonce = Tix.toString(16);
while (Nonce.length < 8) {
Nonce = '0' + Nonce;
}
if (Nonce.length > 8) {
Nonce = Nonce.slice(Nonce.length - 8, Nonce.length);
}
ss = this.crc32(url, this.crc32(Nonce, this.SessionPrivateKey)).toString(16);
while (ss.length < 8) {
ss = '0' + ss;
}
s = url.indexOf("?") == -1 ? url + '?session_signature=' : url + '&session_signature=';
return s + this.SessionIDHexa8 + Nonce + ss;
}
Where SessionIdJHexa8 and SessionPrivateKey are
private gotSession(aSessionKey: string) {
var i: number = aSessionKey.indexOf("+");
this.SessionID = parseInt(aSessionKey.slice(0, i));
this.SessionIDHexa8 = this.SessionID.toString(16);
while (this.SessionIDHexa8.length < 8) {
this.SessionIDHexa8 = '0' + this.SessionIDHexa8;
}
this.SessionPrivateKey = this.crc32(this.PasswordHashHexa, this.crc32(aSessionKey, 0));
};
Last edited by ImproSnake (2017-08-22 05:22:05)
Offline
Hi again. I worked on the server side debug.
First the mormot Log to conolse prints out something like "Invalid Signature expected xxxxxx got yyyyy "
And on the server it comes down to mormot.pas line 51804
if HexDisplayToCardinal(PTimeStamp+8,aSignature) and
(aSignature=aExpectedSignature) then
Obviously the Expected Signature and the aSignature doesnt match. But i cant find out why...
I would be glad if someone can help here
Last edited by ImproSnake (2017-08-22 10:16:44)
Offline
I think there was a bug in the original code. in the crc32 function try changing this line:
crc = crc ^ (-1);
to this:
crc = crc^0xFFFFFFFF;
Offline
Thank you all for your help.
The error was much much easier than the crc - Function and so on.
I just missused the Session Sign Function. I set the whole url and not just root + params.
Instead of : 'http//:xxxxxxxx/ServerRoot/Service'+Params
You have to set 'ServerRoot/Service'+params
as URL for the SessionSign Function.
Just in case somone got stuck on the same Problem.
Sorry for wasting your time.
Last edited by ImproSnake (2017-08-23 07:28:07)
Offline
@albanirneves
i am a newbie of Extjs,could you pl tell me how can i add or use your Ext.js.Auth to example 18. thinks a lot.
Offline
Hello everyone, I try searching everywhere but nothing solve my problem.
I can login and everything works fine. Except that once in a while when client(angular2) call API to mormot server, it will throw error on chrome-debug with
{errorCode:403, errorText:"Authentication Failed: Invalid signature (0)"}
Likely to happen more frequent when server has many access at that time.
This is my SessionSign method on angular2
private SessionSign(url) {
var Tix, Nonce, s, ss, d = new Date();
Tix = d.getTime();
if (this.fLastSessionTickCount == Tix) {Tix = Tix + 1;}
this.fLastSessionTickCount = Tix;
Nonce = Tix.toString(16);
while(Nonce.length < 8) { Nonce = '0'+Nonce; }
if (Nonce.length > 8) { Nonce = Nonce.slice(Nonce.length-8, Nonce.length) }
ss = crc32(url, crc32(Nonce, this.fSessionPrivateKey)).toString(16);
while(ss.length < 8) { ss = '0'+ss; }
s = url.indexOf("?") == -1 ? url+'?session_signature=' : url+'&session_signature=';
return s + this.fSessionIDHexa8 + Nonce + ss;
}
This is when I start service on Server
function TSM.StartService(port, databasename: String): Boolean;
begin
aProps := TOleDBMSSQL2012ConnectionProperties.Create(servername, databasename, username, password);
VirtualTableExternalRegister(Model, TSQLGroups, aProps, 'dbo.LOT_GROUPS');
VirtualTableExternalRegister(Model, TSQLUsers, aProps, 'dbo.LOT_USERS');
ServerDB := TSQLRestServerWS.Create(Model, ':memory:', True);
ServerDB.CreateMissingTables(0);
ServerDB.ServiceRegister(TServiceDatabase, [TypeInfo(IDatabase)], sicShared);
ServerDB.ServiceRegister(TServiceDatabase2, [TypeInfo(IDatabase2)], sicShared);
HttpServer := TSQLHttpServer.Create(port, [ServerDB]);
HttpServer.AccessControlAllowOrigin := '*';
Result := True;
end;
Called method
function AuditLog_GetAuditLog(dataobj: RawUTF8): TServiceCustomAnswer;
var res: ISQLDBRows;
sql,data,lotstatus,onlyunread: RawUTF8;
watch,size: Integer;
obj: Variant;
showAll: Boolean;
begin
obj := _JSON(dataobj);
size := 1000;
showAll := False;
if obj.Exists('size') then begin
size := obj.size;
end;
if obj.Exists('showAll') and (obj.showAll='Y') then
showAll := True;
//---- build SQL command ----
sql := 'select TOP '+size.ToString+' * from AuditLog';
if not showAll then
sql := sql + Format(' where (UserID=%s or ChangeUserID=%s)', [SQLB.Str(obj.userid), SQLB.Str(obj.userid)]);
sql := sql + ' order by ID';
res := SM.aProps.ExecuteInlined(sql, True);
Result.Content := Data_Content(res, 'false'); // make json
Result.Header := 'Content-type: application/json';
end;
Last edited by pandaben7890 (2017-12-11 15:37:40)
Offline
Trying to debug but don't know what i'm doing wrong. Put breakpoint but never stop here U_U.
Offline
consider using NoTimeStampCoherencyCheck, see https://synopse.info/forum/viewtopic.php?id=1983
Offline
@emaxx
Try adding but error still happen
function TSM.StartService(port, databasename: String): Boolean;
begin
aProps := TOleDBMSSQL2012ConnectionProperties.Create(servername, databasename, username, password);
VirtualTableExternalRegister(Model, TSQLGroups, aProps, 'dbo.LOT_GROUPS');
VirtualTableExternalRegister(Model, TSQLUsers, aProps, 'dbo.LOT_USERS');
ServerDB := TSQLRestServerWS.Create(Model, ':memory:', True);
ServerDB.CreateMissingTables(0);
ServerDB.ServiceRegister(TServiceDatabase, [TypeInfo(IDatabase)], sicShared);
ServerDB.ServiceRegister(TServiceDatabase2, [TypeInfo(IDatabase2)], sicShared);
(ServerDB.AuthenticationRegister(TSQLRestServerAuthenticationDefault) as TSQLRestServerAuthenticationSignedURI).NoTimeStampCoherencyCheck := true;
HttpServer := TSQLHttpServer.Create(port, [ServerDB]);
HttpServer.AccessControlAllowOrigin := '*';
Result := True;
end;
Offline
@ab
Not work either.
Try put breakpoint on ServerDB := TSQLRestServerWS.Create(Model, ':memory:', True); not break here too.
Did I do something wrong about debugging?
Offline
It could be a missing '/'
Offline
Hello I am a newbie, but I used a Javascript from somewhere here, perhaps it was SynAuth.js, for the Login and "create user Session" ... and i sometimes got "Invalid signature" on the call right after. In the end it appeared that the signature was computed on the Javascripts side ! BEFORE ! the return of a valid SessionKey was returned. So the signature was computed on a nonexistent Sessionkey. This is just a thought, but perhaps there is something in it!
Offline
Hello, I would like to know how I can store the session key with javascript for use in future requests on a website.
Offline
Thanks ab for the info.
Last edited by polidados (2018-04-16 14:51:01)
Offline
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');
});
Last edited by imperyal (2018-12-17 17:43:26)
Offline
Thanks for sharing, would be great if put it in a github repository
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
No problem, repository created.
Offline
No problem, repository created.
checked out and starred, thank you!
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
Hi,
I am trying to use this js library in my application. It worked fine with sqlite db but when i use MongoDB, login funtion returned false. Server also responed to /auth parameter as "Bad Request". How could i make it work with MongoDB?
Last edited by koraycayiroglu (2019-10-14 10:18:55)
Offline
Happy to revive an old thread.
I've never worked with Javascript but started to use Flutter (Dart language) and I must say it's starting to grow on me.
I struggled to get authentication to a mORMot server working and almost posted here a few times.
Many thanks to the posters above, I couldn't have done it without you.
FWIW, if anyone need to auth to mORMot from Dart:
import 'dart:convert';
import 'package:archive/archive.dart';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart';
main() async {
MORMOT_client http_client = MORMOT_client('http://10.1.1.200:8092/','main/');
await http_client.login('User', 'synopse');
final List<dynamic> products = await http_client.GetList('product');
print(products);
final Map<String,dynamic> product = await http_client.GetItem('product/106');
print(product);
}
class MORMOT_client {
MORMOT_client(String this.SERVER_URL,String this.SERVER_MODEL){}
String SERVER_URL = '';
String SERVER_MODEL = '';
String SESSION_ID = '';
String SESSION_PRIVATEKEY = '';
String SESSION_USERNAME = '';
int SESSION_TICK_COUNT = 1;
int SESSION_START_TIME = 0;
login(String userName, String userPass) async {
String clientnonce = '';
String servernonce = '';
String password = '';
String hashedPassword = '';
SESSION_ID = '';
SESSION_PRIVATEKEY = '';
SESSION_USERNAME = userName;
SESSION_TICK_COUNT = 1;
SESSION_START_TIME = DateTime.now().millisecondsSinceEpoch;
clientnonce = SHA256((DateTime.now().millisecondsSinceEpoch ~/ (1000 * 60 * 5)).toString()).toString();
final Map<String, dynamic> sessionResult = await urlToMap(SERVER_URL+SERVER_MODEL+'auth?Username=$userName');
if (sessionResult.isEmpty) {return;}
servernonce = sessionResult['result'];
hashedPassword = SHA256('salt' + userPass).toString();
password = SHA256('main' + servernonce + clientnonce + userName + hashedPassword).toString();
final Map<String, dynamic> authResult = await urlToMap(SERVER_URL+SERVER_MODEL+'auth?Username=$userName&Password=$password&ClientNonce=$clientnonce');
if (authResult.isEmpty) {return;}
String session_str = authResult['result'].toString();
SESSION_ID = int.parse(session_str.split('+')[0]).toRadixString(16).padLeft(8, '0');
SESSION_PRIVATEKEY = session_str + hashedPassword;
print('SID:$SESSION_ID');
print('SPK:$SESSION_PRIVATEKEY');
}
String GetSessionSignature(String url) {
String? prefix;
String? nonce;
String? keynonce;
String? final_SIGN;
SESSION_TICK_COUNT = DateTime.now().millisecondsSinceEpoch - SESSION_START_TIME;
print('SessionTickCount:$SESSION_TICK_COUNT');
nonce = SESSION_TICK_COUNT.toRadixString(16).padLeft(8, '0');
keynonce = getCrc32(utf8.encode(SESSION_PRIVATEKEY + nonce + SERVER_MODEL+url)).toRadixString(16).padLeft(8, '0');
print('nonce :$nonce');
print('keynonce:$keynonce');
return SESSION_ID + nonce + keynonce;
}
Future<List<dynamic>> GetList(String url)async{
final String signature = GetSessionSignature(url);
final String prefix = (url.indexOf('?')>0) ? '&' : '?';
final String theUrl = SERVER_URL+SERVER_MODEL+url+prefix+'session_signature=$signature';
print('URL:$theUrl');
final List<dynamic> auth2 = await urlToList(theUrl);
print(auth2);
return auth2;
}
Future<Map<String,dynamic>> GetItem(String url)async{
final String signature = GetSessionSignature(url);
final String prefix = (url.indexOf('?')>0) ? '&' : '?';
final String theUrl = SERVER_URL+SERVER_MODEL+url+prefix+'session_signature=$signature';
print('URL:$theUrl');
final Map<String,dynamic> item = await urlToMap(theUrl);
print(item);
return item;
}
}
Digest SHA256(String value){
var bytes = utf8.encode(value);
return sha256.convert(bytes);
}
Future<Map<String,dynamic>> urlToMap(String url)async {
Response r = await get(Uri.parse(url));
if (r.statusCode != 200) {
print(r.statusCode);
print(r.body);
final Map<String,dynamic> tmp = {};
return tmp;
}
final Map<String, dynamic> res = jsonDecode(r.body);
return res;
}
Future<List<dynamic>> urlToList(String url)async {
Response r = await get(Uri.parse(url));
if (r.statusCode != 200) {
print(r.statusCode);
print(r.body);
final List<dynamic> tmp = [];
return tmp;
}
final List<dynamic> res = jsonDecode(r.body);
return res;
}
It is a very raw testing unit, needs exception handling etc.
Output an array(list) and object(map):
[{ID: 1}, {ID: 3}, {ID: 4}, {ID: 5}, {ID: 10},...
{ID: 106, Created: 0, Modified: 135522307659, CODE: SLUC40MBPS, DESCRIPTION: SLUC40MBPS...
Last edited by AntonE (2021-07-17 02:25:06)
Offline
Good day
Is it possible to invalidate an already created user session on the server if the same user login again on another device, browser, etc. let's say the user logs in on one computer, he has his sessionkey for signing requests, then the same user logs in on another computer and now gets another session key and is making signed requests from two places, is it possible that on the second login the first session is invalidated ?
Offline
The same user credential could be used in several places at the same time, each connection with its own session.
This is a feature of the current implementation, and changing it could break a lot of existing code.
So a single session per user could be added as an option.
Do you still use mORMot 1?
Offline
The same user credential could be used in several places at the same time, each connection with its own session.
This is a feature of the current implementation, and changing it could break a lot of existing code.
Ok, I thought maybe I could do it by changing the value of some property I can possibly do it in some server event, any suggestions?
So a single session per user could be added as an option.
Do you still use mORMot 1?
Yes, I'm still on 1
Offline
You have the reOneSessionPerUser execution right which does that:
// - reOneSessionPerUser will force that only one session may be created
// for one user, even if connection comes from the same IP: in this case,
// you may have to set the SessionTimeOut to a small value, in case the
// session is not closed gracefully
Offline
I will explore that feature
Thank you !
Offline
Hi Ab,
I somehow missed these messages...
What is your feedback with Flutter?
Is it easy to work with?
Which tutorial do you recommend?
I think Dart and Flutter is amazing and getting better with updates almost every day.
The best tutorial I saw is on AppBrewery "The Complete 2021 Flutter Development Bootcamp with Dart", Flutter/Dart changed a bit since that course, especially with null-safety, but it's not to bad to figure out what need to change. Really good progression into Dart and Flutter, covering basic design all the way to using and writing APIs, Interfaces, Services (in Delphi-speak).
The whole idea of 'Widgets' in Flutter to dynamically build the UI elements (sometimes on each frame), seems weird at first, but it is so to create dynamic, multi-platform apps. Everything, even an Integer is an Object and everything in the UI is a Widget, so the language get really expressive and compact (sometimes).
I'm not turning back, no more expensive Delphi licenses, which I only need for client-side UI apps, but thanks to mORMot, not for server![8-}>
https://appbrewery.com/courses/enrolled/548873
I have not tested/deployed a full-blown non-debug app, to see performance, etc, yet; Almost...
Good luck!
Anton E
Last edited by AntonE (2022-08-02 07:43:13)
Offline