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: The code consists of the following parts:
| name | description | language | where to find | | 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) | | 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) | | 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) | | 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) | | 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) | | docs | Project documentation made with docusaurus | [Docusaurus](https://docusaurus.io/) | [./docs](./docs) |
Please validate and test the code before you contribute. 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. 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. 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. 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**: **Choose gender-neutral alternatives**:
Opt for gender-neutral terms instead of gendered ones whenever possible. 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." Replace "policeman" with "police officer," "manpower" with "workforce," and "businessman" with "entrepreneur" or "businessperson."
**Avoid ableist language**: **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: To test the database-connected gRPC API, run PostgreSQL and CockroachDB, set up a ZITADEL instance and run the tests including integration tests:
```bash ```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} docker compose -f internal/integration/config/docker-compose.yaml up --pull always --wait ${INTEGRATION_DB_FLAVOR}
make core_integration_test make core_integration_test
docker compose -f internal/integration/config/docker-compose.yaml down docker compose -f internal/integration/config/docker-compose.yaml down

View File

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

View File

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

View File

@ -106,7 +106,13 @@
<cnsl-form-field class="smtp-form-field" label="Sender Address"> <cnsl-form-field class="smtp-form-field" label="Sender Address">
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label> <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>
<cnsl-form-field class="smtp-form-field" label="Sender Name"> <cnsl-form-field class="smtp-form-field" label="Sender Name">

View File

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

View File

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

View File

@ -1994,7 +1994,7 @@
}, },
"CREATE": { "CREATE": {
"TITLE": "Add provider", "TITLE": "Add provider",
"DESCRIPTION": "Select one ore more of the following providers.", "DESCRIPTION": "Select one or more of the following providers.",
"STEPPERTITLE": "Create Provider", "STEPPERTITLE": "Create Provider",
"OIDC": { "OIDC": {
"TITLE": "OIDC Provider", "TITLE": "OIDC Provider",
@ -2264,7 +2264,7 @@
}, },
"CREATE": { "CREATE": {
"TITLE": "Add SMTP provider", "TITLE": "Add SMTP provider",
"DESCRIPTION": "Select one ore more of the following providers.", "DESCRIPTION": "Select one or more of the following providers.",
"STEPS": { "STEPS": {
"TITLE": "Add {{ value }} SMTP Provider", "TITLE": "Add {{ value }} SMTP Provider",
"CREATE_DESC_TITLE": "Enter your {{ value }} SMTP settings step by step", "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 return nil
} }
// Auth // Auth
auth := unencryptedAuth{ err := client.Auth(PlainOrLoginAuth(smtpConfig.User, smtpConfig.Password, host))
smtp.PlainAuth("", smtpConfig.User, smtpConfig.Password, host),
}
err := client.Auth(auth)
if err != nil { if err != nil {
return zerrors.ThrowInternalf(err, "EMAIL-s9kfs", "could not add smtp auth for user %s", smtpConfig.User) 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")
}
}