secret credentials mpped and template preview modified
This commit is contained in:
parent
f69814ce98
commit
4cf7288857
@ -1,16 +1,20 @@
|
|||||||
import { SSOConfig, SSOUserData } from '../types/auth.types';
|
import { SSOConfig, SSOUserData } from '../types/auth.types';
|
||||||
|
|
||||||
|
// Use getter functions to read from process.env dynamically
|
||||||
|
// This ensures values are read after secrets are loaded from Google Secret Manager
|
||||||
const ssoConfig: SSOConfig = {
|
const ssoConfig: SSOConfig = {
|
||||||
jwtSecret: process.env.JWT_SECRET || '',
|
get jwtSecret() { return process.env.JWT_SECRET || ''; },
|
||||||
jwtExpiry: process.env.JWT_EXPIRY || '24h',
|
get jwtExpiry() { return process.env.JWT_EXPIRY || '24h'; },
|
||||||
refreshTokenExpiry: process.env.REFRESH_TOKEN_EXPIRY || '7d',
|
get refreshTokenExpiry() { return process.env.REFRESH_TOKEN_EXPIRY || '7d'; },
|
||||||
sessionSecret: process.env.SESSION_SECRET || '',
|
get sessionSecret() { return process.env.SESSION_SECRET || ''; },
|
||||||
// Use only FRONTEND_URL from environment - no fallbacks
|
// Use only FRONTEND_URL from environment - no fallbacks
|
||||||
allowedOrigins: process.env.FRONTEND_URL?.split(',').map(s => s.trim()).filter(Boolean) || [],
|
get allowedOrigins() {
|
||||||
|
return process.env.FRONTEND_URL?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
||||||
|
},
|
||||||
// Okta/Auth0 configuration for token exchange
|
// Okta/Auth0 configuration for token exchange
|
||||||
oktaDomain: process.env.OKTA_DOMAIN || 'https://dev-830839.oktapreview.com',
|
get oktaDomain() { return process.env.OKTA_DOMAIN || 'https://dev-830839.oktapreview.com'; },
|
||||||
oktaClientId: process.env.OKTA_CLIENT_ID || '',
|
get oktaClientId() { return process.env.OKTA_CLIENT_ID || ''; },
|
||||||
oktaClientSecret: process.env.OKTA_CLIENT_SECRET || '',
|
get oktaClientSecret() { return process.env.OKTA_CLIENT_SECRET || ''; },
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ssoConfig };
|
export { ssoConfig };
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApprovalConfirmationData } from './types';
|
import { ApprovalConfirmationData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, getNextStepsSection, wrapRichText, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, getNextStepsSection, wrapRichText, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getApprovalConfirmationEmail(data: ApprovalConfirmationData): string {
|
export function getApprovalConfirmationEmail(data: ApprovalConfirmationData): string {
|
||||||
@ -31,7 +31,7 @@ export function getApprovalConfirmationEmail(data: ApprovalConfirmationData): st
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Request Approved',
|
title: 'Request Approved',
|
||||||
...HeaderStyles.success
|
...HeaderStyles.success
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApprovalRequestData } from './types';
|
import { ApprovalRequestData } from './types';
|
||||||
import { getEmailFooter, getPrioritySection, getEmailHeader, HeaderStyles, getResponsiveStyles, wrapRichText } from './helpers';
|
import { getEmailFooter, getPrioritySection, getEmailHeader, HeaderStyles, getResponsiveStyles, wrapRichText, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getApprovalRequestEmail(data: ApprovalRequestData): string {
|
export function getApprovalRequestEmail(data: ApprovalRequestData): string {
|
||||||
@ -22,7 +22,7 @@ export function getApprovalRequestEmail(data: ApprovalRequestData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Approval Request',
|
title: 'Approval Request',
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApproverSkippedData } from './types';
|
import { ApproverSkippedData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getApproverSkippedEmail(data: ApproverSkippedData): string {
|
export function getApproverSkippedEmail(data: ApproverSkippedData): string {
|
||||||
@ -22,7 +22,7 @@ export function getApproverSkippedEmail(data: ApproverSkippedData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Approval Level Skipped',
|
title: 'Approval Level Skipped',
|
||||||
...HeaderStyles.infoSecondary
|
...HeaderStyles.infoSecondary
|
||||||
|
|||||||
@ -144,6 +144,15 @@ export function wrapRichText(htmlContent: string): string {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get inline styles for email container table
|
||||||
|
* This ensures width is preserved when emails are forwarded
|
||||||
|
* Email clients often strip CSS classes, so inline styles are critical
|
||||||
|
*/
|
||||||
|
export function getEmailContainerStyles(): string {
|
||||||
|
return 'width: 95%; max-width: 1200px; min-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate all email styles (responsive + rich text)
|
* Generate all email styles (responsive + rich text)
|
||||||
* Desktop-first design (optimized for browser) with mobile responsive breakpoints
|
* Desktop-first design (optimized for browser) with mobile responsive breakpoints
|
||||||
@ -175,8 +184,22 @@ export function getResponsiveStyles(): string {
|
|||||||
|
|
||||||
/* Desktop-first base styles */
|
/* Desktop-first base styles */
|
||||||
.email-container {
|
.email-container {
|
||||||
width: 95%;
|
width: 95% !important;
|
||||||
max-width: 1200px;
|
max-width: 1200px !important;
|
||||||
|
min-width: 600px !important; /* Prevent shrinking below 600px when forwarded */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force full width for forwarded emails - use inline styles in templates */
|
||||||
|
table.email-container {
|
||||||
|
width: 95% !important;
|
||||||
|
max-width: 1200px !important;
|
||||||
|
min-width: 600px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapper table to force full width even when forwarded */
|
||||||
|
.email-wrapper {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-content {
|
.email-content {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { MultiApproverRequestData } from './types';
|
import { MultiApproverRequestData } from './types';
|
||||||
import { getEmailFooter, getPrioritySection, getApprovalChain, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getPrioritySection, getApprovalChain, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getMultiApproverRequestEmail(data: MultiApproverRequestData): string {
|
export function getMultiApproverRequestEmail(data: MultiApproverRequestData): string {
|
||||||
@ -22,7 +22,7 @@ export function getMultiApproverRequestEmail(data: MultiApproverRequestData): st
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Multi-Level Approval Request',
|
title: 'Multi-Level Approval Request',
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ParticipantAddedData } from './types';
|
import { ParticipantAddedData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, getPermissionsContent, getRoleDescription, wrapRichText, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, getPermissionsContent, getRoleDescription, wrapRichText, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getParticipantAddedEmail(data: ParticipantAddedData): string {
|
export function getParticipantAddedEmail(data: ParticipantAddedData): string {
|
||||||
@ -22,7 +22,7 @@ export function getParticipantAddedEmail(data: ParticipantAddedData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: `You've Been Added as ${data.participantRole}`,
|
title: `You've Been Added as ${data.participantRole}`,
|
||||||
...HeaderStyles.info
|
...HeaderStyles.info
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { RejectionNotificationData } from './types';
|
import { RejectionNotificationData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getRejectionNotificationEmail(data: RejectionNotificationData): string {
|
export function getRejectionNotificationEmail(data: RejectionNotificationData): string {
|
||||||
@ -22,7 +22,7 @@ export function getRejectionNotificationEmail(data: RejectionNotificationData):
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Request Rejected',
|
title: 'Request Rejected',
|
||||||
...HeaderStyles.error
|
...HeaderStyles.error
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { RequestClosedData } from './types';
|
import { RequestClosedData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, getConclusionSection, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, getConclusionSection, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getRequestClosedEmail(data: RequestClosedData): string {
|
export function getRequestClosedEmail(data: RequestClosedData): string {
|
||||||
@ -22,7 +22,7 @@ export function getRequestClosedEmail(data: RequestClosedData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Request Closed',
|
title: 'Request Closed',
|
||||||
...HeaderStyles.complete
|
...HeaderStyles.complete
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { RequestCreatedData } from './types';
|
import { RequestCreatedData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, getResponsiveStyles, wrapRichText } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, getResponsiveStyles, wrapRichText, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getRequestCreatedEmail(data: RequestCreatedData): string {
|
export function getRequestCreatedEmail(data: RequestCreatedData): string {
|
||||||
@ -22,7 +22,7 @@ export function getRequestCreatedEmail(data: RequestCreatedData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Request Created Successfully',
|
title: 'Request Created Successfully',
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { TATBreachedData } from './types';
|
import { TATBreachedData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getTATBreachedEmail(data: TATBreachedData): string {
|
export function getTATBreachedEmail(data: TATBreachedData): string {
|
||||||
@ -22,7 +22,7 @@ export function getTATBreachedEmail(data: TATBreachedData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'TAT Breached',
|
title: 'TAT Breached',
|
||||||
subtitle: 'Immediate Action Required',
|
subtitle: 'Immediate Action Required',
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { TATReminderData } from './types';
|
import { TATReminderData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,7 +52,7 @@ export function getTATReminderEmail(data: TATReminderData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'TAT Reminder',
|
title: 'TAT Reminder',
|
||||||
subtitle: `${data.thresholdPercentage}% Elapsed - ${urgencyStyle.title}`,
|
subtitle: `${data.thresholdPercentage}% Elapsed - ${urgencyStyle.title}`,
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { WorkflowPausedData } from './types';
|
import { WorkflowPausedData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, wrapRichText, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getWorkflowPausedEmail(data: WorkflowPausedData): string {
|
export function getWorkflowPausedEmail(data: WorkflowPausedData): string {
|
||||||
@ -22,7 +22,7 @@ export function getWorkflowPausedEmail(data: WorkflowPausedData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Workflow Paused',
|
title: 'Workflow Paused',
|
||||||
...HeaderStyles.neutral
|
...HeaderStyles.neutral
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { WorkflowResumedData } from './types';
|
import { WorkflowResumedData } from './types';
|
||||||
import { getEmailFooter, getEmailHeader, HeaderStyles, getActionRequiredSection, getResponsiveStyles } from './helpers';
|
import { getEmailFooter, getEmailHeader, HeaderStyles, getActionRequiredSection, getResponsiveStyles, getEmailContainerStyles } from './helpers';
|
||||||
import { getBrandedHeader } from './branding.config';
|
import { getBrandedHeader } from './branding.config';
|
||||||
|
|
||||||
export function getWorkflowResumedEmail(data: WorkflowResumedData): string {
|
export function getWorkflowResumedEmail(data: WorkflowResumedData): string {
|
||||||
@ -22,7 +22,7 @@ export function getWorkflowResumedEmail(data: WorkflowResumedData): string {
|
|||||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #f4f4f4;" cellpadding="0" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 40px 0;">
|
<td style="padding: 40px 0;">
|
||||||
<table role="presentation" class="email-container" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" cellpadding="0" cellspacing="0">
|
<table role="presentation" class="email-container" style="${getEmailContainerStyles()}" cellpadding="0" cellspacing="0">
|
||||||
${getEmailHeader(getBrandedHeader({
|
${getEmailHeader(getBrandedHeader({
|
||||||
title: 'Workflow Resumed',
|
title: 'Workflow Resumed',
|
||||||
...HeaderStyles.success
|
...HeaderStyles.success
|
||||||
|
|||||||
@ -139,15 +139,24 @@ class GoogleSecretManagerService {
|
|||||||
logger.debug(`[Secret Manager] Secret ${fullSecretName} exists but payload is empty`);
|
logger.debug(`[Secret Manager] Secret ${fullSecretName} exists but payload is empty`);
|
||||||
return null;
|
return null;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
const isOktaSecret = /OKTA_/i.test(secretName);
|
||||||
|
const logLevel = isOktaSecret ? logger.info.bind(logger) : logger.debug.bind(logger);
|
||||||
|
|
||||||
// Handle "not found" errors (code 5 = NOT_FOUND)
|
// Handle "not found" errors (code 5 = NOT_FOUND)
|
||||||
if (error.code === 5 || error.code === 'NOT_FOUND' || error.message?.includes('not found')) {
|
if (error.code === 5 || error.code === 'NOT_FOUND' || error.message?.includes('not found')) {
|
||||||
logger.debug(`[Secret Manager] Secret not found: ${fullSecretName} (project: ${this.projectId})`);
|
logLevel(`[Secret Manager] Secret not found: ${fullSecretName} (project: ${this.projectId})`);
|
||||||
|
if (isOktaSecret) {
|
||||||
|
logger.info(`[Secret Manager] Searched path: projects/${this.projectId}/secrets/${fullSecretName}/versions/latest`);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle permission errors (code 7 = PERMISSION_DENIED)
|
// Handle permission errors (code 7 = PERMISSION_DENIED)
|
||||||
if (error.code === 7 || error.code === 'PERMISSION_DENIED' || error.message?.includes('Permission denied')) {
|
if (error.code === 7 || error.code === 'PERMISSION_DENIED' || error.message?.includes('Permission denied')) {
|
||||||
logger.warn(`[Secret Manager] ❌ Permission denied for secret '${fullSecretName}'`);
|
logger.warn(`[Secret Manager] ❌ Permission denied for secret '${fullSecretName}'`);
|
||||||
|
if (isOktaSecret) {
|
||||||
|
logger.warn(`[Secret Manager] This is an OKTA secret - check service account permissions`);
|
||||||
|
}
|
||||||
logger.warn(`[Secret Manager] Service account needs 'Secret Manager Secret Accessor' role`);
|
logger.warn(`[Secret Manager] Service account needs 'Secret Manager Secret Accessor' role`);
|
||||||
logger.warn(`[Secret Manager] To grant access, run:`);
|
logger.warn(`[Secret Manager] To grant access, run:`);
|
||||||
logger.warn(`[Secret Manager] gcloud secrets add-iam-policy-binding ${fullSecretName} \\`);
|
logger.warn(`[Secret Manager] gcloud secrets add-iam-policy-binding ${fullSecretName} \\`);
|
||||||
@ -157,11 +166,12 @@ class GoogleSecretManagerService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log full error details for debugging
|
// Log full error details for debugging (info level for OKTA secrets)
|
||||||
logger.warn(`[Secret Manager] Failed to fetch secret '${fullSecretName}' (project: ${this.projectId})`);
|
const errorLogLevel = isOktaSecret ? logger.warn.bind(logger) : logger.warn.bind(logger);
|
||||||
logger.warn(`[Secret Manager] Error code: ${error.code || 'unknown'}, Message: ${error.message || 'no message'}`);
|
errorLogLevel(`[Secret Manager] Failed to fetch secret '${fullSecretName}' (project: ${this.projectId})`);
|
||||||
|
errorLogLevel(`[Secret Manager] Error code: ${error.code || 'unknown'}, Message: ${error.message || 'no message'}`);
|
||||||
if (error.details) {
|
if (error.details) {
|
||||||
logger.warn(`[Secret Manager] Error details: ${JSON.stringify(error.details)}`);
|
errorLogLevel(`[Secret Manager] Error details: ${JSON.stringify(error.details)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -216,26 +226,61 @@ class GoogleSecretManagerService {
|
|||||||
const notFoundSecrets: string[] = [];
|
const notFoundSecrets: string[] = [];
|
||||||
let loadedCount = 0;
|
let loadedCount = 0;
|
||||||
|
|
||||||
|
// Log OKTA and EMAIL secrets specifically if they're in the list
|
||||||
|
const oktaSecrets = secretsToLoad.filter(name => /^OKTA_/i.test(name));
|
||||||
|
const emailSecrets = secretsToLoad.filter(name => /^EMAIL_|^SMTP_/i.test(name));
|
||||||
|
if (oktaSecrets.length > 0) {
|
||||||
|
logger.info(`[Secret Manager] 🔍 Attempting to load OKTA secrets: ${oktaSecrets.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (emailSecrets.length > 0) {
|
||||||
|
logger.info(`[Secret Manager] 📧 Attempting to load EMAIL secrets: ${emailSecrets.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Load each secret
|
// Load each secret
|
||||||
for (const secretName of secretsToLoad) {
|
for (const secretName of secretsToLoad) {
|
||||||
|
const fullSecretName = this.secretPrefix
|
||||||
|
? `${this.secretPrefix}-${secretName}`
|
||||||
|
: secretName;
|
||||||
|
|
||||||
|
// Log OKTA and EMAIL secret attempts in detail
|
||||||
|
const isOktaSecret = /^OKTA_/i.test(secretName);
|
||||||
|
const isEmailSecret = /^EMAIL_|^SMTP_/i.test(secretName);
|
||||||
|
if (isOktaSecret || isEmailSecret) {
|
||||||
|
logger.info(`[Secret Manager] Attempting to load: ${secretName} (full name: ${fullSecretName})`);
|
||||||
|
}
|
||||||
|
|
||||||
const secretValue = await this.getSecret(secretName);
|
const secretValue = await this.getSecret(secretName);
|
||||||
|
|
||||||
if (secretValue !== null) {
|
if (secretValue !== null) {
|
||||||
const envVarName = this.getEnvVarName(secretName);
|
const envVarName = this.getEnvVarName(secretName);
|
||||||
loadedSecrets[envVarName] = secretValue;
|
loadedSecrets[envVarName] = secretValue;
|
||||||
loadedCount++;
|
loadedCount++;
|
||||||
|
if (isOktaSecret || isEmailSecret) {
|
||||||
|
logger.info(`[Secret Manager] ✅ Successfully loaded: ${secretName} -> ${envVarName}`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Track which secrets weren't found for better logging
|
// Track which secrets weren't found for better logging
|
||||||
const fullSecretName = this.secretPrefix
|
|
||||||
? `${this.secretPrefix}-${secretName}`
|
|
||||||
: secretName;
|
|
||||||
notFoundSecrets.push(fullSecretName);
|
notFoundSecrets.push(fullSecretName);
|
||||||
|
if (isOktaSecret || isEmailSecret) {
|
||||||
|
logger.warn(`[Secret Manager] ❌ Not found: ${secretName} (searched as: ${fullSecretName})`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge secrets into process.env (only override if secret exists)
|
// Merge secrets into process.env (only override if secret exists)
|
||||||
|
// Log when overriding existing env vars vs setting new ones
|
||||||
for (const [envVar, value] of Object.entries(loadedSecrets)) {
|
for (const [envVar, value] of Object.entries(loadedSecrets)) {
|
||||||
|
const existingValue = process.env[envVar];
|
||||||
|
const isOverriding = existingValue !== undefined;
|
||||||
|
|
||||||
process.env[envVar] = value;
|
process.env[envVar] = value;
|
||||||
|
|
||||||
|
// Log override behavior for debugging
|
||||||
|
if (isOverriding) {
|
||||||
|
logger.debug(`[Secret Manager] 🔄 Overrode existing env var: ${envVar} (was: ${existingValue ? 'set' : 'undefined'}, now: from Secret Manager)`);
|
||||||
|
} else {
|
||||||
|
logger.debug(`[Secret Manager] ✨ Set new env var: ${envVar} (from Secret Manager)`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`[Secret Manager] ✅ Successfully loaded ${loadedCount}/${secretsToLoad.length} secrets`);
|
logger.info(`[Secret Manager] ✅ Successfully loaded ${loadedCount}/${secretsToLoad.length} secrets`);
|
||||||
@ -251,6 +296,33 @@ class GoogleSecretManagerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log summary of not found secrets, especially OKTA and EMAIL ones
|
||||||
|
if (notFoundSecrets.length > 0) {
|
||||||
|
const notFoundOkta = notFoundSecrets.filter(name => /OKTA_/i.test(name));
|
||||||
|
const notFoundEmail = notFoundSecrets.filter(name => /EMAIL_|SMTP_/i.test(name));
|
||||||
|
|
||||||
|
if (notFoundOkta.length > 0) {
|
||||||
|
logger.warn(`[Secret Manager] ⚠️ OKTA secrets not found (${notFoundOkta.length}): ${notFoundOkta.join(', ')}`);
|
||||||
|
logger.info(`[Secret Manager] 💡 To create OKTA secrets, use:`);
|
||||||
|
notFoundOkta.forEach(secretName => {
|
||||||
|
logger.info(`[Secret Manager] gcloud secrets create ${secretName} --data-file=- --project=${this.projectId}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notFoundEmail.length > 0) {
|
||||||
|
logger.warn(`[Secret Manager] ⚠️ EMAIL secrets not found (${notFoundEmail.length}): ${notFoundEmail.join(', ')}`);
|
||||||
|
logger.info(`[Secret Manager] 💡 To create EMAIL secrets, use:`);
|
||||||
|
notFoundEmail.forEach(secretName => {
|
||||||
|
logger.info(`[Secret Manager] gcloud secrets create ${secretName} --data-file=- --project=${this.projectId}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherNotFound = notFoundSecrets.filter(name => !/OKTA_|EMAIL_|SMTP_/i.test(name));
|
||||||
|
if (otherNotFound.length > 0) {
|
||||||
|
logger.debug(`[Secret Manager] Other secrets not found (${otherNotFound.length}): ${otherNotFound.slice(0, 10).join(', ')}${otherNotFound.length > 10 ? '...' : ''}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error('[Secret Manager] Failed to load secrets:', error);
|
logger.error('[Secret Manager] Failed to load secrets:', error);
|
||||||
@ -265,10 +337,6 @@ class GoogleSecretManagerService {
|
|||||||
private getDefaultSecretNames(): string[] {
|
private getDefaultSecretNames(): string[] {
|
||||||
return [
|
return [
|
||||||
// Database
|
// Database
|
||||||
'DB_HOST',
|
|
||||||
'DB_PORT',
|
|
||||||
'DB_NAME',
|
|
||||||
'DB_USER',
|
|
||||||
'DB_PASSWORD',
|
'DB_PASSWORD',
|
||||||
|
|
||||||
// JWT & Session
|
// JWT & Session
|
||||||
@ -277,7 +345,6 @@ class GoogleSecretManagerService {
|
|||||||
'SESSION_SECRET',
|
'SESSION_SECRET',
|
||||||
|
|
||||||
// Okta/SSO
|
// Okta/SSO
|
||||||
'OKTA_DOMAIN',
|
|
||||||
'OKTA_CLIENT_ID',
|
'OKTA_CLIENT_ID',
|
||||||
'OKTA_CLIENT_SECRET',
|
'OKTA_CLIENT_SECRET',
|
||||||
'OKTA_API_TOKEN',
|
'OKTA_API_TOKEN',
|
||||||
@ -287,15 +354,7 @@ class GoogleSecretManagerService {
|
|||||||
'SMTP_PORT',
|
'SMTP_PORT',
|
||||||
'SMTP_USER',
|
'SMTP_USER',
|
||||||
'SMTP_PASSWORD',
|
'SMTP_PASSWORD',
|
||||||
|
'EMAIL_FROM',
|
||||||
// VAPID (Web Push)
|
|
||||||
'VAPID_PUBLIC_KEY',
|
|
||||||
'VAPID_PRIVATE_KEY',
|
|
||||||
|
|
||||||
// Loki
|
|
||||||
'LOKI_HOST',
|
|
||||||
'LOKI_USER',
|
|
||||||
'LOKI_PASSWORD',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user