fix: Unrecognized Authentication Type Error when SMTP LOGIN Auth method is required (#7761)

* fix: poc outlook.com now works login auth

* fix: remove port arg from smtpAuth

* fix: add outlook provider and custom email placeholder

* fix: minor typo in contributing docs

* fix: use zerrors package

* fix: typo for idp and smtp providers

---------

Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
Miguel Cabrerizo 2024-04-30 09:31:07 +02:00 committed by GitHub
parent 2a421a7b8a
commit 1f54f5b8a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 107 additions and 40 deletions

View File

@ -108,13 +108,13 @@ Please make sure you cover your changes with tests before marking a Pull Request
The code consists of the following parts:
| name | description | language | where to find |
| --------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------- |
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) |
| console | Frontend the user interacts with after log in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) |
| name | description | language | where to find |
| --------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
| backend | Service that serves the grpc(-web) and RESTful API | [go](https://go.dev) | [API implementation](./internal/api/grpc) |
| console | Frontend the user interacts with after log in | [Angular](https://angular.io), [Typescript](https://www.typescriptlang.org) | [./console](./console) |
| login | Server side rendered frontend the user interacts with during login | [go](https://go.dev), [go templates](https://pkg.go.dev/html/template) | [./internal/api/ui/login](./internal/api/ui/login) |
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) |
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
| API definitions | Specifications of the API | [Protobuf](https://developers.google.com/protocol-buffers) | [./proto/zitadel](./proto/zitadel) |
| docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
Please validate and test the code before you contribute.
@ -129,12 +129,12 @@ We add the label "good first issue" for problems we think are a good starting po
We are committed to creating a welcoming and inclusive community for all developers, regardless of their gender identity or expression. To achieve this, we are actively working to ensure that our contribution guidelines are gender-neutral and use inclusive language.
**Use gender-neutral pronouns**:
**Use gender-neutral pronouns**:
Don't use gender-specific pronouns unless the person you're referring to is actually that gender.
In particular, don't use he, him, his, she, or her as gender-neutral pronouns, and don't use he/she or (s)he or other such punctuational approaches. Instead, use the singular they.
**Choose gender-neutral alternatives**:
Opt for gender-neutral terms instead of gendered ones whenever possible.
**Choose gender-neutral alternatives**:
Opt for gender-neutral terms instead of gendered ones whenever possible.
Replace "policeman" with "police officer," "manpower" with "workforce," and "businessman" with "entrepreneur" or "businessperson."
**Avoid ableist language**:
@ -194,7 +194,7 @@ make core_unit_test
To test the database-connected gRPC API, run PostgreSQL and CockroachDB, set up a ZITADEL instance and run the tests including integration tests:
```bash
export INTEGRATION_DB_FLAVOR="postgres" ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
export INTEGRATION_DB_FLAVOR="cockroach" ZITADEL_MASTERKEY="MasterkeyNeedsToHave32Characters"
docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait ${INTEGRATION_DB_FLAVOR}
make core_integration_test
docker compose -f internal/integration/config/docker-compose.yaml down

View File

@ -53,6 +53,7 @@ export interface ProviderDefaultSettings {
value: string;
placeholder: string;
};
senderEmailPlaceholder?: string;
image?: string;
routerLinkElement: string;
}
@ -102,6 +103,7 @@ export const MailjetDefaultSettings: ProviderDefaultSettings = {
user: { value: '', placeholder: 'Your Mailjet API key' },
password: { value: '', placeholder: 'Your Mailjet Secret key' },
image: './assets/images/smtp/mailjet.svg',
senderEmailPlaceholder: 'An authorized domain or email address',
routerLinkElement: 'mailjet',
};
@ -114,6 +116,7 @@ export const PostmarkDefaultSettings: ProviderDefaultSettings = {
user: { value: '', placeholder: 'Your Server API token' },
password: { value: '', placeholder: 'Your Server API token' },
image: './assets/images/smtp/postmark.png',
senderEmailPlaceholder: 'An authorized domain or email address',
routerLinkElement: 'postmark',
};
@ -138,6 +141,7 @@ export const MailchimpDefaultSettings: ProviderDefaultSettings = {
user: { value: '', placeholder: 'Your Mailchimp primary contact email' },
password: { value: '', placeholder: 'Your Mailchimp Transactional API key' },
image: './assets/images/smtp/mailchimp.svg',
senderEmailPlaceholder: 'An authorized domain or email address',
routerLinkElement: 'mailchimp',
};
@ -153,6 +157,19 @@ export const BrevoDefaultSettings: ProviderDefaultSettings = {
routerLinkElement: 'brevo',
};
export const OutlookDefaultSettings: ProviderDefaultSettings = {
name: 'outlook.com',
requiredTls: true,
host: 'smtp-mail.outlook.com',
unencryptedPort: 587,
encryptedPort: 587,
user: { value: '', placeholder: 'Your outlook.com email address' },
password: { value: '', placeholder: 'Your outlook.com password' },
image: './assets/images/smtp/outlook.svg',
senderEmailPlaceholder: 'Your outlook.com email address',
routerLinkElement: 'outlook',
};
export const GenericDefaultSettings: ProviderDefaultSettings = {
name: 'generic',
requiredTls: false,
@ -170,5 +187,6 @@ export const SMTPKnownProviders = [
MailjetDefaultSettings,
PostmarkDefaultSettings,
SendgridDefaultSettings,
OutlookDefaultSettings,
GenericDefaultSettings,
];

View File

@ -13,6 +13,7 @@ const types = [
{ path: 'mailjet', component: SMTPProviderComponent },
{ path: 'mailchimp', component: SMTPProviderComponent },
{ path: 'brevo', component: SMTPProviderComponent },
{ path: 'outlook', component: SMTPProviderComponent },
];
const routes: Routes = types.map((value) => {

View File

@ -106,7 +106,13 @@
<cnsl-form-field class="smtp-form-field" label="Sender Address">
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label>
<input cnslInput name="senderAddress" formControlName="senderAddress" placeholder="sender@example.com" required />
<input
cnslInput
name="senderAddress"
formControlName="senderAddress"
placeholder="{{ senderEmailPlaceholder }}"
required
/>
</cnsl-form-field>
<cnsl-form-field class="smtp-form-field" label="Sender Name">

View File

@ -28,6 +28,7 @@ import {
MailjetDefaultSettings,
PostmarkDefaultSettings,
ProviderDefaultSettings,
OutlookDefaultSettings,
SendgridDefaultSettings,
} from './known-smtp-providers-settings';
@ -56,6 +57,8 @@ export class SMTPProviderComponent {
public firstFormGroup!: UntypedFormGroup;
public secondFormGroup!: UntypedFormGroup;
public senderEmailPlaceholder = 'sender@example.com';
constructor(
private service: AdminService,
private _location: Location,
@ -91,6 +94,9 @@ export class SMTPProviderComponent {
case 'brevo':
this.providerDefaultSetting = BrevoDefaultSettings;
break;
case 'outlook':
this.providerDefaultSetting = OutlookDefaultSettings;
break;
}
this.firstFormGroup = this.fb.group({
@ -106,6 +112,8 @@ export class SMTPProviderComponent {
password: [this.providerDefaultSetting?.password.value || ''],
});
this.senderEmailPlaceholder = this.providerDefaultSetting?.senderEmailPlaceholder || 'sender@example.com';
this.secondFormGroup = this.fb.group({
senderAddress: ['', [requiredValidator]],
senderName: ['', [requiredValidator]],

View File

@ -11,7 +11,8 @@
'/instance/smtpprovider/postmark/create',
'/instance/smtpprovider/sendgrid/create',
'/instance/smtpprovider/mailchimp/create',
'/instance/smtpprovider/brevo/create'
'/instance/smtpprovider/brevo/create',
'/instance/smtpprovider/outlook/create'
]"
[timestamp]="configsResult?.details?.viewTimestamp"
[selection]="selection"

View File

@ -1994,7 +1994,7 @@
},
"CREATE": {
"TITLE": "Add provider",
"DESCRIPTION": "Select one ore more of the following providers.",
"DESCRIPTION": "Select one or more of the following providers.",
"STEPPERTITLE": "Create Provider",
"OIDC": {
"TITLE": "OIDC Provider",
@ -2264,7 +2264,7 @@
},
"CREATE": {
"TITLE": "Add SMTP provider",
"DESCRIPTION": "Select one ore more of the following providers.",
"DESCRIPTION": "Select one or more of the following providers.",
"STEPS": {
"TITLE": "Add {{ value }} SMTP Provider",
"CREATE_DESC_TITLE": "Enter your {{ value }} SMTP settings step by step",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -152,10 +152,7 @@ func (smtpConfig SMTP) smtpAuth(client *smtp.Client, host string) error {
return nil
}
// Auth
auth := unencryptedAuth{
smtp.PlainAuth("", smtpConfig.User, smtpConfig.Password, host),
}
err := client.Auth(auth)
err := client.Auth(PlainOrLoginAuth(smtpConfig.User, smtpConfig.Password, host))
if err != nil {
return zerrors.ThrowInternalf(err, "EMAIL-s9kfs", "could not add smtp auth for user %s", smtpConfig.User)
}

View File

@ -1,22 +0,0 @@
package smtp
import (
"net/smtp"
)
type unencryptedAuth struct {
smtp.Auth
}
// PlainAuth returns an Auth that implements the PLAIN authentication
// mechanism as defined in RFC 4616. The returned Auth uses the given
// username and password to authenticate to host and act as identity.
// Usually identity should be the empty string, to act as username.
//
// This reimplementation allows it to work over non-TLS connections
func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
s := *server
s.TLS = true
return a.Auth.Start(&s)
}

View File

@ -0,0 +1,57 @@
package smtp
import (
"bytes"
"net/smtp"
"slices"
"github.com/zitadel/zitadel/internal/zerrors"
)
// golang net/smtp SMTP AUTH LOGIN or PLAIN Auth Handler
// Reference: https://gist.github.com/andelf/5118732?permalink_comment_id=4825669#gistcomment-4825669
func PlainOrLoginAuth(username, password, host string) smtp.Auth {
return &plainOrLoginAuth{username: username, password: password, host: host}
}
type plainOrLoginAuth struct {
username string
password string
host string
authMethod string
}
func (a *plainOrLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if server.Name != a.host {
return "", nil, zerrors.ThrowInternal(nil, "SMTP-RRi75", "wrong host name")
}
if !slices.Contains(server.Auth, "PLAIN") {
a.authMethod = "LOGIN"
return a.authMethod, nil, nil
} else {
a.authMethod = "PLAIN"
resp := []byte("\x00" + a.username + "\x00" + a.password)
return a.authMethod, resp, nil
}
}
func (a *plainOrLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if !more {
return nil, nil
}
if a.authMethod == "PLAIN" {
// We've already sent everything.
return nil, zerrors.ThrowInternal(nil, "SMTP-AAf43", "unexpected server challenge for PLAIN auth method")
}
switch {
case bytes.Equal(fromServer, []byte("Username:")):
return []byte(a.username), nil
case bytes.Equal(fromServer, []byte("Password:")):
return []byte(a.password), nil
default:
return nil, zerrors.ThrowInternal(nil, "SMTP-HjW21", "unexpected server challenge")
}
}