#51 Re: mORMot 1 » Javascript authentication » 2018-12-14 18:19:16

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');
});

#52 Re: mORMot 1 » Seesion data and group timeout » 2018-11-22 17:48:44

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.

#53 mORMot 1 » Seesion data and group timeout » 2018-11-22 16:02:29

imperyal
Replies: 2

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!

#54 Re: mORMot 1 » Prevent key violation on foreign keys » 2018-10-25 17:31:18

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.

#55 Re: mORMot 1 » Prevent key violation on foreign keys » 2018-10-25 12:10:41

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.

#56 Re: mORMot 1 » Firebird via Zeos no user table and unique fields » 2018-10-23 20:38:46

On my experiments I added the Auth tables to my model. That fixed the issue. Looks like the behavior is different when using SQLite, the Auth tables are created if you are using authentication even if you don’t add the tables to the model... Hope that helps.

#57 mORMot 1 » Prevent key violation on foreign keys » 2018-10-23 16:00:37

imperyal
Replies: 3

Hello!

Another simple question... How do I prevent the insertion of an invalid ID on a table that has a reference to another one (foreign key)?

My test tables:

  TSQLClienteRecord = class(TSQLRecord)
  private
    fNome:       RawUTF8;
    fIdade:      Int64;
    fNotas:      RawUTF8;
  published
    property Nome:  RawUTF8 index 100        read fNome  write fNome stored AS_UNIQUE;
    property Idade: Int64                    read fIdade write fIdade;
    property Notas: RawUTF8                  read fNotas write fNotas;
  end;


  TSQLEncomendaRecord = class(TSQLRecord)
  private
    fCliente: TSQLClienteRecord;
    fNif:     Int64;
    fMorada:  RawUTF8;
  published
    property fkCli:  TSQLClienteRecord read fCliente  write fCliente;
    property Nif:    Int64             read fNif      write fNif;
    property Morada: RawUTF8           read fMorada   write fMorada;
  end;

When I insert a record on EncomendaRecord using Postman for example, I can send any value to fkCli and no error occurs, and the record is inserted on the table. I expected an error to be automatically raised, preventing the insertion of that invalid foreign key.


And how do I do other validation that raise errors to the client AJAX app, like type differences, or string length, etc...

Thank you!

#58 Re: mORMot 1 » Firebird via Zeos no user table and unique fields » 2018-10-22 18:07:15

I can´t see the tables on firebird, but the authentication works, I can add users and login with them... I opened all tables in firebird and I can't find the users.

#59 Re: mORMot 1 » Firebird via Zeos no user table and unique fields » 2018-10-20 10:36:42

That makes perfect sense, I was just switching from SQLite to Firebird for some performance testing and that never came to mind.

Do you have any idea way the user tables are not being created on when using Firebird? They are created normally when I switch to SQLite.

Thank you!

#60 mORMot 1 » Firebird via Zeos no user table and unique fields » 2018-10-19 11:15:16

imperyal
Replies: 6

Hello, I'm starting with mORMot and now I'm trying to use a firebird database (Zeos). My AuthUser/AuthGroup tables are not being created, what's missing from that code below?

var
  fbDatabase: TSQLDBZEOSConnectionProperties;
begin
  Model  := CreateDataModel;

  fbDatabase := TSQLDBZEOSConnectionProperties.Create(
                TSQLDBZEOSConnectionProperties.URI(dFirebird, '', '.\Firebird\fbclient.dll', true),
                'fbDB.fdb',
                'sysdba',
                'masterkey');

  fbDatabase.ThreadingMode := tmMainConnection;

  VirtualTableExternalRegisterAll(Model, fbDatabase,[regMapAutoKeywordFields]);

  Server := TSQLRestServerDB.Create(Model, SQLITE_MEMORY_DATABASE_NAME, true);

  Server.CreateMissingTables;

  AddToServerWrapperMethod(Server, ['D:\Dev\mormot\CrossPlatform\templates']);

  HTTPServer := TSQLHttpServer.Create('8080', [Server], '+', useHttpApiRegisteringURI);
  HTTPServer.AccessControlAllowOrigin := '*';

One other difficulty I'm having with Firebird is that I can't index/unique my string fields (RawUTF8), I'm getting errors with the TSQLRecord below:

  TSQLClienteRecord = class(TSQLRecord)
  private
    fNome:  RawUTF8;
    fIdade: Int64;
    fNotas: RawUTF8;
  published
    property Nome:  RawUTF8 read fNome  write fNome stored AS_UNIQUE;
    property Idade: Int64   read fIdade write fIdade;
    property Notas: RawUTF8 read fNotas write fNotas;
  end;

These are some newbie questions I'm sure, sorry... And thank you smile

#61 Re: mORMot 1 » Smart Mobile Studio 3 - Compilation error » 2018-10-19 11:01:05

Nice, I will experiment with that today, thank you.

#62 Re: mORMot 1 » Smart Mobile Studio 3 - Compilation error » 2018-10-18 17:35:49

Maybe I'm not the right guy to help with this...

The mORMotClient.pas seems Ok.

The compilation errors are on the SynCrossPlatformSpecific.pas and SynCrossPlatformREST.pas.
System.Date seems to have been renamed to System.DateUtils?
The VarIsValidRef function does not exists anymore (or so it seems).
The decodeURIComponent function is now part of the class TString, TString.decodeURIComponent.

And finally, this function: function Add(Value: TSQLRecord; SendData: boolean; ForceID: boolean=false; const FieldNames: string=''): TID; virtual;
Is triggering: Syntax Error: const parameter cannot have a default value [line: 804, column: 46, file: SynCrossPlatformREST]

Is there a way to produce a js file to use on a VUE/REACT application? Put in another way, what is the best approach to build a client web app, other than using SMS?

I'm very new to this ORM and things, but I'm liking it so far.

#63 mORMot 1 » Smart Mobile Studio 3 - Compilation error » 2018-10-18 15:12:29

imperyal
Replies: 10

Hello! I'm evaluating mORMot and SMS 3 for a new project.

I tried the CopySynCrossPlatformUnits.bat file to copy the required .pas files to SMS but the locations appear to be wrong. I then copied the files manually to “C:\ProgramData\The Smart Company\Smart Mobile Studio\Libraries” and then SMS was able to find them.
When compiling the mORMotClient.pas file I get the following errors:
Syntax Error: Unit "System.Date" referenced in unit "SynCrossPlatformSpecific" not found [line: 100, column: 12, file: SynCrossPlatformSpecific]
Syntax Error: const parameter cannot have a default value [line: 804, column: 31, file: SynCrossPlatformREST]
Syntax Error: Unknown name "encodeURIComponent" [line: 1439, column: 13, file: SynCrossPlatformREST]

What am I doing wrong?

Thank you.

Board footer

Powered by FluxBB