#1 2013-12-09 16:10:18

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

How to get the current user?

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

#2 2013-12-09 18:18:45

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: How to get the current user?

What is the server error?
Could you run it in a debugger and find where it breaks in the source code?

Offline

#3 2013-12-09 19:28:22

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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

#4 2013-12-09 19:34:53

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: How to get the current user?

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).

Offline

#5 2013-12-09 20:06:20

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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... sad
Any help??

Last edited by jvillasantegomez (2013-12-09 20:20:37)

Offline

#6 2013-12-09 20:18:35

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: How to get the current user?

What is your TResponse type?

Offline

#7 2013-12-09 20:25:36

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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

#8 2013-12-09 21:35:50

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: How to get the current user?

What is the "Par" variable content?

Offline

#9 2013-12-09 21:40:39

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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

#10 2013-12-09 21:46:58

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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

#11 2013-12-09 22:38:41

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: How to get the current user?

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

Offline

#12 2013-12-10 13:22:58

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

Thank you!!

Offline

#13 2013-12-10 17:22:54

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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

#14 2013-12-10 18:42:08

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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

#15 2013-12-11 07:39:18

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: How to get the current user?

Up to now I was not able to reproduce the error.

How do you register the service?

Do you have some simple code to reproduce it?

Offline

#16 2013-12-11 13:34:02

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

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

#17 2013-12-11 17:46:57

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: How to get the current user?

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.

Offline

#18 2013-12-11 21:44:27

jvillasantegomez
Member
From: Ciudad Habana. Cuba.
Registered: 2013-10-21
Posts: 54

Re: How to get the current user?

I think that sicPerSession it's the best fit for me, done some testing and everything is working so far...

best regards...

Offline

Board footer

Powered by FluxBB