mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Add OAuth provider type support for SMTP configuration and enhance documentation
This commit is contained in:
@@ -3,6 +3,7 @@ import Email from "Common/Types/Email";
|
||||
import EmailMessage from "Common/Types/Email/EmailMessage";
|
||||
import EmailServer from "Common/Types/Email/EmailServer";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import OAuthProviderType from "Common/Types/Email/OAuthProviderType";
|
||||
import SMTPAuthenticationType from "Common/Types/Email/SMTPAuthenticationType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
@@ -52,6 +53,7 @@ router.post(
|
||||
clientSecret: true,
|
||||
tokenUrl: true,
|
||||
scope: true,
|
||||
oauthProviderType: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -102,6 +104,7 @@ router.post(
|
||||
clientSecret: config.clientSecret,
|
||||
tokenUrl: config.tokenUrl ? URL.fromString(config.tokenUrl) : undefined,
|
||||
scope: config.scope,
|
||||
oauthProviderType: config.oauthProviderType as OAuthProviderType | undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -13,6 +13,7 @@ import Email from "Common/Types/Email";
|
||||
import EmailMessage from "Common/Types/Email/EmailMessage";
|
||||
import EmailServer from "Common/Types/Email/EmailServer";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import OAuthProviderType from "Common/Types/Email/OAuthProviderType";
|
||||
import SMTPAuthenticationType from "Common/Types/Email/SMTPAuthenticationType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
@@ -136,11 +137,14 @@ class TransporterPool {
|
||||
}
|
||||
|
||||
// Get the access token using the generic OAuth service
|
||||
// Provider type determines which grant flow to use (Client Credentials vs JWT Bearer)
|
||||
const accessToken: string = await SMTPOAuthService.getAccessToken({
|
||||
clientId: emailServer.clientId,
|
||||
clientSecret: emailServer.clientSecret,
|
||||
tokenUrl: emailServer.tokenUrl,
|
||||
scope: emailServer.scope,
|
||||
username: emailServer.username, // Required for JWT Bearer (user to impersonate)
|
||||
providerType: emailServer.oauthProviderType || OAuthProviderType.ClientCredentials,
|
||||
});
|
||||
|
||||
logger.debug("Creating OAuth transporter for SMTP");
|
||||
|
||||
@@ -3,6 +3,8 @@ import logger from "Common/Server/Utils/Logger";
|
||||
import LocalCache from "Common/Server/Infrastructure/LocalCache";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OAuthProviderType from "Common/Types/Email/OAuthProviderType";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
interface OAuthTokenResponse {
|
||||
access_token: string;
|
||||
@@ -18,40 +20,43 @@ interface CachedToken extends JSONObject {
|
||||
export interface SMTPOAuthConfig {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
tokenUrl: URL; // Full OAuth token endpoint URL
|
||||
scope: string; // OAuth scope(s), space-separated if multiple
|
||||
tokenUrl: URL;
|
||||
scope: string;
|
||||
username?: string; // Email address to send as (required for JWT Bearer to impersonate user)
|
||||
providerType: OAuthProviderType; // The OAuth grant type to use
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic service for fetching OAuth2 access tokens for SMTP authentication.
|
||||
* Supports any OAuth 2.0 provider that implements the client credentials grant flow.
|
||||
* Supports multiple OAuth 2.0 grant types:
|
||||
*
|
||||
* Common configurations:
|
||||
* - Client Credentials (OAuthProviderType.ClientCredentials)
|
||||
* Used by: Microsoft 365, Azure AD, and most OAuth 2.0 providers
|
||||
* Required fields: Client ID, Client Secret, Token URL, Scope
|
||||
*
|
||||
* Microsoft 365:
|
||||
* - tokenUrl: https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
|
||||
* - scope: https://outlook.office365.com/.default
|
||||
*
|
||||
* Google Workspace (requires service account):
|
||||
* - tokenUrl: https://oauth2.googleapis.com/token
|
||||
* - scope: https://mail.google.com/
|
||||
*
|
||||
* Custom OAuth Provider:
|
||||
* - tokenUrl: Your provider's token endpoint
|
||||
* - scope: Required scope(s) for SMTP access
|
||||
* - JWT Bearer Assertion (OAuthProviderType.JWTBearer)
|
||||
* Used by: Google Workspace service accounts
|
||||
* Required fields:
|
||||
* - Client ID: Service account email (client_email from JSON key)
|
||||
* - Client Secret: Private key (private_key from JSON key)
|
||||
* - Token URL: OAuth token endpoint
|
||||
* - Scope: Required scopes
|
||||
* - Username: Email address to impersonate
|
||||
*/
|
||||
export default class SMTPOAuthService {
|
||||
private static readonly TOKEN_CACHE_NAMESPACE = "smtp-oauth-tokens";
|
||||
private static readonly TOKEN_BUFFER_SECONDS = 300; // Refresh token 5 minutes before expiry
|
||||
private static readonly JWT_EXPIRY_SECONDS = 3600; // JWTs are valid for max 1 hour
|
||||
|
||||
/**
|
||||
* Get an access token for SMTP authentication using OAuth 2.0 client credentials flow.
|
||||
* Get an access token for SMTP authentication.
|
||||
* Uses the provider type specified in config to determine the grant type.
|
||||
*
|
||||
* @param config - OAuth configuration including clientId, clientSecret, tokenUrl, and scope
|
||||
* @param config - OAuth configuration
|
||||
* @returns The access token
|
||||
*/
|
||||
public static async getAccessToken(config: SMTPOAuthConfig): Promise<string> {
|
||||
const cacheKey = `${config.tokenUrl.toString()}:${config.clientId}`;
|
||||
const cacheKey = `${config.tokenUrl.toString()}:${config.clientId}:${config.username || ""}`;
|
||||
|
||||
// Check if we have a cached token that's still valid
|
||||
if (LocalCache.hasValue(this.TOKEN_CACHE_NAMESPACE, cacheKey)) {
|
||||
@@ -67,12 +72,147 @@ export default class SMTPOAuthService {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch a new token
|
||||
const token: string = await this.fetchNewToken(config);
|
||||
// Fetch a new token using the appropriate method based on provider type
|
||||
let token: string;
|
||||
|
||||
switch (config.providerType) {
|
||||
case OAuthProviderType.JWTBearer:
|
||||
token = await this.fetchJWTBearerToken(config);
|
||||
break;
|
||||
case OAuthProviderType.ClientCredentials:
|
||||
default:
|
||||
token = await this.fetchClientCredentialsToken(config);
|
||||
break;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private static async fetchNewToken(config: SMTPOAuthConfig): Promise<string> {
|
||||
/**
|
||||
* Fetch token using JWT Bearer assertion flow (RFC 7523).
|
||||
* Used by Google Workspace service accounts and other providers that require signed JWTs.
|
||||
*/
|
||||
private static async fetchJWTBearerToken(config: SMTPOAuthConfig): Promise<string> {
|
||||
if (!config.username) {
|
||||
throw new BadDataException(
|
||||
"Username (email address to impersonate) is required for JWT Bearer OAuth. " +
|
||||
"This should be the email address that will send emails."
|
||||
);
|
||||
}
|
||||
|
||||
// Validate that clientSecret looks like a private key
|
||||
if (!config.clientSecret.includes("-----BEGIN") || !config.clientSecret.includes("PRIVATE KEY")) {
|
||||
throw new BadDataException(
|
||||
"For JWT Bearer OAuth, the Client Secret must be a private key. " +
|
||||
"It should start with '-----BEGIN PRIVATE KEY-----' or '-----BEGIN RSA PRIVATE KEY-----'."
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Creating JWT for OAuth token request");
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Create JWT claims
|
||||
const jwtClaims = {
|
||||
iss: config.clientId, // Issuer (service account email for Google)
|
||||
sub: config.username, // Subject (user to impersonate)
|
||||
scope: config.scope,
|
||||
aud: config.tokenUrl.toString(),
|
||||
iat: now,
|
||||
exp: now + this.JWT_EXPIRY_SECONDS,
|
||||
};
|
||||
|
||||
// Sign the JWT with the private key
|
||||
const signedJwt = jwt.sign(jwtClaims, config.clientSecret, {
|
||||
algorithm: "RS256",
|
||||
});
|
||||
|
||||
logger.debug(`Fetching OAuth token from ${config.tokenUrl.toString()} using JWT Bearer`);
|
||||
|
||||
// Exchange JWT for access token
|
||||
const params = new URLSearchParams();
|
||||
params.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
|
||||
params.append("assertion", signedJwt);
|
||||
|
||||
const response: Response = await fetch(config.tokenUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: params.toString(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText: string = await response.text();
|
||||
logger.error(`Failed to fetch OAuth token: ${response.status} - ${errorText}`);
|
||||
|
||||
// Provide helpful error messages for common issues
|
||||
if (errorText.includes("invalid_grant")) {
|
||||
throw new BadDataException(
|
||||
`OAuth failed: invalid_grant. This usually means: ` +
|
||||
`1) Domain-wide delegation is not enabled, ` +
|
||||
`2) The service account is not authorized in your admin console, ` +
|
||||
`3) The user email '${config.username}' doesn't exist or can't be impersonated, or ` +
|
||||
`4) The scope '${config.scope}' is not authorized. ` +
|
||||
`Please check your OAuth provider's admin console for domain-wide delegation settings.`
|
||||
);
|
||||
}
|
||||
|
||||
if (errorText.includes("unauthorized_client")) {
|
||||
throw new BadDataException(
|
||||
`OAuth failed: unauthorized_client. ` +
|
||||
`The service account '${config.clientId}' is not authorized to impersonate users. ` +
|
||||
`Please enable domain-wide delegation and authorize the client ID in your admin console.`
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadDataException(
|
||||
`Failed to authenticate with OAuth provider: ${response.status}. Error: ${errorText}`
|
||||
);
|
||||
}
|
||||
|
||||
const tokenData: OAuthTokenResponse = (await response.json()) as OAuthTokenResponse;
|
||||
|
||||
if (!tokenData.access_token) {
|
||||
throw new BadDataException("OAuth response did not contain an access token");
|
||||
}
|
||||
|
||||
// Cache the token
|
||||
this.cacheToken(config, tokenData);
|
||||
|
||||
logger.debug("Successfully obtained and cached OAuth token via JWT Bearer");
|
||||
|
||||
return tokenData.access_token;
|
||||
} catch (error) {
|
||||
if (error instanceof BadDataException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.error("Error fetching OAuth token via JWT Bearer:");
|
||||
logger.error(error);
|
||||
|
||||
// Handle JWT signing errors
|
||||
if (error instanceof Error && error.message.includes("PEM")) {
|
||||
throw new BadDataException(
|
||||
`Invalid private key format. Make sure you copied the entire private key, ` +
|
||||
`including the '-----BEGIN PRIVATE KEY-----' and '-----END PRIVATE KEY-----' markers. ` +
|
||||
`Error: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadDataException(
|
||||
`Failed to authenticate with OAuth provider: ${error instanceof Error ? error.message : "Unknown error"}. ` +
|
||||
`Please verify your credentials and OAuth provider settings.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch token using OAuth 2.0 client credentials flow (RFC 6749).
|
||||
* Used by Microsoft 365, Azure AD, and most OAuth 2.0 providers.
|
||||
*/
|
||||
private static async fetchClientCredentialsToken(config: SMTPOAuthConfig): Promise<string> {
|
||||
const params = new URLSearchParams();
|
||||
params.append("client_id", config.clientId);
|
||||
params.append("client_secret", config.clientSecret);
|
||||
@@ -80,7 +220,7 @@ export default class SMTPOAuthService {
|
||||
params.append("grant_type", "client_credentials");
|
||||
|
||||
try {
|
||||
logger.debug(`Fetching new OAuth token from ${config.tokenUrl.toString()}`);
|
||||
logger.debug(`Fetching OAuth token from ${config.tokenUrl.toString()} using Client Credentials`);
|
||||
|
||||
const response: Response = await fetch(config.tokenUrl.toString(), {
|
||||
method: "POST",
|
||||
@@ -96,7 +236,9 @@ export default class SMTPOAuthService {
|
||||
`Failed to fetch OAuth token: ${response.status} - ${errorText}`,
|
||||
);
|
||||
throw new BadDataException(
|
||||
`Failed to authenticate with OAuth provider: ${response.status}. Please check your OAuth credentials (Client ID, Client Secret, Token URL, and Scope). Error: ${errorText}`,
|
||||
`Failed to authenticate with OAuth provider: ${response.status}. ` +
|
||||
`Please check your OAuth credentials (Client ID, Client Secret, Token URL, and Scope). ` +
|
||||
`Error: ${errorText}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,18 +252,9 @@ export default class SMTPOAuthService {
|
||||
}
|
||||
|
||||
// Cache the token
|
||||
const cacheKey = `${config.tokenUrl.toString()}:${config.clientId}`;
|
||||
const expiresAt: number =
|
||||
Date.now() + (tokenData.expires_in - this.TOKEN_BUFFER_SECONDS) * 1000;
|
||||
this.cacheToken(config, tokenData);
|
||||
|
||||
const cachedToken: CachedToken = {
|
||||
accessToken: tokenData.access_token,
|
||||
expiresAt,
|
||||
};
|
||||
|
||||
LocalCache.setJSON(this.TOKEN_CACHE_NAMESPACE, cacheKey, cachedToken);
|
||||
|
||||
logger.debug("Successfully obtained and cached OAuth token");
|
||||
logger.debug("Successfully obtained and cached OAuth token via Client Credentials");
|
||||
|
||||
return tokenData.access_token;
|
||||
} catch (error) {
|
||||
@@ -129,15 +262,32 @@ export default class SMTPOAuthService {
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.error("Error fetching OAuth token:");
|
||||
logger.error("Error fetching OAuth token via Client Credentials:");
|
||||
logger.error(error);
|
||||
|
||||
throw new BadDataException(
|
||||
`Failed to authenticate with OAuth provider: ${error instanceof Error ? error.message : "Unknown error"}. Please check your OAuth credentials and network connectivity.`,
|
||||
`Failed to authenticate with OAuth provider: ${error instanceof Error ? error.message : "Unknown error"}. ` +
|
||||
`Please check your OAuth credentials and network connectivity.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the token for future use.
|
||||
*/
|
||||
private static cacheToken(config: SMTPOAuthConfig, tokenData: OAuthTokenResponse): void {
|
||||
const cacheKey = `${config.tokenUrl.toString()}:${config.clientId}:${config.username || ""}`;
|
||||
const expiresAt: number =
|
||||
Date.now() + (tokenData.expires_in - this.TOKEN_BUFFER_SECONDS) * 1000;
|
||||
|
||||
const cachedToken: CachedToken = {
|
||||
accessToken: tokenData.access_token,
|
||||
expiresAt,
|
||||
};
|
||||
|
||||
LocalCache.setJSON(this.TOKEN_CACHE_NAMESPACE, cacheKey, cachedToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XOAUTH2 token string for SMTP authentication.
|
||||
* Format: base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")
|
||||
@@ -178,4 +328,9 @@ export default class SMTPOAuthService {
|
||||
* Default scope for Google Workspace SMTP.
|
||||
*/
|
||||
public static readonly GOOGLE_SMTP_SCOPE = "https://mail.google.com/";
|
||||
|
||||
/**
|
||||
* Google OAuth token URL.
|
||||
*/
|
||||
public static readonly GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import TableMetadata from "../../Types/Database/TableMetadata";
|
||||
import TenantColumn from "../../Types/Database/TenantColumn";
|
||||
import UniqueColumnBy from "../../Types/Database/UniqueColumnBy";
|
||||
import Email from "../../Types/Email";
|
||||
import OAuthProviderType from "../../Types/Email/OAuthProviderType";
|
||||
import SMTPAuthenticationType from "../../Types/Email/SMTPAuthenticationType";
|
||||
import IconProp from "../../Types/Icon/IconProp";
|
||||
import ObjectID from "../../Types/ObjectID";
|
||||
@@ -728,4 +729,37 @@ export default class ProjectSmtpConfig extends BaseModel {
|
||||
length: ColumnLength.LongText,
|
||||
})
|
||||
public scope?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.CreateProjectSMTPConfig,
|
||||
],
|
||||
read: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.ProjectMember,
|
||||
Permission.ReadProjectSMTPConfig,
|
||||
],
|
||||
update: [
|
||||
Permission.ProjectOwner,
|
||||
Permission.ProjectAdmin,
|
||||
Permission.EditProjectSMTPConfig,
|
||||
],
|
||||
})
|
||||
@TableColumn({
|
||||
required: false,
|
||||
type: TableColumnType.ShortText,
|
||||
title: "OAuth Provider Type",
|
||||
description:
|
||||
"The OAuth grant type to use. 'Client Credentials' for Microsoft 365 and most providers. 'JWT Bearer' for Google Workspace service accounts.",
|
||||
example: "Client Credentials",
|
||||
})
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
})
|
||||
public oauthProviderType?: OAuthProviderType = undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddOAuthProviderType1768217403078 implements MigrationInterface {
|
||||
name = 'AddOAuthProviderType1768217403078'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Add oauthProviderType column to ProjectSMTPConfig table
|
||||
// Values: 'Client Credentials' (for Microsoft 365, etc.) or 'JWT Bearer' (for Google Workspace)
|
||||
await queryRunner.query(`ALTER TABLE "ProjectSMTPConfig" ADD "oauthProviderType" character varying(100)`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "ProjectSMTPConfig" DROP COLUMN "oauthProviderType"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -218,6 +218,7 @@ import { RenameServiceCatalogToService1767966850199 } from "./1767966850199-Rena
|
||||
import { MigrationName1767979055522 } from "./1767979055522-MigrationName";
|
||||
import { MigrationName1767979448478 } from "./1767979448478-MigrationName";
|
||||
import { IncreaseClientSecretLength1768216593272 } from "./1768216593272-IncreaseClientSecretLength";
|
||||
import { AddOAuthProviderType1768217403078 } from "./1768217403078-AddOAuthProviderType";
|
||||
|
||||
export default [
|
||||
InitialMigration,
|
||||
@@ -440,4 +441,5 @@ export default [
|
||||
MigrationName1767979448478,
|
||||
MigrationName1767896933148,
|
||||
IncreaseClientSecretLength1768216593272,
|
||||
AddOAuthProviderType1768217403078,
|
||||
];
|
||||
|
||||
@@ -3,6 +3,7 @@ import URL from "../API/URL";
|
||||
import Email from "../Email";
|
||||
import ObjectID from "../ObjectID";
|
||||
import Port from "../Port";
|
||||
import OAuthProviderType from "./OAuthProviderType";
|
||||
import SMTPAuthenticationType from "./SMTPAuthenticationType";
|
||||
|
||||
export default interface EmailServer {
|
||||
@@ -21,4 +22,5 @@ export default interface EmailServer {
|
||||
clientSecret?: string | undefined; // OAuth Application Client Secret
|
||||
tokenUrl?: URL | undefined; // OAuth token endpoint URL (e.g., https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token)
|
||||
scope?: string | undefined; // OAuth scope(s), space-separated (e.g., https://outlook.office365.com/.default)
|
||||
oauthProviderType?: OAuthProviderType | undefined; // OAuth grant type: Client Credentials or JWT Bearer
|
||||
}
|
||||
|
||||
28
Common/Types/Email/OAuthProviderType.ts
Normal file
28
Common/Types/Email/OAuthProviderType.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* OAuth Provider Types for SMTP authentication.
|
||||
* Different providers use different OAuth 2.0 grant types.
|
||||
*/
|
||||
enum OAuthProviderType {
|
||||
/**
|
||||
* Client Credentials Grant (RFC 6749)
|
||||
* Used by: Microsoft 365, Azure AD, and most OAuth 2.0 providers
|
||||
*
|
||||
* Required fields: Client ID, Client Secret, Token URL, Scope
|
||||
*/
|
||||
ClientCredentials = "Client Credentials",
|
||||
|
||||
/**
|
||||
* JWT Bearer Assertion Grant (RFC 7523)
|
||||
* Used by: Google Workspace service accounts
|
||||
*
|
||||
* Required fields:
|
||||
* - Client ID: Service account email (client_email from JSON key)
|
||||
* - Client Secret: Private key (private_key from JSON key)
|
||||
* - Token URL: OAuth token endpoint
|
||||
* - Scope: Required scopes
|
||||
* - Username: Email address to impersonate
|
||||
*/
|
||||
JWTBearer = "JWT Bearer",
|
||||
}
|
||||
|
||||
export default OAuthProviderType;
|
||||
@@ -10,7 +10,10 @@ This guide covers how to configure OAuth 2.0 authentication for Microsoft 365 an
|
||||
|
||||
## OAuth 2.0 Authentication
|
||||
|
||||
OAuth 2.0 provides a more secure way to authenticate with email servers, especially for enterprise environments that have disabled basic authentication. OneUptime uses the **client credentials flow** which is ideal for server-to-server communication without user interaction.
|
||||
OAuth 2.0 provides a more secure way to authenticate with email servers, especially for enterprise environments that have disabled basic authentication. OneUptime supports two OAuth grant types:
|
||||
|
||||
- **Client Credentials** - Used by Microsoft 365 and most OAuth providers
|
||||
- **JWT Bearer** - Used by Google Workspace service accounts
|
||||
|
||||
### Required Fields for OAuth
|
||||
|
||||
@@ -22,8 +25,9 @@ When configuring SMTP with OAuth authentication in OneUptime, you'll need:
|
||||
| **Port** | SMTP port (typically 587 for STARTTLS or 465 for implicit TLS) |
|
||||
| **Username** | The email address to send from |
|
||||
| **Authentication Type** | Select "OAuth" |
|
||||
| **Client ID** | Application/Client ID from your OAuth provider |
|
||||
| **Client Secret** | Client secret from your OAuth provider |
|
||||
| **OAuth Provider Type** | Select "Client Credentials" for Microsoft 365, or "JWT Bearer" for Google Workspace |
|
||||
| **Client ID** | Application/Client ID from your OAuth provider (for Google: service account email) |
|
||||
| **Client Secret** | Client secret from your OAuth provider (for Google: private key) |
|
||||
| **Token URL** | OAuth token endpoint URL |
|
||||
| **Scope** | Required OAuth scope(s) for SMTP access |
|
||||
|
||||
@@ -110,6 +114,7 @@ In OneUptime, create or edit an SMTP configuration with these settings:
|
||||
| Port | `587` |
|
||||
| Username | The email address you granted permissions to (e.g., `sender@yourdomain.com`) |
|
||||
| Authentication Type | `OAuth` |
|
||||
| OAuth Provider Type | `Client Credentials` |
|
||||
| Client ID | Your Application (client) ID from Step 1 |
|
||||
| Client Secret | The secret value from Step 2 |
|
||||
| Token URL | `https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token` |
|
||||
@@ -189,15 +194,18 @@ In OneUptime, create or edit an SMTP configuration with these settings:
|
||||
|-------|-------|
|
||||
| Hostname | `smtp.gmail.com` |
|
||||
| Port | `587` |
|
||||
| Username | The Google Workspace email address to send from (e.g., `notifications@yourdomain.com`) |
|
||||
| Username | The Google Workspace email address to send from (e.g., `notifications@yourdomain.com`). This user will be impersonated by the service account. |
|
||||
| Authentication Type | `OAuth` |
|
||||
| Client ID | The `client_id` from your service account JSON |
|
||||
| Client Secret | The `private_key` from your service account JSON |
|
||||
| OAuth Provider Type | `JWT Bearer` |
|
||||
| Client ID | The `client_email` from your service account JSON (e.g., `your-service@your-project.iam.gserviceaccount.com`) |
|
||||
| Client Secret | The `private_key` from your service account JSON (the entire key including `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----`) |
|
||||
| Token URL | `https://oauth2.googleapis.com/token` |
|
||||
| Scope | `https://mail.google.com/` |
|
||||
| From Email | Same as Username |
|
||||
| Secure (TLS) | Enabled |
|
||||
|
||||
**Important:** For Google (JWT Bearer), the Client ID is the **service account email** (`client_email`), NOT the numerical `client_id`. The service account will impersonate the user specified in the Username field to send emails.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Reference in New Issue
Block a user