feat: SMTP xoauth2 in console (#11545)

# Which Problems Are Solved
This adds the newly added xoauth2 option to the console allowing SMTP
Providers to be setup that use xoauth2 instead of plain auth.

# How the Problems Are Solved
Added the xoauth2 option to the SMTP Provider Settings


# Additional Changes
Heavily refactored the SMTP Provider page to allow more dynamic
configuration and use Tanstack Query for Data Fetching.

# Additional Context
- Closes #11451
- Backend changes #11239
This commit is contained in:
Ramon
2026-02-11 17:12:41 +00:00
committed by GitHub
parent cc7a6145f1
commit ceffb9c536
13 changed files with 975 additions and 869 deletions
@@ -1,41 +1,24 @@
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Location } from '@angular/common';
import { Component, signal } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Subject, take } from 'rxjs';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { Options } from 'src/app/proto/generated/zitadel/idp_pb';
import { Component, computed, effect, inject, linkedSignal, Signal, viewChild } from '@angular/core';
import { FormBuilder, FormControl, Validators } from '@angular/forms';
import { requiredValidator } from '../form-field/validators/validators';
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import {
AddSMTPConfigRequest,
AddSMTPConfigResponse,
TestSMTPConfigRequest,
UpdateSMTPConfigRequest,
UpdateSMTPConfigResponse,
} from 'src/app/proto/generated/zitadel/admin_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
import { ActivatedRoute, Router } from '@angular/router';
import { MatCheckboxChange } from '@angular/material/checkbox';
import {
AmazonSESDefaultSettings,
BrevoDefaultSettings,
GenericDefaultSettings,
GoogleDefaultSettings,
MailchimpDefaultSettings,
MailgunDefaultSettings,
MailjetDefaultSettings,
PostmarkDefaultSettings,
ProviderDefaultSettings,
OutlookDefaultSettings,
SendgridDefaultSettings,
} from './known-smtp-providers-settings';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import * as SMTPKnownProviders from './known-smtp-providers-settings';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { UserService } from '../../services/user.service';
import { injectMutation, injectQuery, QueryFunction } from '@tanstack/angular-query-experimental';
import { NewAdminService } from '../../services/new-admin.service';
import { MessageInitShape } from '@bufbuild/protobuf';
import { AddEmailProviderSMTPRequestSchema, GetEmailProviderByIdResponse } from '@zitadel/proto/zitadel/admin_pb';
import { EMPTY, map, switchMap } from 'rxjs';
import { filter, startWith } from 'rxjs/operators';
import { EmailProviderState } from '@zitadel/proto/zitadel/settings_pb';
import { ToastService } from '../../services/toast.service';
import { MatStepper } from '@angular/material/stepper';
import { SMTPConfigState } from 'src/app/proto/generated/zitadel/settings_pb';
import { TranslateService } from '@ngx-translate/core';
import { ConnectError } from '@zitadel/client';
type Provider = (typeof SMTPKnownProviders)[keyof typeof SMTPKnownProviders];
type State = SMTPProviderComponent['state'] extends Signal<infer T> ? NonNullable<T> : never;
@Component({
selector: 'cnsl-smtp-provider',
@@ -44,330 +27,489 @@ import { TranslateService } from '@ngx-translate/core';
standalone: false,
})
export class SMTPProviderComponent {
public showOptional: boolean = false;
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
public id: string = '';
public providerDefaultSetting: ProviderDefaultSettings = GenericDefaultSettings;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
protected readonly EmailProviderState = EmailProviderState;
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
protected readonly emailQuery = this.buildEmailQuery();
protected readonly email = linkedSignal(() => this.emailQuery.data() ?? '[email protected]');
public smtpLoading: boolean = false;
public hasSMTPConfig: boolean = false;
public isActive: boolean = false;
public updateClientSecret: boolean = false;
protected readonly newAdminService = inject(NewAdminService);
private readonly toast = inject(ToastService);
// stepper
public currentCreateStep: number = 1;
public requestRedirectValuesSubject$: Subject<void> = new Subject();
public firstFormGroup!: UntypedFormGroup;
public secondFormGroup!: UntypedFormGroup;
protected readonly router = inject(Router);
protected readonly location = inject(Location);
private readonly activatedRoute = inject(ActivatedRoute);
private readonly fb = inject(FormBuilder);
public senderEmailPlaceholder = '[email protected]';
protected readonly configOrDefaultsQuery: ReturnType<typeof this.buildConfigOrDefaultsQuery>;
protected readonly state: ReturnType<typeof this.buildState>;
protected readonly updateDataMutation: ReturnType<typeof this.buildUpdateDataMutation>;
protected readonly testEmailConfigurationMutation: ReturnType<typeof this.buildTestEmailConfigurationMutation>;
public resultClass = 'test-success';
public isLoading = signal(false);
public email: string = '';
public testResult: string = '';
protected readonly stepper = viewChild(MatStepper);
protected readonly preselectedStep: ReturnType<typeof this.getPreselectedStep>;
constructor(
private service: AdminService,
private _location: Location,
private fb: UntypedFormBuilder,
private toast: ToastService,
private router: Router,
private route: ActivatedRoute,
private authService: GrpcAuthService,
private translate: TranslateService,
) {
this.route.parent?.url.subscribe((urlPath) => {
const providerName = urlPath[urlPath.length - 1].path;
switch (providerName) {
case 'aws-ses':
this.providerDefaultSetting = AmazonSESDefaultSettings;
break;
case 'google':
this.providerDefaultSetting = GoogleDefaultSettings;
break;
case 'mailgun':
this.providerDefaultSetting = MailgunDefaultSettings;
break;
case 'mailjet':
this.providerDefaultSetting = MailjetDefaultSettings;
break;
case 'postmark':
this.providerDefaultSetting = PostmarkDefaultSettings;
break;
case 'sendgrid':
this.providerDefaultSetting = SendgridDefaultSettings;
break;
case 'mailchimp':
this.providerDefaultSetting = MailchimpDefaultSettings;
break;
case 'brevo':
this.providerDefaultSetting = BrevoDefaultSettings;
break;
case 'outlook':
this.providerDefaultSetting = OutlookDefaultSettings;
break;
constructor() {
this.configOrDefaultsQuery = this.buildConfigOrDefaultsQuery();
this.state = this.buildState(this.configOrDefaultsQuery);
this.updateDataMutation = this.buildUpdateDataMutation(this.configOrDefaultsQuery, this.state);
this.testEmailConfigurationMutation = this.buildTestEmailConfigurationMutation(this.state);
effect(() => {
const error = this.configOrDefaultsQuery.error();
if (error) {
this.toast.showError(error);
}
});
this.preselectedStep = this.getPreselectedStep(this.activatedRoute);
}
private getPreselectedStep(activatedRoute: ActivatedRoute) {
const paramMapSignal = toSignal(activatedRoute.paramMap, { requireSync: true });
return computed(() => {
const paramMap = paramMapSignal();
const step = paramMap.get('step');
if (!step) {
return 0;
}
this.firstFormGroup = this.fb.group({
description: [this.providerDefaultSetting.name],
tls: [{ value: this.providerDefaultSetting.requiredTls, disabled: this.providerDefaultSetting.requiredTls }],
region: [''],
hostAndPort: [
this.providerDefaultSetting?.host
? `${this.providerDefaultSetting?.host}:${this.providerDefaultSetting?.unencryptedPort}`
: '',
],
user: [this.providerDefaultSetting?.user.value || ''],
password: [this.providerDefaultSetting?.password.value || ''],
});
this.senderEmailPlaceholder = this.providerDefaultSetting?.senderEmailPlaceholder || '[email protected]';
this.secondFormGroup = this.fb.group({
senderAddress: ['', [requiredValidator]],
senderName: ['', [requiredValidator]],
replyToAddress: [''],
});
this.region?.valueChanges.subscribe((region: string) => {
this.hostAndPort?.setValue(
`${region}:${
this.tls ? this.providerDefaultSetting?.encryptedPort : this.providerDefaultSetting?.unencryptedPort
}`,
);
});
if (!this.router.url.endsWith('/create')) {
this.id = this.route.snapshot.paramMap.get('id') || '';
if (this.id) {
this.fetchData(this.id);
}
}
this.authService
.getMyUser()
.then((resp) => {
if (resp.user) {
this.email = resp.user.human?.email?.email || '';
}
})
.catch((error) => {
this.toast.showError(error);
});
return Number(step);
});
}
public changeStep(event: StepperSelectionEvent): void {
this.currentCreateStep = event.selectedIndex + 1;
if (event.selectedIndex >= 2) {
this.requestRedirectValuesSubject$.next();
}
}
public close(): void {
this._location.back();
}
public toggleTLS(event: MatCheckboxChange) {
if (this.providerDefaultSetting.host) {
this.hostAndPort?.setValue(
`${this.providerDefaultSetting?.host}:${
event.checked ? this.providerDefaultSetting?.encryptedPort : this.providerDefaultSetting?.unencryptedPort
}`,
);
}
}
private fetchData(id: string): void {
this.smtpLoading = true;
this.service
.getSMTPConfigById(id)
.then((data) => {
this.smtpLoading = false;
if (data.smtpConfig) {
this.isActive = data.smtpConfig.state === SMTPConfigState.SMTP_CONFIG_ACTIVE;
this.hasSMTPConfig = true;
this.firstFormGroup.patchValue({
['description']: data.smtpConfig.description,
['tls']: data.smtpConfig.tls,
['hostAndPort']: data.smtpConfig.host,
['user']: data.smtpConfig.user,
});
this.secondFormGroup.patchValue({
['senderAddress']: data.smtpConfig.senderAddress,
['senderName']: data.smtpConfig.senderName,
['replyToAddress']: data.smtpConfig.replyToAddress,
});
private buildEmailQuery() {
const userQueryOptions = inject(UserService).userQueryOptions();
return injectQuery(() => ({
...userQueryOptions,
select: (user) => {
if (user?.type.case !== 'human') {
return '';
}
})
.catch((error) => {
this.smtpLoading = false;
if (error && error.code === 5) {
this.hasSMTPConfig = false;
}
});
return user.type.value.email?.email ?? '';
},
}));
}
private buildState(configOrDefaultsQuery: typeof this.configOrDefaultsQuery) {
const stateSignal = computed(() => {
const configOrDefaults = configOrDefaultsQuery.data();
if (!configOrDefaults) {
return undefined;
}
return configOrDefaults.case === 'defaults'
? ({ ...this.buildFormFromDefaults(configOrDefaults.defaults) } as const)
: ({ ...this.buildFormFromConfig(configOrDefaults.config) } as const);
});
const authFormSignal = this.buildAuthForm(stateSignal);
return computed(() => {
const state = stateSignal();
const authForm = authFormSignal();
if (!state || !authForm) {
return undefined;
}
return {
...state,
authForm,
} as const;
});
}
private updateData(): Promise<UpdateSMTPConfigResponse.AsObject | AddSMTPConfigResponse.AsObject> {
if (this.hasSMTPConfig) {
const req = new UpdateSMTPConfigRequest();
req.setId(this.id);
req.setDescription(this.description?.value || '');
req.setTls(this.tls?.value ?? false);
private readonly hostnameValidator = Validators.pattern(/.+:[0-9]+/);
if (this.hostAndPort && this.hostAndPort.value) {
req.setHost(this.hostAndPort.value);
private buildFormFromConfig(config: ReturnType<typeof this.getConfig>) {
const mainForm = this.fb.group({
description: new FormControl<string>(config.description, {
nonNullable: true,
validators: [requiredValidator],
}),
user: new FormControl<string>(config.config.value.user, { nonNullable: true, validators: [requiredValidator] }),
host: new FormControl(config.config.value.host, {
nonNullable: true,
validators: [this.hostnameValidator],
}),
tls: new FormControl(config.config.value.tls, { nonNullable: true }),
xoauth2: new FormControl<boolean>(config.config.value.Auth.case === 'xoauth2', { nonNullable: true }),
});
mainForm.controls.xoauth2.disable();
const senderForm = this.buildSenderForm(config.config.value);
return {
mainForm,
senderForm,
senderEmailPlaceholder: '[email protected]',
config,
};
}
private buildFormFromDefaults(defaults?: Provider):
| {
mainForm: typeof mainForm;
senderForm: typeof senderForm;
senderEmailPlaceholder: string;
}
if (this.user && this.user.value) {
req.setUser(this.user.value);
}
if (this.password && this.password.value) {
req.setPassword(this.password.value);
}
if (this.senderAddress && this.senderAddress.value) {
req.setSenderAddress(this.senderAddress.value);
}
if (this.senderName && this.senderName.value) {
req.setSenderName(this.senderName.value);
}
if (this.replyToAddress && this.replyToAddress.value) {
req.setReplyToAddress(this.replyToAddress.value);
}
return this.service.updateSMTPConfig(req);
} else {
const req = new AddSMTPConfigRequest();
req.setDescription(this.description?.value ?? '');
req.setHost(this.hostAndPort?.value ?? '');
req.setSenderAddress(this.senderAddress?.value ?? '');
req.setSenderName(this.senderName?.value ?? '');
req.setReplyToAddress(this.replyToAddress?.value ?? '');
req.setTls(this.tls?.value ?? false);
req.setUser(this.user?.value ?? '');
req.setPassword(this.password?.value ?? '');
return this.service.addSMTPConfig(req);
| {
mainForm: typeof mainForm;
senderForm: typeof senderForm;
senderEmailPlaceholder: string;
defaults: Provider;
} {
const mainForm = this.fb.group({
description: new FormControl<string>(defaults?.description ?? '', {
nonNullable: true,
validators: [requiredValidator],
}),
user: new FormControl<string>(defaults?.user.value ?? '', { nonNullable: true, validators: [requiredValidator] }),
host: new FormControl(defaults?.host ?? '', {
nonNullable: true,
validators: [this.hostnameValidator],
}),
tls: new FormControl<boolean>(true, { nonNullable: true }),
xoauth2: new FormControl<boolean>(defaults?.auth.case === 'xoauth2', { nonNullable: true }),
});
if (defaults) {
mainForm.controls.tls.disable();
mainForm.controls.xoauth2.disable();
}
const senderForm = this.buildSenderForm();
const senderEmailPlaceholder =
defaults && 'senderEmailPlaceholder' in defaults ? defaults.senderEmailPlaceholder : '[email protected]';
return defaults
? {
mainForm,
senderForm,
senderEmailPlaceholder,
defaults,
}
: {
mainForm,
senderForm,
senderEmailPlaceholder,
};
}
public activateSMTPConfig() {
this.service
.activateSMTPConfig(this.id)
.then(() => {
this.toast.showInfo('SMTP.LIST.DIALOG.ACTIVATED', true);
this.isActive = true;
})
.catch((error) => {
this.toast.showError(error);
});
private buildSenderForm(config?: { senderAddress: string; senderName: string; replyToAddress: string }) {
return this.fb.group({
senderAddress: new FormControl(config?.senderAddress ?? '', { nonNullable: true, validators: [requiredValidator] }),
senderName: new FormControl(config?.senderName ?? '', { nonNullable: true, validators: [requiredValidator] }),
replyToAddress: new FormControl(config?.replyToAddress ?? '', { nonNullable: true }),
});
}
public deactivateSMTPConfig() {
this.service
.deactivateSMTPConfig(this.id)
.then(() => {
this.toast.showInfo('SMTP.LIST.DIALOG.DEACTIVATED', true);
this.isActive = false;
})
.catch((error) => {
this.toast.showError(error);
});
}
private buildAuthForm(
stateSignal: Signal<
ReturnType<typeof this.buildFormFromDefaults> | ReturnType<typeof this.buildFormFromConfig> | undefined
>,
) {
const xoauth2$ = toObservable(stateSignal).pipe(
switchMap((state) => {
if (!state) {
return EMPTY;
}
const xoauth2 = state.mainForm.controls.xoauth2;
return xoauth2.valueChanges.pipe(startWith(xoauth2.value));
}),
);
public savePolicy(stepper: MatStepper): void {
this.updateData()
.then((resp) => {
if (!this.id) {
// This is a new SMTP provider let's get the ID from the addSMTPConfig response
let createResponse = resp as AddSMTPConfigResponse.AsObject;
this.id = createResponse.id;
const xoauth2Signal = toSignal(xoauth2$);
return computed(() => {
const state = stateSignal();
const xoauth2 = xoauth2Signal();
if (!state || xoauth2 === undefined) {
return undefined;
}
if (!xoauth2) {
const form = this.fb.group({
password: new FormControl('', {
nonNullable: true,
validators: 'defaults' in state ? [requiredValidator] : [],
}),
});
if ('config' in state) {
form.controls.password.disable();
}
this.toast.showInfo('SETTING.SMTP.SAVED', true);
setTimeout(() => {
stepper.next();
}, 2000);
})
.catch((error: unknown) => {
if (`${error}`.includes('No changes')) {
return form;
}
const defaultValues =
'config' in state && state.config.config.value.Auth.case === 'xoauth2'
? {
tokenEndpoint: state.config.config.value.Auth.value.tokenEndpoint,
scopes: state.config.config.value.Auth.value.scopes.join(','),
clientId: state.config.config.value.Auth.value.OAuth2Type.value?.clientId ?? '',
}
: 'defaults' in state && state.defaults.auth.case === 'xoauth2'
? { scopes: state.defaults.auth.scopes }
: {};
const form = this.fb.group({
tokenEndpoint: new FormControl<string>(defaultValues.tokenEndpoint ?? '', {
nonNullable: true,
validators: [requiredValidator],
}),
scopes: new FormControl<string>(defaultValues.scopes ?? '', { nonNullable: true, validators: [requiredValidator] }),
clientId: new FormControl<string>(defaultValues.clientId ?? '', {
nonNullable: true,
validators: [requiredValidator],
}),
clientSecret: new FormControl<string>('', { nonNullable: true, validators: [requiredValidator] }),
});
if ('config' in state) {
form.controls.tokenEndpoint.disable();
form.controls.scopes.disable();
form.controls.clientId.disable();
form.controls.clientSecret.disable();
}
return form;
});
}
private buildConfigOrDefaultsQuery() {
const idOrProvider$ = this.activatedRoute.paramMap.pipe(
map((params) => params.get('provider')),
filter(Boolean),
);
const idOrProviderSignal = toSignal(idOrProvider$, { requireSync: true });
return injectQuery(() => {
const idOrProvider = idOrProviderSignal();
const query = this.newAdminService.getEmailProviderByIdQueryOptions(idOrProvider);
const queryKey = query.queryKey as (string | undefined)[];
const queryFn = query.queryFn as QueryFunction<GetEmailProviderByIdResponse | string>;
const select = (configOrProvider: GetEmailProviderByIdResponse | string) =>
typeof configOrProvider === 'string'
? ({
case: 'defaults',
defaults: SMTPKnownProviders[configOrProvider as keyof typeof SMTPKnownProviders] as
| (typeof SMTPKnownProviders)[keyof typeof SMTPKnownProviders]
| undefined,
} as const)
: ({ case: 'config', config: this.getConfig(configOrProvider) } as const);
if (idOrProvider in SMTPKnownProviders || idOrProvider === 'generic') {
return {
queryKey,
queryFn: (async () => idOrProvider) as typeof queryFn,
gcTime: 0,
select,
} as const;
}
return {
queryKey,
queryFn,
select,
} as const;
});
}
private getConfig(resp: GetEmailProviderByIdResponse) {
if (!resp.config) {
throw new Error('No SMTP provider config found');
}
if (resp.config.config.case !== 'smtp') {
throw new Error('Email provider config with id ' + resp.config.id + ' is not an SMTP config');
}
const config = resp.config.config.value;
return {
...resp.config,
config: {
case: 'smtp' as const,
value: {
...config,
Auth: config.Auth,
},
},
};
}
private buildUpdateDataMutation(configOrDefaultsQuery: typeof this.configOrDefaultsQuery, stateSignal: typeof this.state) {
return injectMutation(() => {
const state = stateSignal();
const stepper = this.stepper();
return {
mutationFn: () => {
if (!state) {
throw new Error('Invalid state');
}
return this.updateData(state);
},
onSuccess: () => {
this.toast.showInfo('SETTING.SMTP.SAVED', true);
stepper?.next();
},
onError: (error: ConnectError) => {
if (!error.message.includes('No changes')) {
this.toast.showError(error);
return;
}
this.toast.showInfo('SETTING.SMTP.NOCHANGES', true);
setTimeout(() => {
stepper.next();
}, 2000);
} else {
this.toast.showError(error);
}
stepper?.next();
},
onSettled: () => configOrDefaultsQuery.refetch(),
};
});
}
private async updateData(state: State) {
const authValues = state.authForm.getRawValue();
const { user, tls, host, description } = state.mainForm.getRawValue();
const { senderAddress, senderName, replyToAddress } = state.senderForm.getRawValue();
if ('config' in state) {
return this.newAdminService.updateEmailProviderSMTP({
id: state.config.id,
description,
senderAddress,
senderName,
replyToAddress,
host,
user,
tls,
});
}
const Auth: MessageInitShape<typeof AddEmailProviderSMTPRequestSchema>['Auth'] =
'tokenEndpoint' in authValues
? {
case: 'xoauth2',
value: {
tokenEndpoint: authValues.tokenEndpoint,
scopes: authValues.scopes.replace(/\s/g, '').split(','),
OAuth2Type: {
case: 'clientCredentials',
value: {
clientId: authValues.clientId,
clientSecret: authValues.clientSecret,
},
},
},
}
: authValues.password
? { case: 'plain', value: { password: authValues.password } }
: { case: 'none', value: {} };
const res = await this.newAdminService.addEmailProviderSMTP({
senderAddress,
senderName,
description,
replyToAddress,
host,
user,
tls,
Auth,
});
await this.router.navigate(['/instance/smtpprovider', res.id, { step: 3 }], { skipLocationChange: true });
return res;
}
public testEmailConfiguration(): void {
this.isLoading.set(true);
const req = new TestSMTPConfigRequest();
req.setSenderAddress(this.senderAddress?.value ?? '');
req.setSenderName(this.senderName?.value ?? '');
req.setHost(this.hostAndPort?.value ?? '');
req.setUser(this.user?.value);
req.setPassword(this.password?.value ?? '');
req.setTls(this.tls?.value ?? false);
req.setId(this.id ?? '');
req.setReceiverAddress(this.email ?? '');
this.service
.testSMTPConfig(req)
.then(() => {
this.resultClass = 'test-success';
this.isLoading.set(false);
this.translate
.get('SMTP.CREATE.STEPS.TEST.RESULT')
.pipe(take(1))
.subscribe((msg) => {
this.testResult = msg;
});
})
.catch((error) => {
this.resultClass = 'test-error';
this.isLoading.set(false);
this.testResult = error;
});
protected async activateSMTPConfig(id: string) {
try {
await this.newAdminService.activateSMTPConfig(id);
this.toast.showInfo('SMTP.LIST.DIALOG.ACTIVATED', true);
await this.configOrDefaultsQuery.refetch();
} catch (error) {
this.toast.showError(error);
}
}
public get description(): AbstractControl | null {
return this.firstFormGroup.get('description');
protected async deactivateSMTPConfig(id: string) {
try {
await this.newAdminService.deactivateSMTPConfig(id);
this.toast.showInfo('SMTP.LIST.DIALOG.DEACTIVATED', true);
await this.configOrDefaultsQuery.refetch();
} catch (error) {
this.toast.showError(error);
}
}
public get tls(): AbstractControl | null {
return this.firstFormGroup.get('tls');
}
protected buildTestEmailConfigurationMutation(stateSignal: typeof this.state) {
const buildRequest = (state: State, receiverAddress: string) => {
const authValues = state.authForm.getRawValue();
const { user, tls, host } = state.mainForm.getRawValue();
const { senderAddress, senderName } = state.senderForm.getRawValue();
public get region(): AbstractControl | null {
return this.firstFormGroup.get('region');
}
if ('config' in state) {
return {
id: state.config.id,
senderAddress,
senderName,
host,
user,
tls,
receiverAddress,
};
}
public get hostAndPort(): AbstractControl | null {
return this.firstFormGroup.get('hostAndPort');
}
const Auth: MessageInitShape<typeof AddEmailProviderSMTPRequestSchema>['Auth'] =
'tokenEndpoint' in authValues
? {
case: 'xoauth2',
value: {
tokenEndpoint: authValues.tokenEndpoint,
scopes: authValues.scopes.replace(/\s/g, '').split(','),
OAuth2Type: {
case: 'clientCredentials',
value: {
clientId: authValues.clientId,
clientSecret: authValues.clientSecret,
},
},
},
}
: authValues.password
? { case: 'plain', value: { password: authValues.password } }
: { case: 'none', value: {} };
public get user(): AbstractControl | null {
return this.firstFormGroup.get('user');
}
return {
senderAddress,
senderName,
host,
user,
tls,
Auth,
receiverAddress,
};
};
public get password(): AbstractControl | null {
return this.firstFormGroup.get('password');
}
return injectMutation(() => {
const state = stateSignal();
const email = this.email();
public get senderAddress(): AbstractControl | null {
return this.secondFormGroup.get('senderAddress');
}
public get senderName(): AbstractControl | null {
return this.secondFormGroup.get('senderName');
}
public get replyToAddress(): AbstractControl | null {
return this.secondFormGroup.get('replyToAddress');
return {
mutationFn: () => {
if (!state) {
throw new Error('Invalid state');
}
return this.newAdminService.testEmailProviderSMTP(buildRequest(state, email));
},
};
});
}
}