Skip to content

Commit f95f9d8

Browse files
committed
fix: add client side validation on integration credentials
1 parent d55977c commit f95f9d8

File tree

4 files changed

+45
-22
lines changed

4 files changed

+45
-22
lines changed

apps/dashboard/src/components/integrations/components/integration-credentials.tsx

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Input } from '@/components/primitives/input';
22
import { SecretInput } from '@/components/primitives/secret-input';
33
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/primitives/select';
4+
import { Textarea } from '@/components/primitives/textarea';
45
import { Switch } from '@/components/primitives/switch';
56
import { CredentialsKeyEnum, IProviderConfig } from '@novu/shared';
67
import { Control } from 'react-hook-form';
@@ -32,14 +33,24 @@ const SECURE_CREDENTIALS = [
3233
];
3334

3435
export function CredentialsSection({ provider, control }: CredentialsSectionProps) {
36+
console.log(provider);
3537
return (
3638
<div className="border-neutral-alpha-200 bg-background text-foreground-600 mx-0 mt-0 flex flex-col gap-2 rounded-lg border p-3">
3739
{provider?.credentials?.map((credential) => (
3840
<FormField
3941
key={credential.key}
4042
control={control}
4143
name={`credentials.${credential.key}`}
42-
rules={{ required: credential.required ? `${credential.displayName} is required` : false }}
44+
rules={{
45+
required: credential.required ? `${credential.displayName} is required` : false,
46+
validate: credential.validation?.validate,
47+
pattern: credential.validation?.pattern
48+
? {
49+
value: credential.validation.pattern,
50+
message: credential.validation.message || 'Invalid format',
51+
}
52+
: undefined,
53+
}}
4354
render={({ field, fieldState }) => (
4455
<FormItem className="mb-2">
4556
<FormLabel htmlFor={credential.key} optional={!credential.required}>
@@ -66,6 +77,16 @@ export function CredentialsSection({ provider, control }: CredentialsSectionProp
6677
</SelectContent>
6778
</Select>
6879
</FormControl>
80+
) : credential.type === 'textarea' ? (
81+
<FormControl>
82+
<Textarea
83+
id={credential.key}
84+
placeholder={`Enter ${credential.displayName.toLowerCase()}`}
85+
value={field.value || ''}
86+
onChange={field.onChange}
87+
rows={7}
88+
/>
89+
</FormControl>
6990
) : credential.type === 'secret' || SECURE_CREDENTIALS.includes(credential.key as CredentialsKeyEnum) ? (
7091
<FormControl>
7192
<SecretInput
@@ -88,7 +109,7 @@ export function CredentialsSection({ provider, control }: CredentialsSectionProp
88109
</FormControl>
89110
)}
90111

91-
<FormMessage>{credential.description}</FormMessage>
112+
<FormMessage>{fieldState.error?.message || credential.description}</FormMessage>
92113
</FormItem>
93114
)}
94115
/>

packages/providers/src/lib/push/apns/apns.provider.ts

+1-17
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class APNSPushProvider extends BaseProvider implements IPushProvider {
3535
this.config = config;
3636
this.provider = new apn.Provider({
3737
token: {
38-
key: this.validateAndFormatKey(config.key),
38+
key: config.key,
3939
keyId: config.keyId,
4040
teamId: config.teamId,
4141
},
@@ -73,20 +73,4 @@ export class APNSPushProvider extends BaseProvider implements IPushProvider {
7373
date: new Date().toISOString(),
7474
};
7575
}
76-
77-
private validateAndFormatKey(key: string): string {
78-
// Check if key is already properly formatted
79-
const properFormat = /^-----BEGIN PRIVATE KEY-----\n[A-Za-z0-9+/=\n]+\n-----END PRIVATE KEY-----$/;
80-
81-
if (properFormat.test(key)) {
82-
return key; // Key is already in correct format
83-
}
84-
85-
// If not properly formatted, clean and format the key
86-
const cleanKey = key.replace(/-----BEGIN PRIVATE KEY-----|-----END PRIVATE KEY-----|[\s\n\r]/g, '');
87-
88-
const formattedKey = cleanKey.match(/.{1,64}/g)?.join('\n') || '';
89-
90-
return `-----BEGIN PRIVATE KEY-----\n${formattedKey}\n-----END PRIVATE KEY-----`;
91-
}
9276
}

packages/shared/src/consts/providers/credentials/provider-credentials.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,22 @@ export const apnsConfig: IConfigCredentials[] = [
641641
{
642642
key: CredentialsKeyEnum.SecretKey,
643643
displayName: 'Private Key',
644-
type: 'text',
645-
required: true,
644+
type: 'textarea',
645+
required: true,
646+
validation: {
647+
validate: (value: string) => {
648+
try {
649+
// Check if it's a valid PEM format
650+
if (!value.includes('-----BEGIN PRIVATE KEY-----') || !value.includes('-----END PRIVATE KEY-----')) {
651+
return 'Invalid private key format. Must be in PEM format.';
652+
}
653+
654+
return true;
655+
} catch (e) {
656+
return 'Invalid private key format. Must be in PEM format.';
657+
}
658+
},
659+
},
646660
},
647661
{
648662
key: CredentialsKeyEnum.ApiKey,
@@ -668,7 +682,6 @@ export const apnsConfig: IConfigCredentials[] = [
668682
type: 'switch',
669683
required: false,
670684
},
671-
672685
...pushConfigBase,
673686
];
674687

packages/shared/src/consts/providers/provider.interface.ts

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ export interface IConfigCredentials {
2626
name: string;
2727
value: string | null;
2828
}>;
29+
validation?: {
30+
pattern?: RegExp;
31+
message?: string;
32+
validate?: (value: string) => boolean | string;
33+
};
2934
}
3035

3136
export interface ILogoFileName {

0 commit comments

Comments
 (0)