Skip to content

Commit 11dc413

Browse files
scopsycursoragent
andauthored
feat(api-service): resolve auth from bridge resolver (#10663)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent d027a6b commit 11dc413

2 files changed

Lines changed: 90 additions & 5 deletions

File tree

apps/api/src/app/environments-v1/novu-bridge-client.ts

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { Inject } from '@nestjs/common';
1+
import { Inject, NotFoundException } from '@nestjs/common';
2+
import {
3+
GetDecryptedSecretKey,
4+
GetDecryptedSecretKeyCommand,
5+
InMemoryLRUCacheService,
6+
InMemoryLRUCacheStore,
7+
PinoLogger,
8+
} from '@novu/application-generic';
29
import { PostActionEnum, type Workflow } from '@novu/framework/internal';
310
import { Client, NovuHandler, NovuRequestHandler } from '@novu/framework/nest';
411
import { EnvironmentTypeEnum } from '@novu/shared';
@@ -18,10 +25,23 @@ const frameworkName = 'novu-nest';
1825
export class NovuBridgeClient {
1926
constructor(
2027
@Inject(NovuHandler) private novuHandler: NovuHandler,
21-
private constructFrameworkWorkflow: ConstructFrameworkWorkflow
28+
private constructFrameworkWorkflow: ConstructFrameworkWorkflow,
29+
private getDecryptedSecretKey: GetDecryptedSecretKey,
30+
private inMemoryLRUCacheService: InMemoryLRUCacheService,
31+
private logger: PinoLogger
2232
) {}
2333

2434
public async handleRequest(req: Request, res: Response) {
35+
const environmentId = req.params.environmentId;
36+
if (!environmentId || !String(environmentId).trim()) {
37+
res.status(400).json({
38+
error: 'Missing or invalid environmentId',
39+
details: 'The bridge route requires a non-empty environmentId path parameter.',
40+
});
41+
42+
return;
43+
}
44+
2545
const workflows: Workflow[] = [];
2646

2747
/*
@@ -32,7 +52,7 @@ export class NovuBridgeClient {
3252
if (Object.values(PostActionEnum).includes(req.query.action as PostActionEnum)) {
3353
const programmaticallyConstructedWorkflow = await this.constructFrameworkWorkflow.execute(
3454
ConstructFrameworkWorkflowCommand.create({
35-
environmentId: req.params.environmentId,
55+
environmentId,
3656
workflowId: req.query.workflowId as string,
3757
layoutId: req.query.layoutId as string,
3858
controlValues: req.body.controls,
@@ -46,13 +66,76 @@ export class NovuBridgeClient {
4666
workflows.push(programmaticallyConstructedWorkflow);
4767
}
4868

69+
const cacheKey = `bridge-secret-key:${environmentId}`;
70+
const storeName = InMemoryLRUCacheStore.VALIDATOR;
71+
72+
let secretKey: string;
73+
try {
74+
const resolved = await this.inMemoryLRUCacheService.get(
75+
storeName,
76+
cacheKey,
77+
() =>
78+
this.getDecryptedSecretKey.execute(
79+
GetDecryptedSecretKeyCommand.create({
80+
environmentId,
81+
})
82+
),
83+
{
84+
environmentId,
85+
cacheVariant: 'bridge-secret-key',
86+
}
87+
);
88+
89+
if (typeof resolved !== 'string' || !resolved.trim()) {
90+
this.logger.error(
91+
`Bridge secret key missing or invalid after cache lookup (store=${storeName}, cacheKey=${cacheKey}, environmentId=${environmentId})`
92+
);
93+
res.status(500).json({
94+
error: 'Failed to resolve environment secret key',
95+
details: `Empty or invalid secret from ${storeName} for cache key ${cacheKey}.`,
96+
});
97+
98+
return;
99+
}
100+
101+
secretKey = resolved;
102+
} catch (error) {
103+
if (error instanceof NotFoundException) {
104+
this.logger.warn(
105+
`Environment not found for bridge secret (store=${storeName}, cacheKey=${cacheKey}): ${error.message}`
106+
);
107+
res.status(404).json({
108+
error: 'Environment not found',
109+
details: `No environment for cache key ${cacheKey} (${storeName}).`,
110+
});
111+
112+
return;
113+
}
114+
115+
this.logger.error(
116+
{ err: error },
117+
`Failed to resolve bridge secret key (store=${storeName}, cacheKey=${cacheKey}, environmentId=${environmentId})`
118+
);
119+
res.status(500).json({
120+
error: 'Failed to resolve environment secret key',
121+
details: `Unexpected error while loading secret via ${storeName} for cache key ${cacheKey}.`,
122+
});
123+
124+
return;
125+
}
126+
49127
const novuRequestHandler = new NovuRequestHandler({
50128
frameworkName,
51129
workflows,
52-
client: new Client({ secretKey: 'INTERNAL_KEY', strictAuthentication: false, verbose: false }),
130+
client: new Client({ secretKey, strictAuthentication: true, verbose: false }),
53131
handler: this.novuHandler.handler,
54132
});
55133

56-
await novuRequestHandler.createHandler()(req as any, res as any);
134+
const bridgeHandler = novuRequestHandler.createHandler() as (
135+
request: Request,
136+
response: Response
137+
) => void | Promise<void>;
138+
139+
await bridgeHandler(req, res);
57140
}
58141
}

apps/api/src/app/environments-v1/novu-bridge.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
} from '@novu/dal';
2626
import { NovuClient, NovuHandler } from '@novu/framework/nest';
2727
import { GetOrganizationSettings } from '../organization/usecases/get-organization-settings/get-organization-settings.usecase';
28+
import { SharedModule } from '../shared/shared.module';
2829
import { NovuBridgeController } from './novu-bridge.controller';
2930
import { NovuBridgeClient } from './novu-bridge-client';
3031
import { ConstructFrameworkWorkflow } from './usecases/construct-framework-workflow';
@@ -50,6 +51,7 @@ export const featureFlagsService = {
5051
};
5152

5253
@Module({
54+
imports: [SharedModule],
5355
controllers: [NovuBridgeController],
5456
providers: [
5557
{

0 commit comments

Comments
 (0)