В статье рассматривается распространенная проблема, с которой сталкиваются разработчики, использующие Delphi для отправки электронных писем через Gmail с использованием OAuth 2.0. После успешного прохождения процесса подтверждения в Google Console и получения необходимых разрешений (scope), пользователи часто получают ошибку "username and password not accepted" при попытке отправить письмо. Данная статья предлагает анализ проблемы и предлагает решения, основанные на обсуждении в сообществе Delphi и Pascal.
Описание проблемы
Проблема возникает после того, как приложение успешно прошло процесс авторизации Google OAuth 2.0 и получило access token и refresh token. Приложение использует полученные токены для аутентификации при отправке письма через SMTP (Simple Mail Transfer Protocol). Однако, несмотря на успешную авторизацию, Gmail отклоняет запрос, выдавая ошибку "username and password not accepted".
Анализ проблемы
Обсуждение на форуме Delphi (emileverh, rvk, Remy Lebeau) выявило несколько потенциальных причин этой ошибки:
Необходимость CASA Tier 2 Security Assessment: Google требует прохождения оценки безопасности CASA Tier 2 для приложений, использующих Gmail API. Отсутствие прохождения этой оценки может привести к ограничению функциональности, включая возможность отправки писем. Однако, прохождение этой оценки сложно для приложений, работающих в автономном режиме (offline Windows application), что, по словам rvk, привело к отказу от процесса.
Неправильный scope: Несмотря на то, что scope https://www.googleapis.com/auth/gmail.send был запрошен и одобрен в Google Console, его использование в SMTP-аутентификации может быть не поддерживается.
Проблема с чекбоксом "Allow send" в Google Console: При прохождении процесса подтверждения в Google Console, не всегда по умолчанию включен чекбокс "Allow send". Необходимо убедиться, что этот чекбокс установлен.
Ограничение на использование gmail.send scope с SMTP: Согласно документации Google и информации, полученной на Stack Overflow, использование ограниченного gmail.send scope непосредственно с SMTP не поддерживается. Требуется использование Gmail API (через REST) для отправки писем.
Решение: Использование Gmail API (REST)
Наиболее надежным решением, подтвержденным сообществом, является переход от использования SMTP с OAuth 2.0 к прямому использованию Gmail API (REST). Это позволяет обойти ограничения, связанные с gmail.send scope и SMTP-аутентификацией.
Пример кода (Object Pascal - Delphi) для отправки письма через Gmail API:
uses
IdHTTP,
IdJSON,
System.NetEncoding;
procedure SendEmailViaGmailAPI(const RecipientEmail: string; const Subject: string; const Body: string; const AttachmentPath: string);
var
HTTP: TIdHTTP;
JSONData: TJSONValue;
AccessToken: string;
URL: string;
Response: string;
begin
HTTP := TIdHTTP.Create(nil);
try
// Замените на ваш Access Token. Реальный код должен получать его из безопасного хранилища.
AccessToken := 'YOUR_ACCESS_TOKEN';
// Создание URL для отправки письма
URL := 'https://gmail.googleapis.com/upload/gmail/v1/users/me/messages/send';
// Создание JSON данных для запроса
JSONData := TJSONObject.Create;
try
JSONData.AddPair('raw', TJSONObject.Create);
JSONData['raw'].AddPair('body', TJSONObject.Create);
JSONData['raw']['body'].AddPair('data', TJSONString.Create(StringToUTF8(Body)));
JSONData['raw']['body'].AddPair('filename', TJSONString.Create('message.eml'));
JSONData['raw']['body'].AddPair('content_type', TJSONString.Create('text/plain'));
// Добавление данных для заголовков
JSONData['raw'].AddPair('headers', TJSONObject.Create);
JSONData['raw']['headers'].AddPair('From', TJSONString.Create('me'));
JSONData['raw']['headers'].AddPair('To', TJSONString.Create(RecipientEmail));
JSONData['raw']['headers'].AddPair('Subject', TJSONString.Create(Subject));
// Добавление вложения (если есть)
if not Trim(AttachmentPath).IsEmpty() then
begin
// Чтение файла вложения
var FileContent: string;
FileContent := TEncoding.UTF8.GetString(TFile.ReadAllBytes(AttachmentPath));
// Добавление данных для вложения
JSONData['raw'].AddPair('attachments', TJSONObject.Create);
JSONData['raw']['attachments'].AddPair('data', TJSONString.Create(StringToUTF8(FileContent)));
JSONData['raw']['attachments'].AddPair('filename', TJSONString.Create(ExtractFileName(AttachmentPath)));
JSONData['raw']['attachments'].AddPair('content_type', TJSONString.Create(GetFileContentType(AttachmentPath)));
end;
// Преобразование JSON в строку
var JSONString: string;
JSONString := TJSONObject.ToString(JSONData);
// Отправка запроса
HTTP.Request.ContentType := 'application/json';
HTTP.Request.Accept := 'application/json';
HTTP.Request.Headers.AddValue('Authorization', 'Bearer ' + AccessToken);
Response := HTTP.Post(URL, JSONString);
// Обработка ответа
if HTTP.Response.StatusCode = 200 then
begin
ShowMessage('Email sent successfully!');
end
else
begin
ShowMessage('Error sending email: ' + HTTP.Response.StatusCode + ' - ' + HTTP.Response.Content);
end;
finally
JSONData.Free;
end;
finally
HTTP.Free;
end;
end;
// Вспомогательная функция для определения типа контента файла
function GetFileContentType(const FileName: string): string;
begin
// Реализация определения типа контента по расширению файла.
// Это пример, и его необходимо расширить для поддержки большего количества типов файлов.
if LowerCase(ExtractFileExt(FileName)) = '.pdf' then
Result := 'application/pdf'
else if LowerCase(ExtractFileExt(FileName)) = '.doc' then
Result := 'application/msword'
else if LowerCase(ExtractFileExt(FileName)) = '.docx' then
Result := 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
else
Result := 'application/octet-stream'; // По умолчанию
end;
Альтернативные решения и рекомендации:
Использование Indy OAuth components: Remy Lebeau отметил, что Indy недавно получил новые компоненты OAuth, которые могут упростить процесс получения и обновления токенов. Однако, для отправки писем потребуется использовать TIdHTTP для взаимодействия с Gmail API.
Использование сторонних библиотек: Существуют сторонние библиотеки для Delphi, которые упрощают работу с Gmail API, предоставляя более удобные интерфейсы.
Проверка настроек Google Console: Убедитесь, что в Google Console правильно настроен scope https://www.googleapis.com/auth/gmail.send и включен чекбокс "Allow send".
App Passwords: Если использование Gmail API (REST) не подходит, можно рассмотреть использование App Passwords для SMTP-аутентификации, как это делал rvk. Однако, следует учитывать, что App Passwords могут быть отключены Google в будущем.
Заключение
Проблема "username and password not accepted" при отправке писем через Gmail с использованием OAuth 2.0 в Delphi часто связана с неправильной конфигурацией scope, необходимостью прохождения CASA Tier 2 Security Assessment или ограничением на использование gmail.send scope с SMTP. Наиболее надежным решением является переход к использованию Gmail API (REST) для отправки писем, что позволяет обойти эти ограничения и обеспечивает более стабильную и предсказуемую работу приложения. Использование Indy OAuth components или сторонних библиотек может упростить процесс интеграции с Gmail API.
Статья посвящена решению проблемы, возникающей у разработчиков Delphi при отправке электронных писем через Gmail с использованием OAuth 2.0, когда после успешной авторизации Gmail отклоняет запрос с ошибкой "username and password not accepted".
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.