Add Legal Office seal and complete Azure CDN deployment
- Add Legal Office of the Master seal (SVG design with Maltese Cross, scales of justice, legal scroll) - Create legal-office-manifest-template.json for Legal Office credentials - Update SEAL_MAPPING.md and DESIGN_GUIDE.md with Legal Office seal documentation - Complete Azure CDN infrastructure deployment: - Resource group, storage account, and container created - 17 PNG seal files uploaded to Azure Blob Storage - All manifest templates updated with Azure URLs - Configuration files generated (azure-cdn-config.env) - Add comprehensive Azure CDN setup scripts and documentation - Fix manifest URL generation to prevent double slashes - Verify all seals accessible via HTTPS
This commit is contained in:
@@ -49,6 +49,16 @@ const envSchema = z.object({
|
||||
ENTRA_CLIENT_ID: z.string().optional(),
|
||||
ENTRA_CLIENT_SECRET: z.string().optional(),
|
||||
ENTRA_CREDENTIAL_MANIFEST_ID: z.string().optional(),
|
||||
ENTRA_MANIFESTS: z.string().optional(), // JSON object mapping manifest names to IDs
|
||||
// Entra Rate Limiting
|
||||
ENTRA_RATE_LIMIT_ISSUANCE: z.string().optional(),
|
||||
ENTRA_RATE_LIMIT_VERIFICATION: z.string().optional(),
|
||||
ENTRA_RATE_LIMIT_STATUS_CHECK: z.string().optional(),
|
||||
ENTRA_RATE_LIMIT_GLOBAL: z.string().optional(),
|
||||
// Credential Display/Images
|
||||
ENTRA_CREDENTIAL_LOGO_URI: z.string().url().optional(),
|
||||
ENTRA_CREDENTIAL_BG_COLOR: z.string().optional(),
|
||||
ENTRA_CREDENTIAL_TEXT_COLOR: z.string().optional(),
|
||||
|
||||
// Credential Rate Limiting
|
||||
CREDENTIAL_RATE_LIMIT_PER_USER: z.string().optional(),
|
||||
|
||||
@@ -10,6 +10,7 @@ export * from './middleware';
|
||||
export * from './validation';
|
||||
export * from './auth';
|
||||
export * from './rate-limit-credential';
|
||||
export * from './rate-limit-entra';
|
||||
export * from './authorization';
|
||||
export * from './compliance';
|
||||
export * from './retry';
|
||||
|
||||
144
packages/shared/src/rate-limit-entra.ts
Normal file
144
packages/shared/src/rate-limit-entra.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Entra VerifiedID API rate limiting
|
||||
* Specific rate limits for Entra VerifiedID endpoints to prevent API quota exhaustion
|
||||
*/
|
||||
|
||||
import { FastifyInstance, FastifyRequest } from 'fastify';
|
||||
import fastifyRateLimit from '@fastify/rate-limit';
|
||||
import { getEnv } from './env';
|
||||
|
||||
export interface EntraRateLimitConfig {
|
||||
issuance?: {
|
||||
max: number;
|
||||
timeWindow: string | number;
|
||||
};
|
||||
verification?: {
|
||||
max: number;
|
||||
timeWindow: string | number;
|
||||
};
|
||||
statusCheck?: {
|
||||
max: number;
|
||||
timeWindow: string | number;
|
||||
};
|
||||
global?: {
|
||||
max: number;
|
||||
timeWindow: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Entra VerifiedID-specific rate limiting
|
||||
*/
|
||||
export async function registerEntraRateLimit(
|
||||
server: FastifyInstance,
|
||||
config?: EntraRateLimitConfig
|
||||
): Promise<void> {
|
||||
const env = getEnv();
|
||||
|
||||
// Default configuration - conservative limits to avoid API quota issues
|
||||
const defaultConfig: Required<EntraRateLimitConfig> = {
|
||||
issuance: {
|
||||
max: parseInt(env.ENTRA_RATE_LIMIT_ISSUANCE || '10', 10),
|
||||
timeWindow: '1 minute',
|
||||
},
|
||||
verification: {
|
||||
max: parseInt(env.ENTRA_RATE_LIMIT_VERIFICATION || '20', 10),
|
||||
timeWindow: '1 minute',
|
||||
},
|
||||
statusCheck: {
|
||||
max: parseInt(env.ENTRA_RATE_LIMIT_STATUS_CHECK || '30', 10),
|
||||
timeWindow: '1 minute',
|
||||
},
|
||||
global: {
|
||||
max: parseInt(env.ENTRA_RATE_LIMIT_GLOBAL || '50', 10),
|
||||
timeWindow: '1 minute',
|
||||
},
|
||||
};
|
||||
|
||||
const finalConfig = { ...defaultConfig, ...config };
|
||||
|
||||
// Global Entra API rate limit
|
||||
await server.register(fastifyRateLimit, {
|
||||
max: finalConfig.global.max,
|
||||
timeWindow: finalConfig.global.timeWindow,
|
||||
keyGenerator: (request: FastifyRequest) => {
|
||||
// Rate limit by IP for Entra endpoints
|
||||
return `entra:global:${request.ip}`;
|
||||
},
|
||||
errorResponseBuilder: (_request, context) => {
|
||||
return {
|
||||
error: {
|
||||
code: 'ENTRA_RATE_LIMIT_EXCEEDED',
|
||||
message: `Entra API rate limit exceeded, retry in ${Math.ceil(context.ttl / 1000)} seconds`,
|
||||
},
|
||||
};
|
||||
},
|
||||
skipOnError: false,
|
||||
});
|
||||
|
||||
// Issuance-specific rate limit
|
||||
await server.register(fastifyRateLimit, {
|
||||
max: finalConfig.issuance.max,
|
||||
timeWindow: finalConfig.issuance.timeWindow,
|
||||
keyGenerator: (request: FastifyRequest) => {
|
||||
const userId = (request as any).user?.id || 'anonymous';
|
||||
return `entra:issuance:${userId}:${request.ip}`;
|
||||
},
|
||||
errorResponseBuilder: (_request, context) => {
|
||||
return {
|
||||
error: {
|
||||
code: 'ENTRA_ISSUANCE_RATE_LIMIT_EXCEEDED',
|
||||
message: `Entra issuance rate limit exceeded, retry in ${Math.ceil(context.ttl / 1000)} seconds`,
|
||||
},
|
||||
};
|
||||
},
|
||||
skipOnError: false,
|
||||
});
|
||||
|
||||
// Verification-specific rate limit
|
||||
await server.register(fastifyRateLimit, {
|
||||
max: finalConfig.verification.max,
|
||||
timeWindow: finalConfig.verification.timeWindow,
|
||||
keyGenerator: (request: FastifyRequest) => {
|
||||
return `entra:verification:${request.ip}`;
|
||||
},
|
||||
errorResponseBuilder: (_request, context) => {
|
||||
return {
|
||||
error: {
|
||||
code: 'ENTRA_VERIFICATION_RATE_LIMIT_EXCEEDED',
|
||||
message: `Entra verification rate limit exceeded, retry in ${Math.ceil(context.ttl / 1000)} seconds`,
|
||||
},
|
||||
};
|
||||
},
|
||||
skipOnError: false,
|
||||
});
|
||||
|
||||
// Status check-specific rate limit
|
||||
await server.register(fastifyRateLimit, {
|
||||
max: finalConfig.statusCheck.max,
|
||||
timeWindow: finalConfig.statusCheck.timeWindow,
|
||||
keyGenerator: (request: FastifyRequest) => {
|
||||
const requestId = (request.params as any)?.requestId || (request.query as any)?.requestId || 'unknown';
|
||||
return `entra:status:${requestId}`;
|
||||
},
|
||||
errorResponseBuilder: (_request, context) => {
|
||||
return {
|
||||
error: {
|
||||
code: 'ENTRA_STATUS_CHECK_RATE_LIMIT_EXCEEDED',
|
||||
message: `Entra status check rate limit exceeded, retry in ${Math.ceil(context.ttl / 1000)} seconds`,
|
||||
},
|
||||
};
|
||||
},
|
||||
skipOnError: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Entra rate limit plugin
|
||||
*/
|
||||
export function createEntraRateLimitPlugin(config?: EntraRateLimitConfig) {
|
||||
return async function (server: FastifyInstance) {
|
||||
await registerEntraRateLimit(server, config);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user