Jump to content

emileverh

Members
  • Content Count

    94
  • Joined

  • Last visited

  • Days Won

    2

Posts posted by emileverh


  1. Just now, Remy Lebeau said:

    Indy recently got a few new OAuth components that work with SMTP/POP3/IMAP4 SASL-based authentication.  However, getting/refreshing a token is up to you to handle separately via each OAuth provider's API (via HTTP/REST, etc), but once you have a token then Indy can login with it.  Geoffrey Smith's example predates those components, but he handles both REST and SASL portions.  Indy doesn't have any components to handle the REST portion for you, but its TIdHTTP component can certain be used to send REST requests.

    "...Indy recently got a few new OAuth components...." In Delphi 12.2?


  2. 1 hour ago, rvk said:

    If you didn't go through the CASA Tier 2 then you are still in "Testing" phase. But that's ok and indeed perhaps off topic.

     

    Did you add scope https://www.googleapis.com/auth/gmail.send when asking for authentication? I know you said you added it to your consent screen but you also need to provide that scope (in your code) when going through the OAuth2 process.

     

    And second... when entering the consent screen, you need to CHECK the "Allow send" checkbox. It's not checked by default (and if you forget it, you won't have direct send access). I just did this myself again in my program and both my compose draft and send mail checkboxes where not checked by default.

     

    (I don't use the Indy components but Synapse but that shouldn't matter for above issues.)

     

    Yes scope is correct. Else I did not get the final approval.

     

    @Remy Lebeau You as a specialist. Do you know if we can get it working via other components such as TNetHTTPClient? So not for the full scope but for the send-only scope. If you have any good tips for Indy, that is of course also welcome! ( https://www.googleapis.com/auth/gmail.send  )


  3. 3 hours ago, rvk said:

    Did you also pass the "CASA Tier 2 security assessment" for your application? Can you provide the steps?

     

    I tried this a few years ago but they didn't provide any documentation as how to pass this.

    Tier 2 was only documented for online environments and Android Apps (where I would need to provide my app so they could test it).

    But I have an offline Windows application (which they don't seem to take into account).

    I finally gave up on the verification process. (I still get the "not verified" before entering the authentication screen)

     

    Or are you just in the test-phase where you can have a limited number of users (also normally used for internal testing)? (And also have the "not verified" screen)

     

    Regarding the error... I've only used the "AUTHENTICATE XOAUTH2" command with IMAP access to gMail. I see this library tries to do a user Bearer token.

    Did you try creating a new refresh-token (so going through the consent screen again)?

     

     

    CASA Tier 2.... I don't have a clue what that is. And I think also off topic.

     

    Yes, as I wrote, I am verified owner. I did 1000 times go to screen consent screen again.

     

    QUESTION.... ANYBODY: Is there a other way by not using the Indy components. Because gathering the tokens I tried today with a different component from DevExpress ( TdxGoogleAPIOauth2AuthorizationAgent). But I am stuck with 'IdSTMP.Authenticate' as you can see in the first post.

    In other words is there a working example for TNetHTTPClient?!?!?


  4. 1 hour ago, eivindbakkestuen said:

    We can't see your screen, or your code...

    procedure TdtmOAuth.DataModuleCreate(Sender: TObject);
    begin
      var inifilename: string := ChangeFileExt(ParamStr(0), '.ini');
      FIniSettings := TIniFile.Create(inifilename);
      FOAuth2 := TEnhancedOAuth2Authenticator.Create(nil);
      IdHTTPServer.Active := True;
    end;
    
    procedure TdtmOAuth.DataModuleDestroy(Sender: TObject);
    begin
      FreeAndNil(FOAuth2);
    end;
    
    procedure TdtmOAuth.DoLog(const msg: String);
    begin
      CodeSite.Send(msg);
    end;
    
    function TdtmOAuth.HasRefreshToken: Boolean;
    begin
      Result := not FOAuth2.RefreshToken.IsEmpty;
    end;
    
    procedure TdtmOAuth.IdConnectionReceive(ASender: TIdConnectionIntercept; var ABuffer: TIdBytes);
    begin
      DoLog('R:' + TEncoding.ASCII.GetString(ABuffer));
    end;
    
    procedure TdtmOAuth.IdConnectionSend(ASender: TIdConnectionIntercept; var ABuffer: TIdBytes);
    begin
      DoLog('S:' + TEncoding.ASCII.GetString(ABuffer));
    end;
    
    procedure TdtmOAuth.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    var code: string;
    begin
      if ARequestInfo.QueryParams = '' then
        Exit;
    
      var url: TURI := TURI.Create('https://localhost/?' + ARequestInfo.QueryParams);
      try
        code := url.ParameterByName['code'];
      except
        Exit;
      end;
      FOAuth2.AuthCode := code;
      FOAuth2.ChangeAuthCodeToAccesToken;
    
      // AResponseInfo.ContentText :=     '<html><body onload="setTimeout(window.close(), 5000);"><h1>PERIFACT: U kunt de browser nu sluiten en terugkeren naar het programma/ You can close the browser and return to the program</h1></body></html>';
      AResponseInfo.ContentText := '<html><body><h1>PERIFACT: U kunt de browser nu sluiten en terugkeren naar het programma/ You can close the browser and return to the program</h1></body></html>';
    
      var tokennamestr: string := Provider.AuthName + 'Token';
      FIniSettings.WriteString('Authentication', tokennamestr, FOAuth2.RefreshToken);
      DoLog('Authenticated via OAUTH2');
      DoLog(FOAuth2.RefreshToken);
      SetupAuthenticator;
    end;
    
    function TdtmOAuth.IsAuthenticated: Boolean;
    begin
      Result := FIsAuthenticated;
    end;
    
    procedure TdtmOAuth.AuthenticateInBrowser;
    begin
      var uri: TURI := TURI.Create(FOAuth2.AuthorizationRequestURI);
      ShellExecute(0, 'open', PChar(uri.ToString), nil, nil, 0);
    end;
    
    procedure TdtmOAuth.ClearAuthentication;
    begin
      // Delete persistent Refresh_token.  Note
      // - This probably should have a logout function called on it
      // - The token should be stored in an encrypted way ... but this is just a demo.
      FIniSettings.DeleteKey('Authentication', Provider.TokenName);
      SetupAuthenticator;
    end;
    
    procedure TdtmOAuth.SendMessage;
    var IdMessage: TIdMessage; xoauthSASL: TIdSASLListEntry;
    begin
      // if we only have refresh_token or access token has expired
      // request new access_token to use with request
      FOAuth2.ClientID := Provider.ClientID;
      FOAuth2.ClientSecret := Provider.ClientSecret;
    
      FOAuth2.RefreshAccessTokenIfRequired;
    
      DoLog('refresh_token=' + FOAuth2.RefreshToken);
      DoLog('access_token=' + FOAuth2.AccessToken);
    
      if FOAuth2.AccessToken.Length = 0 then
      begin
        DoLog('Failed to authenticate properly');
        Exit;
      end;
    
      IdSMTP.Host := Provider.SmtpHost;
      IdSMTP.UseTLS := Provider.TLS;
      IdSMTP.Port := Provider.SmtpPort;
    
      xoauthSASL := IdSMTP.SASLMechanisms.Add;
      xoauthSASL.SASL := Provider.AuthenticationType.Create(nil);
    
      TIdSASLOAuthBase(xoauthSASL.SASL).Token := FOAuth2.AccessToken;
      TIdSASLOAuthBase(xoauthSASL.SASL).User := Provider.ClientAccount;
    
      IdSSLIOHandlerSocketSMTP.SSLOptions.SSLVersions := [sslvTLSv1_2];
    
      IdSMTP.Connect;
    
      IdSMTP.AuthType := satSASL;
      IdSMTP.Authenticate;		// <<<<<<<<<<<< HERE IT FAILS!!! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    
      IdMessage := TIdMessage.Create(Self);
      IdMessage.From.Address := Provider.ClientAccount;
      IdMessage.From.Name := CLIENTNAME;
      IdMessage.ReplyTo.EMailAddresses := IdMessage.From.Address;
      IdMessage.Recipients.Add.Text := CLIENTSENDTOADDRESS;
      IdMessage.Subject := 'Hello World';
      IdMessage.Body.Text := 'Hello Body';
    
      IdSMTP.Send(IdMessage);
      IdSMTP.Disconnect;
      ShowMessage('Message Sent');
    end;

     


  5. Hi team!

     

    I have download the great github example from Geoffrey Smith https://github.com/geoffsmith82/GmailAuthSMTP/. So in short, I want to send (and send only) for clients who have a Google account

    And it worked fine. So I went to the verification approval process of Google and I passed 😉 

     

    I asked for the scope in Google Console: https://www.googleapis.com/auth/gmail.send  because I want to send (and sending only)  PDF's from my application (I have an invoicing app). But after the approval I get the error message "username and password not accepted". I looked at StackOverflow, Google, Deepseek, etc. to find an answer. I am spending already lots of time in this.... 

     

    Anyone an idea? 


  6. 11 hours ago, Cristian Peța said:

    Have you tried FideDAC Mapping?

    From the link provided by Dmitry I see two thing you can try (specify type name in column alias and mapping):

     

    Hmm this feels good. I think we are in the right direction. I am gonna try this and come back with the results!


  7. 10 minutes ago, John R. said:

    I was mentioning another tool to make sure that the problem is actually in your Delphi app, not in your SQLite DB. As an example, a SQLite trigger could cause such problems. But if you've already eliminated that possibility (which was not clear from your original message), then the problem is clearly in your Delphi code somewhere.

    Or in a Delphi connection-setting (TFDConnection) which I don't know yet....


  8. 35 minutes ago, John R. said:

    I would start with manual testing by crafting SQL queries which goes over the limits of your table definition with a tool such as SQLiteSpy: https://www.yunqa.de/delphi/apps/sqlitespy/index

    If that works from there, it probably means that the limit is still defined somewhere in your Delphi app. Searching for "varchar(256)" or "256" in your *.pas and *.dfm files might be useful, depending on your project's size.

    What an other tool does, says nothing about Delphi. And the search for '256' I already... so still no result ;-((


  9. I have a SQLite table. Which has a column 'Description' and is initially defined as varchar(256). But that limitation is too small for the customers and I want to go to varchar(1024). According to the SQLite website they don't care;  

    "SQLite does not enforce the length of a VARCHAR. You can declare a VARCHAR(10) and SQLite will be happy to store a 500-million character string there. And it will keep all 500-million characters intact. Your content is never truncated. SQLite understands the column type of "VARCHAR(N)" to be the same as "TEXT", regardless of the value of N.""

     

    I changed in the TFDQuery the field .Size definition of that field from 256 to 1024 in the Fields-editor. But still Delphi cuts of my string to 256 chars. When I Google I see all kinds of scripts of creating a temp-table and redefine the thing. I don't want to do that. Question; is there a property (somewhere, no size checking?!?) which I can change so that Delphi does not cut of the string?

     

    Thanks!

    -Emile

    CREATE TABLE IF NOT EXISTS cardlines (
      UniquePK INTEGER PRIMARY KEY AUTOINCREMENT,
      Description varchar(256) NOT NULL,
    ........

     


  10. If I was you in SQLite I defined 'database_id' as AUTOINCREMENT. No duplicates anymore!

     

    CREATE TABLE databases
    (
       database_id INTEGER PRIMARY KEY AUTOINCREMENT

     

    And if you want to know the last inserted id, call the following statement: select LAST_INSERT_ID() as seq

    • Like 2

  11. The final solution is different than proposed. I did not know but the SUM() function can return NULL values. So on larger datasets I did get 'WideString'!!! conversion error messages on SQLite. So I had to use IFNULL.

    May be not for all the most elegant solution. But this TFDQUERY works now for both MySQL and SQLite

     

    select IFNULL(SUM(AmountValue),0) AS SumVal
    from camtentries

     

    And in the code:

     

    var mycurr: currency := qryMyQuery.FieldByName('SumValue').Value;

     

    Normally I don't work with .FieldByName and .Value. I prefer this: 

     

    var mycurr: currency := qryMyQuerySumValue.AsCurrency;

     

    But know the app is not complaining. I have hundreds of queries, but only 2 SUM()'s. So I can live with that. I don't use them in a loop, so performance is not an issue. And I develop the app with MySQL. Because I can easy test my data together with HeidiSQL. The field names become also strange

    select IFNULL(SUM(AmountValue),0) AS "SumVal::DOUBLE:" will be  qryMyQuerySumValueDOUBLE.AsCurrency;

     

    Although the first answers where not the implemented one. I want to thank all the people in general on this forum. As solo entrepreneur I don't have a collegae to ask. So thank you all!  

    • Like 1

  12. 2 hours ago, emileverh said:

    Nice. But this is overwhelming, I don't see any practical info for me now....

    I am sorry! Yes it was a bit overwhelming, but you helped me. So thanks 😉 I have a direction now.

    The code as shown on the EMB website does not compile. With this code it compiles for both versions..... But I am losing data? The dtFloat type does not exist. I am accurate enough with this code? Please no discussions about the "with"-statements, I need to rewrite this....

     

    Thanks in advance!
     

      with qrySumPositive.FormatOptions.MapRules.Add do  // Need to add the .Add else it does not compile
      begin
        SourceDataType := dtDouble;  // <<<< THIS IS A GUESS. THE TYPE dtFLOAT DOES NOT EXISTS
        TargetDataType := dtFmtBCD;
      end;
      qrySumPositive.FormatOptions.OwnMapRules := true;
    
    
    
      with qrySumPositive do
      begin
        ParamByName('prmDateStart').AsDateTime := MyStartOfaDay(ADateStart);
        ParamByName('prmDateEnd').AsDateTime := MyEndOfaDay(ADateEnd);
        Open;
        Result := qrySumPositiveSumVal.AsCurrency;
        Close;
      end;
    

     


  13. Team!

     

    I have one large application which I compile twice, one is for MySQL and the other for SQLite. For regular queries like select * from everything goes fine. But it goes wrong with SUM() functions.

    The query defined in a TFDQuery below works fine in MySQL, but when I compile the application for SQLite I get an exception "Type mismatch for field 'SumVal', expecting: FMTBcd actual: Float'. "

     

    The definition of AmountValue in the database is:   AmountValue DECIMAL(13,4) NOT NULL

     

    And here is the query:

     

    select SUM(AmountValue) AS SumVal
    from camtentries


     

    Is there a property for this one and only TFDQuery or for the field qrySumPositiveSumVal.AsCurrency where it compiles fine in both applications? I prefer not to set an overall property in the TFDConnection.

     

    Thanks!!!

    -Emile


  14. 39 minutes ago, Remy Lebeau said:

    Use the Win32 SHFileOperation() API to copy the file with the FOF_RENAMEONCOLLISION flag:

     If you need to know what the copied filename was renamed to, include the FOF_WANTMAPPINGHANDLE flag as well:

    Alternatively, on Windows Vista+, you can use the IFileOperation API instead, using IFileOperation.SetOperationFlags() to set the FOF_RENAMEONCOLLISION flag, IFileOperation.CopyItem() and IFileOperation.PerformOperations() to perform the copy, and if needed use an IFileOperationProgressSink event sink to discover the copied filename in the PostCopyItem event.

    Thanks Remy for your help!!!


  15. Just now, Remy Lebeau said:

    You cannot delete a file that is in use, unless the app that is using the file explicitly permits the deletion (by specifying FILE_SHARE_DELETE when opening the file). Nobody does that under normal usage, so you will need to detect when this condition happens (check GetLastError() when the deletion fails) and then. either 1) retry the deletion later or 2) ask Windows to delete the file on the next reboot (via MoveFileEx(MOVEFILE_DELAY_UNTIL_REBOOT)).

    You are right force deleting a file is not a good idea. The other (unknown for me) process needs the file. Any idea if there is an API to add a kind of duplication number like: file (1).txt, file (2).txt, file (3).txt ? 

    I did Google something but I did not find 1-2-3 an API for that. Anybody knows?


  16. 17 minutes ago, Lajos Juhász said:

    You should check GetLastError to see the reason why it fails. https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile

     

    One of the possible reason is that the file doesn't exists and the other is that the file is in use by other application.

    Thanks and agree! I had to check if the file exists. But the DeleteFile() call was recently added. In previous versions of my app it failed on the next statement TFIle.Copy().  So again; is there a way to force delete a file? 


  17. Hi!

     

    I need to copy a file to the Windows-temp folder. But on some of my clients it fails. I can not reproduce it, but when they open a mail with attachment in Outlook and my app is working on the same filename it fails. The routine below fails.

     

    Please do not focus on Outlook, but in general. How can I 'force delete' a file? Or how can I safe save a file to the temp folder without problems? Any suggestions? Please help!  

    -Emile

    function TdtmBoot.CopyFileToTempFolder(const AFullFileName: TFileName): string;
    begin
      var lFileName: string := ExtractFileName(AFullFileName);
      var lTempFolderFullFileName: string := GetTempFolder + lFileName;
    
      try
        if DeleteFile(lTempFolderFullFileName) = False then; // @17.22
        begin
          SendMadBugReport(Self, 'CopyFileToTempFolder(), delete failed');  // <<<<<<<< HERE IT FAILS!!!!!!!
        end;
      except
      end;
    
      Result := lTempFolderFullFileName;
      try
        TFile.Copy(AFullFileName, lTempFolderFullFileName, true); // true = overwrite
        Result := lTempFolderFullFileName;
      except
      end;
    end;

     


  18. 8 hours ago, johnnydp said:

    Delphi suffers from lack of popularity and thus lack of adequate funding.

    Too many things at once (mac, linux, mobile, fmx, vcl, c++ builder).
    Ideally, it would be as if the company would choose some one way and focus all attention and power on it, increasing quality and performance. 
    Delphi needs:

    - Roadmap back!
    - Make finally 64 bit IDE, compiler, linker etc. with total abandon of 32 bit(do not wasting time on 32 bit version and focus on one)
    - performance (yes it matters all the time)
    - Improve the quality and speed of bug patching (it's a little better than e.g. 5 years ago, but it's still outrageously slow comparing e.g. to VStudio, where we get some update, hotfix etc. every few weeks.

    - Realise that developers need quality much more than new features (they don't understand it for over 25 years)

     

     

    Agree! Hotfixes and/or faster updates would be nice. Good quality goes above new features! 

    • Like 2
×