refactor: update environment configuration and documentation
- Modified `.env.example` to reflect new API URL structure and added CDN configuration for external storage. - Updated `.gitignore` to include deployment secrets and certificate files, ensuring sensitive information is not committed. - Revised `BACKEND_INTEGRATION.md` to clarify authentication methods, replacing Cognito references with Nostr NIP-98. - Deleted outdated documentation files (`CONTENT-INTEGRATION-COMPLETE.md`, `CURSOR-MCP-SETUP.md`, `FINAL-STATUS.md`, `FIXES-APPLIED.md`, `INDEEHHUB-INTEGRATION.md`, `PROJECT-COMPLETE.md`, `PROJECT-SUMMARY.md`) to streamline project documentation. These changes enhance the clarity of the environment setup and improve the overall documentation structure for better developer onboarding.
This commit is contained in:
@@ -1,101 +1,44 @@
|
||||
ENVIRONMENT=local # local | development | production
|
||||
ENVIRONMENT=local
|
||||
|
||||
# App - Local
|
||||
# App
|
||||
PORT=4000
|
||||
DOMAIN=localhost:4000
|
||||
FRONTEND_URL=http://localhost:3000
|
||||
FRONTEND_URL=http://localhost:5174
|
||||
|
||||
# DB - API
|
||||
# PostgreSQL
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_USER=postgres
|
||||
DATABASE_PASSWORD=local
|
||||
DATABASE_NAME=indeehub
|
||||
DATABASE_USER=indeedhub
|
||||
DATABASE_PASSWORD=your_password
|
||||
DATABASE_NAME=indeedhub
|
||||
|
||||
# DB - EVENTS/ANALYTICS (POSTHOG)
|
||||
DATABASE_POSTHOG_HOST=localhost
|
||||
DATABASE_POSTHOG_PORT=5434
|
||||
DATABASE_POSTHOG_USER=postgres
|
||||
DATABASE_POSTHOG_PASSWORD=local
|
||||
DATABASE_POSTHOG_NAME=staging
|
||||
|
||||
# Trascoding Queue - Local
|
||||
# Redis (BullMQ)
|
||||
QUEUE_HOST=localhost
|
||||
QUEUE_PORT=6379
|
||||
QUEUE_PASSWORD=
|
||||
|
||||
# BTCPay Server - Bitcoin/Lightning Payments
|
||||
BTCPAY_URL=https://btcpay.yourdomain.com
|
||||
# MinIO (S3-compatible storage)
|
||||
S3_ENDPOINT=http://localhost:9000
|
||||
AWS_REGION=us-east-1
|
||||
AWS_ACCESS_KEY=minioadmin
|
||||
AWS_SECRET_KEY=minioadmin123
|
||||
S3_PRIVATE_BUCKET_NAME=indeedhub-private
|
||||
S3_PUBLIC_BUCKET_NAME=indeedhub-public
|
||||
S3_PUBLIC_BUCKET_URL=http://localhost:7777/storage/
|
||||
|
||||
# BTCPay Server (Lightning payments)
|
||||
BTCPAY_URL=https://your-btcpay.com
|
||||
BTCPAY_STORE_ID=
|
||||
BTCPAY_API_KEY=
|
||||
BTCPAY_WEBHOOK_SECRET=
|
||||
# Create a separate internal Lightning invoice with privateRouteHints.
|
||||
# Only needed when BTCPay's built-in route hints are NOT enabled.
|
||||
# If you enabled "Include hop hints" in BTCPay's Lightning settings,
|
||||
# leave this as false — BTCPay handles route hints natively.
|
||||
BTCPAY_ROUTE_HINTS=false
|
||||
|
||||
# User Pool - AWS Cognito
|
||||
COGNITO_USER_POOL_ID=
|
||||
COGNITO_CLIENT_ID=
|
||||
# Nostr auth (required)
|
||||
NOSTR_JWT_SECRET=generate_with_openssl_rand_hex_32
|
||||
NOSTR_JWT_EXPIRES_IN=7d
|
||||
|
||||
# Sendgrid - Email Service
|
||||
SENDGRID_API_KEY=
|
||||
SENDGRID_SENDER=
|
||||
SENDGRID_WAITLIST=
|
||||
# Content encryption (required for transcoding)
|
||||
AES_MASTER_SECRET=generate_64_hex_chars
|
||||
|
||||
# AWS KEYS
|
||||
AWS_ACCESS_KEY=
|
||||
AWS_SECRET_KEY=
|
||||
AWS_REGION=
|
||||
|
||||
# AWS S3
|
||||
S3_PUBLIC_BUCKET_URL=
|
||||
S3_PUBLIC_BUCKET_NAME=
|
||||
|
||||
# AWS CloudFront
|
||||
CLOUDFRONT_PRIVATE_KEY=
|
||||
CLOUDFRONT_KEY_PAIR_ID=
|
||||
|
||||
# Note: Stripe has been removed. All payments are now via BTCPay Server (Lightning).
|
||||
|
||||
# PAY WITH FLASH - ENTHUSIAST SUBSCRIPTION
|
||||
FLASH_JWT_SECRET_ENTHUSIAST=
|
||||
|
||||
# PAY WITH FLASH - FILM BUFF SUBSCRIPTION
|
||||
FLASH_JWT_SECRET_FILM_BUFF=
|
||||
|
||||
# PAY WITH FLASH - CINEPHILE SUBSCRIPTION
|
||||
FLASH_JWT_SECRET_CINEPHILE=
|
||||
|
||||
# PAY WITH FLASH - RSS ADDON SUBSCRIPTION
|
||||
FLASH_JWT_SECRET_RSS_ADDON=
|
||||
|
||||
# PAY WITH FLASH - VERIFICATION ADDON SUBSCRIPTION
|
||||
FLASH_JWT_SECRET_VERIFICATION_ADDON=
|
||||
|
||||
# Transcoding API
|
||||
TRANSCODING_API_KEY=
|
||||
TRANSCODING_API_URL=http://localhost:4001
|
||||
|
||||
# Podping - RSS Update Notifications
|
||||
PODPING_URL=https://podping.cloud/
|
||||
PODPING_KEY=
|
||||
PODPING_USER_AGENT=
|
||||
|
||||
# Retool API KEY - Admin Dashboard
|
||||
# Admin API (optional)
|
||||
ADMIN_API_KEY=
|
||||
|
||||
# PostHog - Analytics
|
||||
POSTHOG_API_KEY=
|
||||
|
||||
# Sentry - Error Tracking
|
||||
SENTRY_ENVIRONMENT=
|
||||
|
||||
# BUYDRM
|
||||
PRIVATE_AUTH_CERTIFICATE_KEY_ID =
|
||||
DRM_SECRET_NAME =
|
||||
|
||||
# Project Review
|
||||
DASHBOARD_REVIEW_URL = https://oneseventech.retool.com/apps/5e86de84-7cdb-11ef-998f-47951c6124a1/Testing%20IndeeHub/film-details?id=
|
||||
PROJECT_REVIEW_RECIPIENT_EMAIL = <your email>
|
||||
|
||||
10
backend/package-lock.json
generated
10
backend/package-lock.json
generated
@@ -49,6 +49,7 @@
|
||||
"class-validator": "^0.14.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"fast-xml-parser": "^5.2.0",
|
||||
"helmet": "^8.1.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"jwks-rsa": "^3.0.1",
|
||||
"moment": "^2.30.1",
|
||||
@@ -14299,6 +14300,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/helmet": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
|
||||
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/homedir-polyfill": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
"class-validator": "^0.14.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"fast-xml-parser": "^5.2.0",
|
||||
"helmet": "^8.1.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"jwks-rsa": "^3.0.1",
|
||||
"moment": "^2.30.1",
|
||||
|
||||
@@ -5,35 +5,62 @@ import {
|
||||
Logger,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { CognitoJwtVerifier } from 'aws-jwt-verify';
|
||||
import { verify } from 'jsonwebtoken';
|
||||
import { Socket } from 'socket.io';
|
||||
|
||||
interface NostrSessionPayload {
|
||||
sub: string;
|
||||
typ: 'nostr-session' | 'nostr-refresh';
|
||||
uid?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket JWT auth guard.
|
||||
* Validates Nostr session JWTs (signed with NOSTR_JWT_SECRET).
|
||||
* Replaces legacy Cognito-based validation.
|
||||
*/
|
||||
@Injectable()
|
||||
export class WsJwtAuthGuard implements CanActivate {
|
||||
static async validateToken(client: Socket) {
|
||||
const authorization = this.extractTokenFromHeader(client.handshake);
|
||||
if (!authorization) throw new UnauthorizedException();
|
||||
const verifier = CognitoJwtVerifier.create({
|
||||
userPoolId: process.env.COGNITO_USER_POOL_ID,
|
||||
tokenUse: 'id',
|
||||
clientId: process.env.COGNITO_CLIENT_ID,
|
||||
});
|
||||
|
||||
await verifier.verify(authorization);
|
||||
return true;
|
||||
}
|
||||
private static extractTokenFromHeader(request: any): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
async canActivate(context: ExecutionContext) {
|
||||
|
||||
static async validateToken(client: Socket): Promise<boolean> {
|
||||
const token = WsJwtAuthGuard.extractTokenFromHeader(client.handshake);
|
||||
if (!token) throw new UnauthorizedException('No token provided');
|
||||
|
||||
const secret = process.env.NOSTR_JWT_SECRET;
|
||||
if (!secret) {
|
||||
Logger.error('NOSTR_JWT_SECRET not configured', 'WsJwtAuthGuard');
|
||||
throw new UnauthorizedException('Server misconfiguration');
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = verify(token, secret) as NostrSessionPayload;
|
||||
if (payload.typ !== 'nostr-session') {
|
||||
throw new UnauthorizedException('Invalid token type');
|
||||
}
|
||||
(client as Socket & { data?: { pubkey?: string } }).data = {
|
||||
pubkey: payload.sub,
|
||||
};
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof UnauthorizedException) throw error;
|
||||
Logger.warn(`WebSocket token validation failed: ${error?.message}`);
|
||||
throw new UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
if (context.getType() !== 'ws') return true;
|
||||
const client: Socket = context.switchToWs().getClient();
|
||||
try {
|
||||
await WsJwtAuthGuard.validateToken(client);
|
||||
return true;
|
||||
} catch (error) {
|
||||
Logger.error(`Error validating token: ${error.message}`);
|
||||
if (error instanceof UnauthorizedException) throw error;
|
||||
Logger.warn(`WebSocket auth failed: ${error?.message}`);
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,24 +38,41 @@ interface TranscodeJobData {
|
||||
drmMediaId?: string;
|
||||
}
|
||||
|
||||
const isProduction = process.env.ENVIRONMENT === 'production';
|
||||
|
||||
function requireEnv(name: string): string {
|
||||
const val = process.env[name];
|
||||
if (isProduction && !val) {
|
||||
throw new Error(`FFmpeg worker: ${name} is required in production`);
|
||||
}
|
||||
return val || '';
|
||||
}
|
||||
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION || 'us-east-1',
|
||||
endpoint: process.env.S3_ENDPOINT || 'http://minio:9000',
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY || 'minioadmin',
|
||||
secretAccessKey: process.env.AWS_SECRET_KEY || 'minioadmin123',
|
||||
accessKeyId: isProduction
|
||||
? requireEnv('AWS_ACCESS_KEY')
|
||||
: process.env.AWS_ACCESS_KEY || 'minioadmin',
|
||||
secretAccessKey: isProduction
|
||||
? requireEnv('AWS_SECRET_KEY')
|
||||
: process.env.AWS_SECRET_KEY || 'minioadmin123',
|
||||
},
|
||||
});
|
||||
|
||||
const privateBucket = process.env.S3_PRIVATE_BUCKET_NAME || 'indeedhub-private';
|
||||
const privateBucket =
|
||||
process.env.S3_PRIVATE_BUCKET_NAME || 'indeedhub-private';
|
||||
|
||||
function getPgClient(): Client {
|
||||
return new Client({
|
||||
host: process.env.DATABASE_HOST || 'postgres',
|
||||
port: Number(process.env.DATABASE_PORT || '5432'),
|
||||
user: process.env.DATABASE_USER || 'indeedhub',
|
||||
password: process.env.DATABASE_PASSWORD || 'indeedhub_dev_2026',
|
||||
password: isProduction
|
||||
? requireEnv('DATABASE_PASSWORD')
|
||||
: process.env.DATABASE_PASSWORD || 'indeedhub_dev_2026',
|
||||
database: process.env.DATABASE_NAME || 'indeedhub',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ExpressAdapter,
|
||||
NestExpressApplication,
|
||||
} from '@nestjs/platform-express';
|
||||
import helmet from 'helmet';
|
||||
import { RawBodyRequest } from './types/raw-body-request';
|
||||
import { validateEnvironment } from './common/validate-env';
|
||||
|
||||
@@ -49,6 +50,8 @@ async function bootstrap() {
|
||||
|
||||
useContainer(app.select(AppModule), { fallbackOnErrors: true });
|
||||
|
||||
app.use(helmet());
|
||||
|
||||
if (
|
||||
process.env.ENVIRONMENT === 'development' ||
|
||||
process.env.ENVIRONMENT === 'local'
|
||||
@@ -73,6 +76,7 @@ async function bootstrap() {
|
||||
'https://www.indeehub.studio',
|
||||
'https://app.indeehub.studio',
|
||||
'https://bff.indeehub.studio',
|
||||
'https://archipelago.indeehub.studio',
|
||||
];
|
||||
|
||||
if (process.env.FRONTEND_URL) {
|
||||
|
||||
@@ -17,11 +17,17 @@
|
||||
import { Client } from 'pg';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const isProduction = process.env.ENVIRONMENT === 'production';
|
||||
const dbPassword = process.env.DATABASE_PASSWORD;
|
||||
if (isProduction && !dbPassword) {
|
||||
throw new Error('DATABASE_PASSWORD is required when ENVIRONMENT=production');
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
host: process.env.DATABASE_HOST || 'localhost',
|
||||
port: Number(process.env.DATABASE_PORT || '5432'),
|
||||
user: process.env.DATABASE_USER || 'indeedhub',
|
||||
password: process.env.DATABASE_PASSWORD || 'indeedhub_dev_2026',
|
||||
password: dbPassword || 'indeedhub_dev_2026',
|
||||
database: process.env.DATABASE_NAME || 'indeedhub',
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user