-
-
{{ 'SMTP.CREATE.STEPS.ACTIVATE.TITLE' | translate }}
-
-
-
+ @if ('config' in state) {
+
-
{{ 'SMTP.CREATE.STEPS.ACTIVATE.DESCRIPTION' | translate }}
-
+
+ }
+
-
-
-
-
{{ 'SMTP.CREATE.STEPS.DEACTIVATE.TITLE' | translate }}
-
-
-
-
-
-
- {{ 'SMTP.CREATE.STEPS.DEACTIVATE.DESCRIPTION' | translate }}
-
-
-
-
-
-
-
-
-
-
- check
-
-
+
+ check
+
+
+ }
diff --git a/console/src/app/modules/smtp-provider/smtp-provider.component.ts b/console/src/app/modules/smtp-provider/smtp-provider.component.ts
index 0a810a30d..2bdd20333 100644
--- a/console/src/app/modules/smtp-provider/smtp-provider.component.ts
+++ b/console/src/app/modules/smtp-provider/smtp-provider.component.ts
@@ -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
? NonNullable : 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() ?? 'test@example.com');
- 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 = 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 = 'sender@example.com';
+ protected readonly configOrDefaultsQuery: ReturnType;
+ protected readonly state: ReturnType;
+ protected readonly updateDataMutation: ReturnType;
+ protected readonly testEmailConfigurationMutation: ReturnType;
- public resultClass = 'test-success';
- public isLoading = signal(false);
- public email: string = '';
- public testResult: string = '';
+ protected readonly stepper = viewChild(MatStepper);
+ protected readonly preselectedStep: ReturnType;
- 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 || 'sender@example.com';
-
- 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 {
- 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) {
+ const mainForm = this.fb.group({
+ description: new FormControl(config.description, {
+ nonNullable: true,
+ validators: [requiredValidator],
+ }),
+ user: new FormControl(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(config.config.value.Auth.case === 'xoauth2', { nonNullable: true }),
+ });
+
+ mainForm.controls.xoauth2.disable();
+
+ const senderForm = this.buildSenderForm(config.config.value);
+
+ return {
+ mainForm,
+ senderForm,
+ senderEmailPlaceholder: 'sender@example.com',
+ 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(defaults?.description ?? '', {
+ nonNullable: true,
+ validators: [requiredValidator],
+ }),
+ user: new FormControl(defaults?.user.value ?? '', { nonNullable: true, validators: [requiredValidator] }),
+ host: new FormControl(defaults?.host ?? '', {
+ nonNullable: true,
+ validators: [this.hostnameValidator],
+ }),
+ tls: new FormControl(true, { nonNullable: true }),
+ xoauth2: new FormControl(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 : 'sender@example.com';
+
+ 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 | ReturnType | 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(defaultValues.tokenEndpoint ?? '', {
+ nonNullable: true,
+ validators: [requiredValidator],
+ }),
+ scopes: new FormControl(defaultValues.scopes ?? '', { nonNullable: true, validators: [requiredValidator] }),
+ clientId: new FormControl(defaultValues.clientId ?? '', {
+ nonNullable: true,
+ validators: [requiredValidator],
+ }),
+ clientSecret: new FormControl('', { 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;
+
+ 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['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['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));
+ },
+ };
+ });
}
}
diff --git a/console/src/app/modules/smtp-table/smtp-table.component.ts b/console/src/app/modules/smtp-table/smtp-table.component.ts
index 4bdac1f75..247223206 100644
--- a/console/src/app/modules/smtp-table/smtp-table.component.ts
+++ b/console/src/app/modules/smtp-table/smtp-table.component.ts
@@ -193,18 +193,10 @@ export class SMTPTableComponent implements OnInit {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
- public get createRouterLink(): RouterLink | any {
- return ['/instance', 'idp', 'create'];
- }
-
public routerLinkForRow(row: SMTPConfig.AsObject): any {
return ['/instance', 'smtpprovider', row.id];
}
- public get displayedColumnsWithActions(): string[] {
- return ['actions', ...this.displayedColumns];
- }
-
public navigateToProvider(row: SMTPConfig.AsObject) {
if (!row.senderAddress) {
return;
diff --git a/console/src/app/services/new-admin.service.ts b/console/src/app/services/new-admin.service.ts
index 665f9f961..ddda9e165 100644
--- a/console/src/app/services/new-admin.service.ts
+++ b/console/src/app/services/new-admin.service.ts
@@ -2,12 +2,14 @@ import { Injectable } from '@angular/core';
import { GrpcService } from './grpc.service';
import { MessageInitShape } from '@bufbuild/protobuf';
import {
+ AddEmailProviderSMTPRequestSchema,
GetDefaultOrgResponse,
GetMyInstanceResponse,
SetUpOrgRequestSchema,
- SetUpOrgResponse,
+ TestEmailProviderSMTPRequestSchema,
+ UpdateEmailProviderSMTPRequestSchema,
} from '@zitadel/proto/zitadel/admin_pb';
-import { injectQuery } from '@tanstack/angular-query-experimental';
+import { injectQuery, queryOptions, skipToken } from '@tanstack/angular-query-experimental';
import { NewAuthService } from './new-auth.service';
import { UserService } from './user.service';
@@ -21,7 +23,7 @@ export class NewAdminService {
private readonly userService: UserService,
) {}
- public setupOrg(req: MessageInitShape): Promise {
+ public setupOrg(req: MessageInitShape) {
return this.grpcService.adminNew.setUpOrg(req);
}
@@ -41,4 +43,35 @@ export class NewAdminService {
enabled: (listMyZitadelPermissionsQuery.data() ?? []).includes('iam.write'),
}));
}
+
+ public testEmailProviderSMTP(req: MessageInitShape) {
+ return this.grpcService.adminNew.testEmailProviderSMTP(req);
+ }
+
+ public getEmailProviderById(id: string, signal: AbortSignal) {
+ return this.grpcService.adminNew.getEmailProviderById({ id }, { signal });
+ }
+
+ public getEmailProviderByIdQueryOptions(id?: string) {
+ return queryOptions({
+ queryKey: [this.userService.userId(), 'AdminService', 'getEmailProviderById', id],
+ queryFn: id ? ({ signal }) => this.getEmailProviderById(id, signal) : skipToken,
+ });
+ }
+
+ public addEmailProviderSMTP(req: MessageInitShape) {
+ return this.grpcService.adminNew.addEmailProviderSMTP(req);
+ }
+
+ public updateEmailProviderSMTP(req: MessageInitShape) {
+ return this.grpcService.adminNew.updateEmailProviderSMTP(req);
+ }
+
+ public activateSMTPConfig(id: string) {
+ return this.grpcService.adminNew.activateSMTPConfig({ id });
+ }
+
+ public deactivateSMTPConfig(id: string) {
+ return this.grpcService.adminNew.deactivateSMTPConfig({ id });
+ }
}
diff --git a/tests/functional-ui/cypress/e2e/instance/settings/notifications.cy.ts b/tests/functional-ui/cypress/e2e/instance/settings/notifications.cy.ts
index 99e68a4a0..bed7a966b 100644
--- a/tests/functional-ui/cypress/e2e/instance/settings/notifications.cy.ts
+++ b/tests/functional-ui/cypress/e2e/instance/settings/notifications.cy.ts
@@ -36,19 +36,19 @@ describe('instance notifications', () => {
cy.get('@provider').then((provider) => {
cy.visit(smtpPath);
cy.get(`a:contains('Mailgun')`).click();
- cy.get('[formcontrolname="description"]').should('be.enabled').clear().type(provider.description);
- cy.get('[formcontrolname="hostAndPort"]').should('have.value', 'smtp.mailgun.org:587');
- cy.get('[formcontrolname="user"]').should('be.enabled').clear().type('user@example.com');
- cy.get('[formcontrolname="password"]').should('be.enabled').clear().type('password');
+ cy.get('input[name="description"]').should('be.enabled').clear().type(provider.description);
+ cy.get('input[name="hostAndPort"]').should('have.value', 'smtp.mailgun.org:465');
+ cy.get('input[name="smtp-user"]').should('be.enabled').clear().type('user@example.com');
+ cy.get('input[name="smtp-password"]').should('be.enabled').clear().type('password');
cy.get('[data-e2e="continue-to-2nd-form"]').should('be.enabled').click();
- cy.get('[formcontrolname="senderAddress"]').should('be.enabled').clear().type('sender1@example.com');
- cy.get('[formcontrolname="senderName"]').should('be.enabled').clear().type('Test1');
- cy.get('[formcontrolname="replyToAddress"]').should('be.enabled').clear().type('replyto1@example.com');
+ cy.get('input[name="senderAddress"]').should('be.enabled').clear().type('sender1@example.com');
+ cy.get('input[name="senderName"]').should('be.enabled').clear().type('Test1');
+ cy.get('input[name="senderReplyToAddress"]').should('be.enabled').clear().type('replyto1@example.com');
cy.get('[data-e2e="continue-button"]').should('be.enabled').click();
cy.get('[data-e2e="create-button"]').should('be.enabled').click();
cy.shouldConfirmSuccess();
cy.get('[data-e2e="close-button"]').should('be.enabled').click();
- cy.get(provider.rowSelector).contains('smtp.mailgun.org:587');
+ cy.get(provider.rowSelector).contains('smtp.mailgun.org:465');
cy.get(provider.rowSelector).contains('sender1@example.com');
});
});
@@ -57,14 +57,14 @@ describe('instance notifications', () => {
cy.get('@provider').then((provider) => {
cy.visit(smtpPath);
cy.get(`a:contains('Mailgun')`).click();
- cy.get('[formcontrolname="description"]').should('be.enabled').clear().type(provider.description);
- cy.get('[formcontrolname="hostAndPort"]').should('have.value', 'smtp.mailgun.org:587');
- cy.get('[formcontrolname="user"]').should('be.enabled').clear().type('user@example.com');
- cy.get('[formcontrolname="password"]').should('be.enabled').clear().type('password');
+ cy.get('input[name="description"]').should('be.enabled').clear().type(provider.description);
+ cy.get('input[name="hostAndPort"]').should('have.value', 'smtp.mailgun.org:465');
+ cy.get('input[name="smtp-user"]').should('be.enabled').clear().type('user@example.com');
+ cy.get('input[name="smtp-password"]').should('be.enabled').clear().type('password');
cy.get('[data-e2e="continue-to-2nd-form"]').should('be.enabled').click();
- cy.get('[formcontrolname="senderAddress"]').should('be.enabled').clear().type('sender1@example.com');
- cy.get('[formcontrolname="senderName"]').should('be.enabled').clear().type('Test1');
- cy.get('[formcontrolname="replyToAddress"]').should('be.enabled').clear().type('replyto1@example.com');
+ cy.get('input[name="senderAddress"]').should('be.enabled').clear().type('sender1@example.com');
+ cy.get('input[name="senderName"]').should('be.enabled').clear().type('Test1');
+ cy.get('input[name="senderReplyToAddress"]').should('be.enabled').clear().type('replyto1@example.com');
cy.get('[data-e2e="continue-button"]').should('be.enabled').click();
cy.get('[data-e2e="create-button"]').click();
cy.shouldConfirmSuccess();
@@ -72,7 +72,7 @@ describe('instance notifications', () => {
cy.shouldConfirmSuccess();
cy.get('[data-e2e="close-button"]').click();
cy.get(provider.rowSelector).find('[data-e2e="active-provider"]');
- cy.get(provider.rowSelector).contains('smtp.mailgun.org:587');
+ cy.get(provider.rowSelector).contains('smtp.mailgun.org:465');
cy.get(provider.rowSelector).contains('sender1@example.com');
});
});
@@ -91,8 +91,8 @@ describe('instance notifications', () => {
cy.get('@provider').then(({ rowSelector }) => {
cy.get(rowSelector).click();
cy.get('[data-e2e="continue-to-2nd-form"]').click();
- cy.get('[formcontrolname="senderAddress"]').should('be.enabled').clear().type('senderchange1@example.com');
- cy.get('[formcontrolname="senderName"]').clear().type('Change1');
+ cy.get('input[name="senderAddress"]').should('be.enabled').clear().type('senderchange1@example.com');
+ cy.get('input[name="senderName"]').clear().type('Change1');
cy.get('[data-e2e="continue-button"]').click();
cy.get('[data-e2e="create-button"]').click();
cy.shouldConfirmSuccess();
diff --git a/tests/functional-ui/project.json b/tests/functional-ui/project.json
index 542b0e55a..124ce79d0 100644
--- a/tests/functional-ui/project.json
+++ b/tests/functional-ui/project.json
@@ -1,6 +1,6 @@
{
"$schema": "../../node_modules/nx/schemas/project-schema.json",
- "implicitDependencies": ["@zitadel/api"],
+ "implicitDependencies": ["@zitadel/api", "@zitadel/console"],
"targets": {
"run-db": {
"continuous": true,