From 0d0b781b2af4b54aaff54880778f5f4df5803673 Mon Sep 17 00:00:00 2001 From: Yerti <36551149+UZ9@users.noreply.github.com> Date: Sun, 16 Nov 2025 02:46:43 -0500 Subject: [PATCH] [PoC] JWT-based project identification --- src/internal/api/authApi.ts | 11 ++- src/lib/analytics.ts | 131 ++++++++++++++++++++------ src/lib/analyticsConfig.ts | 32 ++++++- src/lib/apiCredentials.ts | 4 + src/lib/email.ts | 181 +++++++++++++++++++++++++++--------- src/lib/settings.ts | 33 ++++++- 6 files changed, 309 insertions(+), 83 deletions(-) create mode 100644 src/lib/apiCredentials.ts diff --git a/src/internal/api/authApi.ts b/src/internal/api/authApi.ts index b2ac7ac..f8bc9a8 100644 --- a/src/internal/api/authApi.ts +++ b/src/internal/api/authApi.ts @@ -516,9 +516,9 @@ export class AuthApi { }); } /** - * This endpoint is used to test if authentication is working correctly. - * @summary Test endpoint for validating middleware authentication - * @param authorization Bearer token (API Key or JWT) + * This endpoint validates a user JWT token and returns the associated user information if valid + * @summary Validates user JWT and returns user data + * @param authorization Bearer token (User JWT) */ public async authControllerTestAuth( authorization: string, @@ -557,6 +557,11 @@ export class AuthApi { }; let authenticationPromise = Promise.resolve(); + if (this.authentications.API_Key.accessToken) { + authenticationPromise = authenticationPromise.then(() => + this.authentications.API_Key.applyToRequest(localVarRequestOptions) + ); + } authenticationPromise = authenticationPromise.then(() => this.authentications.default.applyToRequest(localVarRequestOptions) ); diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts index 77b6381..41703e8 100644 --- a/src/lib/analytics.ts +++ b/src/lib/analytics.ts @@ -19,7 +19,7 @@ import { LogVisitEventRequest, VisitEventResponse, } from '../internal/api'; - +import { ApiCredentials } from './apiCredentials'; import { validateString } from './validators'; export class AnalyticsAPI { @@ -217,15 +217,28 @@ export class AnalyticsAPI { } async getCustomEventTypesByProject( - projectName: string + projectName: string, + credentials?: ApiCredentials ): Promise { validateString( projectName, 'The projectName must be provided as an input and has to be nonempty.' ); + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if ( + credentials?.projectId !== undefined && + credentials.projectId !== null + ) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalApi.analyticsControllerGetCustomEventTypes( - projectName + projectName, + { headers } ); return res.body; @@ -255,11 +268,14 @@ export class AnalyticsAPI { return res.body; } - async getAllClickEvents(event: { - projectName: string; - afterTime?: string; - limit?: number; - }): Promise { + async getAllClickEvents( + event: { + projectName: string; + afterTime?: string; + limit?: number; + }, + credentials?: ApiCredentials + ): Promise { const { projectName, afterTime, limit } = event; validateString( @@ -267,19 +283,34 @@ export class AnalyticsAPI { 'The projectName must be provided as an input and has to be nonempty.' ); + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if ( + credentials?.projectId !== undefined && + credentials.projectId !== null + ) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalApi.analyticsControllerGetAllClickEvents( projectName, afterTime, - limit + limit, + { headers } ); return res.body; } - async getAllVisitEvents(event: { - projectName: string; - afterTime?: string; - limit?: number; - }): Promise { + async getAllVisitEvents( + event: { + projectName: string; + afterTime?: string; + limit?: number; + }, + credentials?: ApiCredentials + ): Promise { const { projectName, afterTime, limit } = event; validateString( @@ -287,19 +318,34 @@ export class AnalyticsAPI { 'The projectName must be provided as an input and has to be nonempty.' ); + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if ( + credentials?.projectId !== undefined && + credentials.projectId !== null + ) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalApi.analyticsControllerGetAllVisitEvents( projectName, afterTime, - limit + limit, + { headers } ); return res.body; } - async getAllInputEvents(event: { - projectName: string; - afterTime?: string; - limit?: number; - }): Promise { + async getAllInputEvents( + event: { + projectName: string; + afterTime?: string; + limit?: number; + }, + credentials?: ApiCredentials + ): Promise { const { projectName, afterTime, limit } = event; validateString( @@ -307,21 +353,36 @@ export class AnalyticsAPI { 'The projectName must be provided as an input and has to be nonempty.' ); + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if ( + credentials?.projectId !== undefined && + credentials.projectId !== null + ) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalApi.analyticsControllerGetAllInputEvents( projectName, afterTime, - limit + limit, + { headers } ); return res.body; } - async getAllCustomEvents(event: { - projectName: string; - category: string; - subcategory: string; - afterTime?: string; - limit?: number; - }): Promise { + async getAllCustomEvents( + event: { + projectName: string; + category: string; + subcategory: string; + afterTime?: string; + limit?: number; + }, + credentials?: ApiCredentials + ): Promise { const { projectName, category, subcategory, afterTime, limit } = event; validateString( @@ -339,12 +400,24 @@ export class AnalyticsAPI { 'The subcategory must be provided as an input and has to be nonempty.' ); + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if ( + credentials?.projectId !== undefined && + credentials.projectId !== null + ) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalApi.analyticsControllerGetAllCustomEvents( projectName, category, subcategory, afterTime, - limit + limit, + { headers } ); return res.body; } diff --git a/src/lib/analyticsConfig.ts b/src/lib/analyticsConfig.ts index 003036f..5deb748 100644 --- a/src/lib/analyticsConfig.ts +++ b/src/lib/analyticsConfig.ts @@ -4,6 +4,7 @@ import { CreateAnalyticsConfigModel, UpdateAnalyticsConfigModel, } from '../internal/api'; +import { ApiCredentials } from './apiCredentials'; import { validateString } from './validators'; export class AnalyticsConfigAPI { @@ -14,7 +15,8 @@ export class AnalyticsConfigAPI { } async createAnalyticsConfig( - config: CreateAnalyticsConfigModel + config: CreateAnalyticsConfigModel, + credentials?: ApiCredentials ): Promise { validateString( config.clientAnalyticsKey, @@ -26,24 +28,46 @@ export class AnalyticsConfigAPI { 'The serverAnalyticsKey must be provided as an input and has to be nonempty.' ); + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if ( + credentials?.projectId !== undefined && + credentials.projectId !== null + ) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalApi.analyticsConfigControllerCreateAnalyticsConfig( - config + config, + { headers } ); return res.body; } async getAnalyticsConfig( - projectId: string + projectId: string, + credentials?: ApiCredentials ): Promise { validateString( projectId, 'The projectId must be provided as an input and has to be nonempty.' ); + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalApi.analyticsConfigControllerGetAnalyticsConfig( - projectId + projectId, + { headers } ); return res.body; } diff --git a/src/lib/apiCredentials.ts b/src/lib/apiCredentials.ts new file mode 100644 index 0000000..79d827a --- /dev/null +++ b/src/lib/apiCredentials.ts @@ -0,0 +1,4 @@ +export interface ApiCredentials { + userJwt?: string; + projectId?: number | string; +} diff --git a/src/lib/email.ts b/src/lib/email.ts index 051fb8e..d71a4e0 100644 --- a/src/lib/email.ts +++ b/src/lib/email.ts @@ -15,6 +15,7 @@ import { EmailSenderSendEmailModel, } from '../internal/api'; import { AuthAPI } from './auth'; +import { ApiCredentials } from './apiCredentials'; import { JunoValidationError } from './errors'; import { validateEmailContent, @@ -34,30 +35,42 @@ export class EmailAPI { } async setupEmail( - options: SetupEmailServiceModel + options: SetupEmailServiceModel, + credentials?: ApiCredentials ): Promise { const { sendgridKey } = options; validateSendGridKey(sendgridKey); + const headers: any = { + Authorization: `Bearer ${this.auth.junoApiKey}`, + }; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const result = await this.internalApi.emailControllerSetup(options, { - headers: { - Authorization: `Bearer ${this.auth.junoApiKey}`, - }, + headers, }); return result.body; } - async sendEmail(options: { - recipients?: Array; - cc?: Array; - bcc?: Array; - replyToList?: Array; - sender: EmailSenderSendEmailModel; - subject: string; - contents: Array; - }): Promise { + async sendEmail( + options: { + recipients?: Array; + cc?: Array; + bcc?: Array; + replyToList?: Array; + sender: EmailSenderSendEmailModel; + subject: string; + contents: Array; + }, + credentials?: ApiCredentials + ): Promise { const { recipients, cc, bcc, sender, contents, replyToList } = options; if (!sender || !contents) { throw new JunoValidationError( @@ -94,12 +107,20 @@ export class EmailAPI { sendEmailModel.bcc = bcc ?? []; sendEmailModel.subject = options.subject; + const headers: any = { + Authorization: `Bearer ${this.auth.junoApiKey}`, + }; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const result = await this.internalApi.emailControllerSendEmail( sendEmailModel, { - headers: { - Authorization: `Bearer ${this.auth.junoApiKey}`, - }, + headers, } ); return result.body; @@ -107,17 +128,20 @@ export class EmailAPI { throw e; } } - async registerSenderAddress(options: { - email: string; - name: string; - replyTo: string | undefined; - nickname: string; - address: string; - city: string; - state: string; - zip: string; - country: string; - }): Promise { + async registerSenderAddress( + options: { + email: string; + name: string; + replyTo: string | undefined; + nickname: string; + address: string; + city: string; + state: string; + zip: string; + country: string; + }, + credentials?: ApiCredentials + ): Promise { let { email, name, replyTo, nickname, address, city, state, zip, country } = options; @@ -147,13 +171,21 @@ export class EmailAPI { registerEmailModel.state = state; registerEmailModel.country = country; + const headers: any = { + Authorization: `Bearer ${this.auth.junoApiKey}`, + }; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const result = await this.internalApi.emailControllerRegisterSenderAddress( registerEmailModel, { - headers: { - Authorization: `Bearer ${this.auth.junoApiKey}`, - }, + headers, } ); return result.body; @@ -161,10 +193,13 @@ export class EmailAPI { throw e; } } - async registerDomain(options: { - domain: string; - subdomain: string | undefined; - }): Promise { + async registerDomain( + options: { + domain: string; + subdomain: string | undefined; + }, + credentials?: ApiCredentials + ): Promise { const { domain, subdomain } = options; validateString(domain, 'Domain cannot be null or empty string'); @@ -174,12 +209,20 @@ export class EmailAPI { registerDomainModel.domain = domain; registerDomainModel.subdomain = subdomain; + const headers: any = { + Authorization: `Bearer ${this.auth.junoApiKey}`, + }; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const result = await this.internalApi.emailControllerRegisterEmailDomain( registerDomainModel, { - headers: { - Authorization: `Bearer ${this.auth.junoApiKey}`, - }, + headers, } ); return result.body; @@ -187,9 +230,12 @@ export class EmailAPI { throw e; } } - async verifyDomain(options: { - domain: string; - }): Promise { + async verifyDomain( + options: { + domain: string; + }, + credentials?: ApiCredentials + ): Promise { const { domain } = options; validateString(domain, 'Domain cannot be null or empty string'); @@ -198,12 +244,20 @@ export class EmailAPI { const verifyDomainModel = new VerifyDomainModel(); verifyDomainModel.domain = domain; + const headers: any = { + Authorization: `Bearer ${this.auth.junoApiKey}`, + }; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const result = await this.internalApi.emailControllerVerifySenderDomain( verifyDomainModel, { - headers: { - Authorization: `Bearer ${this.auth.junoApiKey}`, - }, + headers, } ); return result.body; @@ -211,4 +265,45 @@ export class EmailAPI { throw e; } } + + async getStatistics( + options: { + startDate: string; + endDate?: string; + limit?: number; + offset?: number; + aggregatedBy?: string; + }, + credentials?: ApiCredentials + ): Promise { + const { startDate, endDate, limit, offset, aggregatedBy } = options; + + validateString(startDate, 'startDate must be non-empty'); + + const headers: any = { + Authorization: `Bearer ${this.auth.junoApiKey}`, + }; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + + const queryParams: any = { startDate }; + if (endDate) queryParams.endDate = endDate; + if (limit !== undefined) queryParams.limit = limit; + if (offset !== undefined) queryParams.offset = offset; + if (aggregatedBy) queryParams.aggregatedBy = aggregatedBy; + + const result = await this.internalApi.emailControllerGetStatistics( + startDate, + limit, + offset, + aggregatedBy as any, + endDate, + { headers } + ); + return result.body; + } } diff --git a/src/lib/settings.ts b/src/lib/settings.ts index a7f6f60..90ef6e4 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -4,6 +4,7 @@ import { FileConfigApi, FileConfigResponse, } from '../internal/api'; +import { ApiCredentials } from './apiCredentials'; export class SettingsAPI { private internalFileConfigApi: FileConfigApi; @@ -16,17 +17,41 @@ export class SettingsAPI { this.internalEmailApi.accessToken = apiKey; } - async getFileConfig(projectId: string): Promise { + async getFileConfig( + projectId: string, + credentials?: ApiCredentials + ): Promise { + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalFileConfigApi.fileConfigControllerGetFileConfigByProjectId( - projectId + projectId, + { headers } ); return res.body; } - async getEmailConfig(projectId: string): Promise { + async getEmailConfig( + projectId: string, + credentials?: ApiCredentials + ): Promise { + const headers: any = {}; + if (credentials?.userJwt) { + headers['X-User-JWT'] = credentials.userJwt; + } + if (credentials?.projectId !== undefined) { + headers['X-Project-Id'] = String(credentials.projectId); + } + const res = await this.internalEmailApi.emailControllerGetEmailConfigById( - projectId + projectId, + { headers } ); return res.body; }