You are not logged in.
Pages: 1
When writing a client with javascript I want to get the current logged in user whenever the user reloads the page. This is a SPA but the user can reload the page on the browser and I don't wanna him to authenticate again.
I wrote this Service on mormot:
function TUsers.GetCurrent: TResponse;
var
context: TServiceRunningContext;
begin
context := CurrentServiceContext;
Result.data := IntToStr(context.Request.Session);
Result.count := 1;
end;
That work well after authentication, but when the user reloads the page, after authentication, it returns a 500 (Internal Server Error).
Here's what i want to do in javascript:
requestCurrentUser: function() {
var url = Settings.mainUrl + '/Users.GetCurrent';
var token = getSessionSignature(url.substr(Settings.serverUrl.length + 1));
$.ajax({
type: "POST",
dataType: "json",
url: url + '?session_signature=' + token,
contenttype: 'application/json; charset=utf-8',
timeout:2000,
success: function(response, textStatus, jqXHR) {
return currentUser; // As saved on local storage
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
}
});
Here's the use case. A users effectively logs in on our application, mormot accepts his credentials and start a session for him, this session lasts about 60minutes.
The user closes his browers and reopen it, he must be authenticated by default because on the server the session is still valid but when I call the functions above it returns 500 (Internal Server Error)
Best regards
Also, how to effectively do a logout from javascript... this url always fails:
var url = Settings.mainUrl + '/auth' + '?session=' + sessionId + '&UserName=' + userName;
Last edited by jvillasantegomez (2013-12-09 16:39:34)
Offline
The problem is that i don't know how to debbug it.
Mormot is like an onion (or a route middleware for people comming from standard frameworks), it seems that the problem is on the authentication routine so, the request never hits my service implementation. I'm using TSQLRestServerAuthenticationDefault and i don't know where to place the breakpoint to debug this, can you hint me on this?
Also, i don't know how to effectively log out a user and for long running servers it start giving "Invalid pointer operation" exception when I try to access my server. If you hint me about where to start debbugging the authentication stuff i will debug this out and write here.
As a side note, it would be great to support another kind of authentication, HMAC authentication without any server side session (as seen here: http://www.thebuzzmedia.com/designing-a … tication/), this is great for people writting an api, after i debug the problem with TSQLRestServerAuthenticationDefault and solve it I would give a go at writing an hmac authentication for mormot, i've done it dozens of times on php so, it wouldn't be so hard to write on delphi.
Best regards.
Offline
First of all, ensure you got the latest 1.18 revision - e.g. from the nightly package http://synopse.info/files/mORMotNightlyBuild.zip
Then download the latest 1.18 SAD pdf from http://synopse.info/files/pdf/Synopse%2 … 201.18.pdf
There is a whole part describing how authentication is done.
In short, it is class-driven.
Just put a breakpoint in the method's class used for authentication.
Since it is class-driven, any other kind of authentication could be implemented.
In fact, HMAC authentication is very close to the default mORMot model of URI signature, except that it uses CRC32 to sign the URI (much faster than SHA-256).
Online
I understand that.
For now, here's the line that raises the exception on the mORmot unit: Ctxt.ExecuteSOAByInterface
procedure TSQLRestServer.URI(var Call: TSQLRestURIParams);
var Ctxt: TSQLRestServerURIContext;
{$ifdef WITHSTATPROCESS}
timeStart,timeEnd: Int64;
begin
QueryPerformanceCounter(timeStart);
{$else}
begin
{$endif}
inc(fStats.fIncomingBytes,length(Call.url)+length(call.method)+length(call.InHead)+length(call.InBody)+12);
Call.OutInternalState := InternalState; // other threads may change it
Call.OutStatus := HTML_BADREQUEST; // default error code is 400 BAD REQUEST
Ctxt := ServicesRouting.Create(self,Call);
try
{$ifdef WITHLOG}
Ctxt.Log := SQLite3Log.Add;
Ctxt.Log.Enter(Self,pointer(Ctxt.URIWithoutSignature),true);
{$endif}
if Ctxt.Method=mNone then
Ctxt.Error('Unknown VERB') else
// 1. decode URI
if not Ctxt.URIDecodeREST then
Ctxt.Error('Invalid Root',HTML_NOTFOUND) else begin
Ctxt.URIDecodeSOAByMethod;
if (Ctxt.MethodIndex<0) and (Ctxt.URI<>'') and
(reService in Call.RestAccessRights^.AllowRemoteExecute) then
Ctxt.URIDecodeSOAByInterface;
// 2. handle security
if not Ctxt.Authenticate then // 403 in case of authentication failure
// 401 Unauthorized response MUST include a WWW-Authenticate header,
// which is not what we used, so we won't send 401 error code but 403
Call.OutStatus := HTML_FORBIDDEN else
// 3. call appropriate ORM / SOA commands
try
if Ctxt.MethodIndex>=0 then
Ctxt.ExecuteSOAByMethod else
if Ctxt.Service<>nil then
Ctxt.ExecuteSOAByInterface else // <<- this line raises 500 (Internal Server Error)
if Ctxt.Method in [mLOCK,mGET,mUNLOCK,mSTATE] then
// handle read methods
Ctxt.ExecuteORMGet else
// write methods (mPOST, mPUT, mDELETE...) are locked
if AcquireWrite(Ctxt) then // make it thread-safe and transaction-safe
try
Ctxt.ExecuteORMWrite;
finally
ReleaseWrite;
end else
// AcquireWrite(SessionID) returned false (e.g. endless transaction)
Call.OutStatus := HTML_TIMEOUT; // 408 Request Time-out
except
on E: Exception do
Ctxt.Error('Exception %: %',
[PShortString(PPointer(PPtrInt(E)^+vmtClassName)^)^,E.Message],
HTML_SERVERERROR); // 500 internal server error
end;
end;
// 4. returns expected result to the client and update Server statistics
if (Call.OutStatus in [HTML_SUCCESS,HTML_CREATED]) or
(Call.OutStatus=HTML_NOTMODIFIED) then begin
inc(fStats.fResponses);
{$ifdef WITHLOG}
Ctxt.Log.Log(sllServer,'% % -> %',[Call.Method,Ctxt.URI,Call.OutStatus],self);
{$endif}
end else begin
inc(fStats.fInvalid);
if Call.OutBody='' then // if no custom error message, compute it now as JSON
Ctxt.Error(Ctxt.CustomErrorMsg,Call.OutStatus);
end;
inc(fStats.fOutcomingBytes,length(Call.OutHead)+length(Call.OutBody)+16);
if (Call.OutBody<>'') and
(length(Call.OutHead)=Length(STATICFILE_CONTENT_TYPE_HEADER)) and
(Call.OutHead[Length(HEADER_CONTENT_TYPE)+1]='!') and
IdemPChar(pointer(Call.OutHead),STATICFILE_CONTENT_TYPE_HEADER) then
inc(fStats.fOutcomingFiles);
if (Ctxt.Static<>nil) and Ctxt.Static.fOutInternalStateForcedRefresh then
// force always refresh for Static table which demands it
Call.OutInternalState := cardinal(-1) else
// database state may have changed above
Call.OutInternalState := InternalState;
{$ifdef WITHSTATPROCESS}
QueryPerformanceCounter(timeEnd);
inc(fStats.ProcessTimeCounter,timeEnd-timeStart);
{$endif}
finally
Ctxt.Free;
end;
end;
It seems that the authentication works, but the problem is while calling my service implementation...
Going deeper, this line is the one that raises the exception on mORmot Unit:
if not fInterface.fMethods[Ctxt.ServiceMethodIndex].InternalExecute(
[PAnsiChar(Inst.Instance)+entry^.IOffset],Ctxt.ServiceParameters,WR,
Ctxt.Call.OutHead,fExecution[Ctxt.ServiceMethodIndex].Options,
Ctxt.ForceServiceResultAsJSONObject)
And here's the exit command that raises the exception on mORmot unit method: TServiceMethod.InternalExecute
if Par^ in [#1..' '] then repeat inc(Par) until not(Par^ in [#1..' ']);
if Par^<>'[' then
Exit;
That conditions and exit are very cryptic to me...
Any help??
Last edited by jvillasantegomez (2013-12-09 20:20:37)
Offline
My TResponse type is simply a record that i return to the client:
TResponse = record
count: integer;
data: RawUTF8;
end;
with that record i use this global function to return the response to the client:
function TBase.WrapResponseSuccess(response: TResponse; code, status: string): RawUTF8;
begin
Result := JSONEncode([
'code', code,
'status', status,
'count', response.count,
'data', response.data
]);
end;
in my services I always return a TServiceCustom Answer like this:
function TService.MyService(someparam: RawUTF8): TServiceCustomAnswer;
var
response: TResponse;
begin
try
try
response := Self.controller.SomeFunction(someparam);
Result.Header := JSON_CONTENT_TYPE_HEADER;
Result.Content := WrapResponseSuccess(response);
finally
// ...
end;
except
On E: Exception do begin
Result.Header := JSON_CONTENT_TYPE_HEADER;
Result.Content := WrapResponseError(E.Message, E.ClassName);
end;
end;
end;
Offline
I've solved the issue. The Par variable content are the parameters. It seems that when you do a POST even when you don't need to pass any parameters you need to pass and empty array like this:
requestCurrentUser: function() {
var url = Settings.mainUrl + '/Users.GetCurrent';
var token = getSessionSignature(url.substr(Settings.serverUrl.length + 1));
$.ajax({
type: "POST",
dataType: "json",
url: url + '?session_signature=' + token,
data: [],
contenttype: 'application/json; charset=utf-8',
timeout:2000,
success: function(response, textStatus, jqXHR) {
return currentUser; // As saved on local storage
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
}
});
so data: [] would solve the issue right away... Thks for the responses...
Offline
Now remains the issue of how to log out, this url always returns an error: 500 (Internal Server Error)
GET http://localhost:8080/api/auth?UserName=User&session=29886586&session_signature=01c8087ad952578cf587857c
best regards
Offline
I've modified the code to ignore incoming parameters for interface-based services if the method does not expect any of them.
As a consequence, sending '[]' for a method without any input is not mandatory any more.
See http://synopse.info/fossil/info/21d4bd30ec
For the sessions, ensure that you have the latest version, including ticket http://synopse.info/fossil/info/7723fa7ebd
Online
Thank you!!
Offline
Unfortunately, a get request to this URI:
http://localhost:8080/api/auth?UserName=User&session=13165683&session_signature=00c8e473dd7c1cc0e83631c6
fails with:
{"ErrorCode":500,"ErrorText":"ExceptionEInvalidPointer:Invalidpointeroperation"}
The error raises on Line: 33941 of function TServiceFactoryServer.InternalInstanceRetrieve of MormotUnit
// search the instance corresponding to Inst.InstanceID
for i := 0 to fInstancesCount-1 do
with fInstances[i] do
if InstanceID=Inst.InstanceID then begin
if aMethodIndex<0 then begin
// aMethodIndex=-1 for {"method":"_free_", "params":[], "id":1234}
SafeFreeInstance({$ifndef LVCL}fImplementationFreeInMainThread{$endif}); // <-- this line raises the error
result := true; // successfully released instance
exit;
end;
LastAccess := Inst.LastAccess;
Inst.Instance := Instance;
exit;
end;
That line, going deeper, calls this function (an stdcall) on System unit wich produces the error:
function InterlockedDecrement(var Addend: Integer): Integer; stdcall;
external kernel name 'InterlockedDecrement';
Any help?
Best regards...
Last edited by jvillasantegomez (2013-12-10 17:27:12)
Offline
On the other hand, when I let my server running and come back after some time elapsed it would give me the same error as above. This time i debugged the process and ended up here:
Line: 33923 of mormot unit
// first release any deprecated instances
if fInstanceTimeout<>0 then
for i := fInstancesCount-1 downto 0 do
with fInstances[i] do
if InstanceID<>0 then
if (Inst.LastAccess<LastAccess) or
(QWord(Inst.LastAccess)>QWord(LastAccess)+QWord(fInstanceTimeout)) then begin
// mark this entry is empty
{$ifdef WITHLOG}
SQLite3Log.Add.Log(sllServiceCall,
'Deleted % instance (id=%) after % ms timeout (max % ms)',
[fInterfaceURI,InstanceID,Inst.LastAccess-LastAccess,fInstanceTimeOut],self);
{$endif}
SafeFreeInstance({$ifndef LVCL}fImplementationFreeInMainThread{$endif}); // <-- this line raises the error
end;
I suspect that it is because the session has expired, but it mus return a 403 response and don't raise exception... or better yet, reconnect when asked.
This problem first raises on line
if not Ctxt.Authenticate then
of procedure TSQLRestServer.URI on mormot unit, so, it is effectively related to authentication.
It is worth noting that, after that, i must restart my server for it to work again, so, this is a serious error killing out my server.
Any help???
best regards
Last edited by jvillasantegomez (2013-12-10 18:48:50)
Offline
Here's how I register my services:
aServer.ServiceRegister(TMyService, [TypeInfo(IMyService)], sicPerSession)
.DenyAll.AllowAllByName(['User']);
and the problems that i'm having are the ones stated above. I use a javascript client (written in angularjs) to access my server.
Now, if I change my servers with this:
aServer.ServiceRegister(TMyService, [TypeInfo(IMyService)], sicPerUser)
.DenyAll.AllowAllByName(['User']);
At least the logout works ok, i'll debug the other error soon.
So, it seems that sicPerSession. I'll read the SAD to figure out what "aInstanceCreation" it's better for my services.
Best regards.
Last edited by jvillasantegomez (2013-12-11 13:44:31)
Offline
I've just fixed a potential issue when using sicPerClient kind of interface-based services.
See http://synopse.info/fossil/info/a91b6b9126
It may not be exactly your problem (since I was not able to reproduce the issue yet), but it won't hurt.
Online
I think that sicPerSession it's the best fit for me, done some testing and everything is working so far...
best regards...
Offline
Pages: 1