7417 workflows i can send emails using the email account (#7431)

- update `send-email.workflow-action.ts` so it send email via the google
sdk
- remove useless `workflow-action.email.ts`
- add `send` authorization to google api scopes
- update the front workflow email step form to provide a
`connectedAccountId` from the available connected accounts
- update the permissions of connected accounts: ask users to reconnect
when selecting missing send permission


![image](https://github.com/user-attachments/assets/fe3c329d-fd67-4d0d-8450-099c35933645)
This commit is contained in:
martmull
2024-10-08 23:29:09 +02:00
committed by GitHub
parent 444cd3f03f
commit f138a1cf6e
30 changed files with 443 additions and 159 deletions

View File

@ -27,6 +27,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { AuthResolver } from './auth.resolver';
@ -52,6 +53,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
OnboardingModule,
WorkspaceDataSourceModule,
ConnectedAccountModule,
FeatureFlagModule,
],
controllers: [
GoogleAuthController,

View File

@ -8,18 +8,33 @@ import {
import { GoogleAPIsOauthExchangeCodeForTokenStrategy } from 'src/engine/core-modules/auth/strategies/google-apis-oauth-exchange-code-for-token.auth.strategy';
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TokenService } from 'src/engine/core-modules/auth/token/services/token.service';
@Injectable()
export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
'google-apis',
) {
constructor(private readonly environmentService: EnvironmentService) {
constructor(
private readonly environmentService: EnvironmentService,
private readonly featureFlagService: FeatureFlagService,
private readonly tokenService: TokenService,
) {
super();
}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const state = JSON.parse(request.query.state);
const { workspaceId } = await this.tokenService.verifyTransientToken(
state.transientToken,
);
const isGmailSendEmailScopeEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsGmailSendEmailScopeEnabled,
workspaceId,
);
if (
!this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') &&
@ -34,6 +49,7 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
new GoogleAPIsOauthExchangeCodeForTokenStrategy(
this.environmentService,
{},
isGmailSendEmailScopeEnabled,
);
setRequestExtraParams(request, {

View File

@ -8,10 +8,17 @@ import {
import { GoogleAPIsOauthRequestCodeStrategy } from 'src/engine/core-modules/auth/strategies/google-apis-oauth-request-code.auth.strategy';
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TokenService } from 'src/engine/core-modules/auth/token/services/token.service';
@Injectable()
export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
constructor(private readonly environmentService: EnvironmentService) {
constructor(
private readonly environmentService: EnvironmentService,
private readonly featureFlagService: FeatureFlagService,
private readonly tokenService: TokenService,
) {
super({
prompt: 'select_account',
});
@ -20,6 +27,15 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const { workspaceId } = await this.tokenService.verifyTransientToken(
request.query.transientToken,
);
const isGmailSendEmailScopeEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsGmailSendEmailScopeEnabled,
workspaceId,
);
if (
!this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED') &&
!this.environmentService.get('CALENDAR_PROVIDER_GOOGLE_ENABLED')
@ -30,12 +46,17 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
);
}
new GoogleAPIsOauthRequestCodeStrategy(this.environmentService, {});
new GoogleAPIsOauthRequestCodeStrategy(
this.environmentService,
{},
isGmailSendEmailScopeEnabled,
);
setRequestExtraParams(request, {
transientToken: request.query.transientToken,
redirectLocation: request.query.redirectLocation,
calendarVisibility: request.query.calendarVisibility,
messageVisibility: request.query.messageVisibility,
loginHint: request.query.loginHint,
});
const activate = (await super.canActivate(context)) as boolean;

View File

@ -33,6 +33,9 @@ import {
MessagingMessageListFetchJobData,
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { getGoogleApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
@Injectable()
export class GoogleAPIsService {
@ -44,6 +47,7 @@ export class GoogleAPIsService {
private readonly calendarQueueService: MessageQueueService,
private readonly environmentService: EnvironmentService,
private readonly accountsToReconnectService: AccountsToReconnectService,
private readonly featureFlagService: FeatureFlagService,
) {}
async refreshGoogleRefreshToken(input: {
@ -95,6 +99,13 @@ export class GoogleAPIsService {
const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId);
const isGmailSendEmailScopeEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsGmailSendEmailScopeEnabled,
workspaceId,
);
const scopes = getGoogleApisOauthScopes(isGmailSendEmailScopeEnabled);
await workspaceDataSource.transaction(async (manager: EntityManager) => {
if (!existingAccountId) {
await connectedAccountRepository.save(
@ -105,6 +116,7 @@ export class GoogleAPIsService {
accessToken: input.accessToken,
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
},
{},
manager,
@ -146,6 +158,7 @@ export class GoogleAPIsService {
{
accessToken: input.accessToken,
refreshToken: input.refreshToken,
scopes,
},
manager,
);

View File

@ -4,6 +4,7 @@ import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-google-oauth20';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { getGoogleApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes';
export type GoogleAPIScopeConfig = {
isCalendarEnabled?: boolean;
@ -18,14 +19,9 @@ export class GoogleAPIsOauthCommonStrategy extends PassportStrategy(
constructor(
environmentService: EnvironmentService,
scopeConfig: GoogleAPIScopeConfig,
isGmailSendEmailScopeEnabled = false,
) {
const scopes = [
'email',
'profile',
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/calendar.events',
'https://www.googleapis.com/auth/profile.emails.read',
];
const scopes = getGoogleApisOauthScopes(isGmailSendEmailScopeEnabled);
super({
clientID: environmentService.get('AUTH_GOOGLE_CLIENT_ID'),

View File

@ -15,8 +15,9 @@ export class GoogleAPIsOauthExchangeCodeForTokenStrategy extends GoogleAPIsOauth
constructor(
environmentService: EnvironmentService,
scopeConfig: GoogleAPIScopeConfig,
isGmailSendEmailScopeEnabled = false,
) {
super(environmentService, scopeConfig);
super(environmentService, scopeConfig, isGmailSendEmailScopeEnabled);
}
async validate(

View File

@ -13,8 +13,9 @@ export class GoogleAPIsOauthRequestCodeStrategy extends GoogleAPIsOauthCommonStr
constructor(
environmentService: EnvironmentService,
scopeConfig: GoogleAPIScopeConfig,
isGmailSendEmailScopeEnabled = false,
) {
super(environmentService, scopeConfig);
super(environmentService, scopeConfig, isGmailSendEmailScopeEnabled);
}
authenticate(req: any, options: any) {
@ -22,6 +23,7 @@ export class GoogleAPIsOauthRequestCodeStrategy extends GoogleAPIsOauthCommonStr
...options,
accessType: 'offline',
prompt: 'consent',
loginHint: req.params.loginHint,
state: JSON.stringify({
transientToken: req.params.transientToken,
redirectLocation: req.params.redirectLocation,

View File

@ -0,0 +1,17 @@
export const getGoogleApisOauthScopes = (
isGmailSendEmailScopeEnabled = false,
) => {
const scopes = [
'email',
'profile',
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/calendar.events',
'https://www.googleapis.com/auth/profile.emails.read',
];
if (isGmailSendEmailScopeEnabled) {
scopes.push('https://www.googleapis.com/auth/gmail.send');
}
return scopes;
};

View File

@ -9,6 +9,7 @@ type GoogleAPIsRequestExtraParams = {
redirectLocation?: string;
calendarVisibility?: string;
messageVisibility?: string;
loginHint?: string;
};
export const setRequestExtraParams = (
@ -20,6 +21,7 @@ export const setRequestExtraParams = (
redirectLocation,
calendarVisibility,
messageVisibility,
loginHint,
} = params;
if (!transientToken) {
@ -42,4 +44,7 @@ export const setRequestExtraParams = (
if (messageVisibility) {
request.params.messageVisibility = messageVisibility;
}
if (loginHint) {
request.params.loginHint = loginHint;
}
};

View File

@ -12,4 +12,5 @@ export enum FeatureFlagKey {
IsWorkspaceFavoriteEnabled = 'IS_WORKSPACE_FAVORITE_ENABLED',
IsSearchEnabled = 'IS_SEARCH_ENABLED',
IsWorkspaceMigratedForSearch = 'IS_WORKSPACE_MIGRATED_FOR_SEARCH',
IsGmailSendEmailScopeEnabled = 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED',
}