feat: implement billing, usage tracking, and administrative geocoding features in API with infrastructure updates

This commit is contained in:
Hamza-Ayed
2026-07-28 04:43:23 +03:00
parent 2f7c2066f7
commit dc9d546733
133 changed files with 3599 additions and 356 deletions
+8 -4
View File
@@ -8,6 +8,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthModule = void 0;
const common_1 = require("@nestjs/common");
@@ -17,6 +18,9 @@ const tenant_entity_1 = require("./entities/tenant.entity");
const api_key_entity_1 = require("./entities/api-key.entity");
const redis_module_1 = require("../common/redis.module");
const config_1 = require("@nestjs/config");
const tenant_controller_1 = require("./tenant.controller");
const firebase_admin_service_1 = require("./firebase-admin.service");
const firebase_auth_guard_1 = require("./guards/firebase-auth.guard");
let AuthModule = class AuthModule {
authService;
configService;
@@ -40,10 +44,10 @@ exports.AuthModule = AuthModule = __decorate([
typeorm_1.TypeOrmModule.forFeature([tenant_entity_1.Tenant, api_key_entity_1.ApiKey]),
redis_module_1.RedisModule,
],
providers: [auth_service_1.AuthService],
exports: [auth_service_1.AuthService],
controllers: [tenant_controller_1.TenantController],
providers: [auth_service_1.AuthService, firebase_admin_service_1.FirebaseAdminService, firebase_auth_guard_1.FirebaseAuthGuard],
exports: [auth_service_1.AuthService, firebase_admin_service_1.FirebaseAdminService, firebase_auth_guard_1.FirebaseAuthGuard],
}),
__metadata("design:paramtypes", [auth_service_1.AuthService,
config_1.ConfigService])
__metadata("design:paramtypes", [auth_service_1.AuthService, typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], AuthModule);
//# sourceMappingURL=auth.module.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA8D;AAC9D,6CAAgD;AAChD,iDAA6C;AAC7C,4DAAkD;AAClD,8DAAmD;AACnD,yDAAqD;AACrD,2CAA+C;AAWxC,IAAM,UAAU,GAAhB,MAAM,UAAU;IAEF;IACA;IAFnB,YACmB,WAAwB,EACxB,aAA4B;QAD5B,gBAAW,GAAX,WAAW,CAAa;QACxB,kBAAa,GAAb,aAAa,CAAe;IAC5C,CAAC;IAMJ,KAAK,CAAC,YAAY;QAChB,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,aAAa,CAAC,CAAC;QACjE,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,CAAC,CAAC;YACzF,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;CACF,CAAA;AAjBY,gCAAU;qBAAV,UAAU;IATtB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,sBAAM,EAAE,uBAAM,CAAC,CAAC;YAC1C,0BAAW;SACZ;QACD,SAAS,EAAE,CAAC,0BAAW,CAAC;QACxB,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;qCAGgC,0BAAW;QACT,sBAAa;GAHpC,UAAU,CAiBtB"}
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAA8D;AAC9D,6CAAgD;AAChD,iDAA6C;AAC7C,4DAAkD;AAClD,8DAAmD;AACnD,yDAAqD;AACrD,2CAA+C;AAC/C,2DAAuD;AACvD,qEAAgE;AAChE,sEAAiE;AAY1D,IAAM,UAAU,GAAhB,MAAM,UAAU;IAEF;IACA;IAFnB,YACmB,WAAwB,EACxB,aAA4B;QAD5B,gBAAW,GAAX,WAAW,CAAa;QACxB,kBAAa,GAAb,aAAa,CAAe;IAC5C,CAAC;IAMJ,KAAK,CAAC,YAAY;QAChB,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,aAAa,CAAC,CAAC;QACjE,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,CAAC,CAAC;YACzF,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;CACF,CAAA;AAjBY,gCAAU;qBAAV,UAAU;IAVtB,IAAA,eAAM,GAAE;IACR,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,uBAAa,CAAC,UAAU,CAAC,CAAC,sBAAM,EAAE,uBAAM,CAAC,CAAC;YAC1C,0BAAW;SACZ;QACD,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,0BAAW,EAAE,6CAAoB,EAAE,uCAAiB,CAAC;QACjE,OAAO,EAAE,CAAC,0BAAW,EAAE,6CAAoB,EAAE,uCAAiB,CAAC;KAChE,CAAC;qCAGgC,0BAAW,sBACT,sBAAa,oBAAb,sBAAa;GAHpC,UAAU,CAiBtB"}
+4
View File
@@ -17,4 +17,8 @@ export declare class AuthService {
private isDomainAllowed;
hashSecret(secret: string): string;
seedDefaultKey(name: string, email: string, keyString: string): Promise<void>;
getApiKeys(tenantId: string): Promise<ApiKey[]>;
createApiKey(tenantId: string, name: string, rateLimit?: number, allowedOrigins?: string[]): Promise<ApiKey>;
getDefaultTenant(): Promise<Tenant>;
findOrCreateByFirebaseUid(uid: string, email: string, name: string, photoUrl?: string): Promise<Tenant>;
}
+70 -5
View File
@@ -12,6 +12,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var AuthService_1;
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthService = void 0;
const common_1 = require("@nestjs/common");
@@ -49,7 +50,7 @@ let AuthService = AuthService_1 = class AuthService {
const result = {
tenant: apiKey.tenant,
apiKey: apiKey,
rateLimit: apiKey.rateLimit || 100,
rateLimit: apiKey.rateLimit || tenant_entity_1.RATE_LIMITS[apiKey.tenant.plan] || 100,
};
await this.redisService.set(cacheKey, result, 3600);
this.apiKeyRepository.update(apiKey.id, { lastUsedAt: new Date() }).catch(err => this.logger.error(`Failed to update lastUsedAt for API key ${apiKey.id}: ${err.message}`));
@@ -87,7 +88,8 @@ let AuthService = AuthService_1 = class AuthService {
tenant = await this.tenantRepository.save({
name,
email,
isActive: true
isActive: true,
plan: tenant_entity_1.TenantPlan.ENTERPRISE,
});
}
const existingKey = await this.apiKeyRepository.findOne({ where: { key: keyString } });
@@ -102,14 +104,77 @@ let AuthService = AuthService_1 = class AuthService {
});
}
}
async getApiKeys(tenantId) {
return this.apiKeyRepository.find({
where: { tenantId },
order: { createdAt: 'DESC' }
});
}
async createApiKey(tenantId, name, rateLimit, allowedOrigins) {
const existingKeysCount = await this.apiKeyRepository.count({ where: { tenantId } });
if (existingKeysCount >= 1) {
throw new common_1.ConflictException('Limit reached: Only 1 API key allowed per developer currently.');
}
const key = `in_${(0, crypto_1.createHash)('md5').update(Math.random().toString()).digest('hex').substring(0, 24)}`;
const apiKey = this.apiKeyRepository.create({
key,
secretHash: this.hashSecret(key),
name,
tenantId,
rateLimit: rateLimit || 100,
allowedOrigins: allowedOrigins || [],
isActive: true
});
return this.apiKeyRepository.save(apiKey);
}
async getDefaultTenant() {
const tenant = await this.tenantRepository.findOne({ where: {} });
if (!tenant)
throw new common_1.NotFoundException('No tenants found in system');
return tenant;
}
async findOrCreateByFirebaseUid(uid, email, name, photoUrl) {
let tenant = await this.tenantRepository.findOne({ where: { firebaseUid: uid } });
if (!tenant) {
tenant = await this.tenantRepository.findOne({ where: { email } });
if (tenant) {
this.logger.log(`Linking existing tenant ${email} to Firebase UID: ${uid}`);
tenant.firebaseUid = uid;
if (photoUrl)
tenant.photoUrl = photoUrl;
tenant = await this.tenantRepository.save(tenant);
}
else {
this.logger.log(`Creating new tenant for Firebase user: ${email} (${uid})`);
const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com' || email === 'hamzaayedpython@gmail.com';
tenant = await this.tenantRepository.save({
firebaseUid: uid,
email,
name,
photoUrl,
plan: isAdmin ? tenant_entity_1.TenantPlan.ENTERPRISE : tenant_entity_1.TenantPlan.FREE,
isActive: true,
});
}
}
else {
const isAdmin = email === 'hamzaaleghwairyeen@gmail.com' || email === 'hamzadoctor@gmail.com' || email === 'hamzaayedpython@gmail.com';
if (isAdmin && tenant.plan !== tenant_entity_1.TenantPlan.ENTERPRISE) {
tenant.plan = tenant_entity_1.TenantPlan.ENTERPRISE;
await this.tenantRepository.save(tenant);
}
if (tenant.photoUrl !== photoUrl || tenant.name !== name) {
await this.tenantRepository.update(tenant.id, { photoUrl, name });
}
}
return tenant;
}
};
exports.AuthService = AuthService;
exports.AuthService = AuthService = AuthService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(api_key_entity_1.ApiKey)),
__param(1, (0, typeorm_1.InjectRepository)(tenant_entity_1.Tenant)),
__metadata("design:paramtypes", [typeorm_2.Repository,
typeorm_2.Repository,
redis_service_1.RedisService])
__metadata("design:paramtypes", [typeof (_a = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _a : Object, typeof (_b = typeof typeorm_2.Repository !== "undefined" && typeorm_2.Repository) === "function" ? _b : Object, redis_service_1.RedisService])
], AuthService);
//# sourceMappingURL=auth.service.js.map
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
export declare class CreateKeyDto {
name: string;
rateLimit?: number;
allowedOrigins?: string[];
}
+35
View File
@@ -0,0 +1,35 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateKeyDto = void 0;
const class_validator_1 = require("class-validator");
class CreateKeyDto {
name;
rateLimit;
allowedOrigins;
}
exports.CreateKeyDto = CreateKeyDto;
__decorate([
(0, class_validator_1.IsString)(),
__metadata("design:type", String)
], CreateKeyDto.prototype, "name", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsNumber)(),
__metadata("design:type", Number)
], CreateKeyDto.prototype, "rateLimit", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsArray)(),
(0, class_validator_1.IsString)({ each: true }),
__metadata("design:type", Array)
], CreateKeyDto.prototype, "allowedOrigins", void 0);
//# sourceMappingURL=create-key.dto.js.map
@@ -0,0 +1 @@
{"version":3,"file":"create-key.dto.js","sourceRoot":"","sources":["../../../../src/auth/dto/management/create-key.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA0E;AAE1E,MAAa,YAAY;IAEvB,IAAI,CAAS;IAIb,SAAS,CAAU;IAKnB,cAAc,CAAY;CAC3B;AAZD,oCAYC;AAVC;IADC,IAAA,0BAAQ,GAAE;;0CACE;AAIb;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;+CACQ;AAKnB;IAHC,IAAA,4BAAU,GAAE;IACZ,IAAA,yBAAO,GAAE;IACT,IAAA,0BAAQ,EAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;;oDACC"}
+11 -1
View File
@@ -1,14 +1,24 @@
import { ApiKey } from './api-key.entity';
export declare enum TenantPlan {
FREE = "FREE",
PREMIUM = "PREMIUM",
STARTER = "STARTER",
PRO = "PRO",
ENTERPRISE = "ENTERPRISE"
}
export declare enum TenantRole {
USER = "USER",
ADMIN = "ADMIN"
}
export declare const QUOTA_LIMITS: Record<TenantPlan, number>;
export declare const RATE_LIMITS: Record<TenantPlan, number>;
export declare class Tenant {
id: string;
name: string;
email: string;
plan: TenantPlan;
firebaseUid: string;
role: TenantRole;
photoUrl: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
+39 -2
View File
@@ -9,20 +9,41 @@ var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tenant = exports.TenantPlan = void 0;
exports.Tenant = exports.RATE_LIMITS = exports.QUOTA_LIMITS = exports.TenantRole = exports.TenantPlan = void 0;
const typeorm_1 = require("typeorm");
const api_key_entity_1 = require("./api-key.entity");
var TenantPlan;
(function (TenantPlan) {
TenantPlan["FREE"] = "FREE";
TenantPlan["PREMIUM"] = "PREMIUM";
TenantPlan["STARTER"] = "STARTER";
TenantPlan["PRO"] = "PRO";
TenantPlan["ENTERPRISE"] = "ENTERPRISE";
})(TenantPlan || (exports.TenantPlan = TenantPlan = {}));
var TenantRole;
(function (TenantRole) {
TenantRole["USER"] = "USER";
TenantRole["ADMIN"] = "ADMIN";
})(TenantRole || (exports.TenantRole = TenantRole = {}));
exports.QUOTA_LIMITS = {
[TenantPlan.FREE]: 8000,
[TenantPlan.STARTER]: 25000,
[TenantPlan.PRO]: 100000,
[TenantPlan.ENTERPRISE]: 500000,
};
exports.RATE_LIMITS = {
[TenantPlan.FREE]: 5,
[TenantPlan.STARTER]: 100,
[TenantPlan.PRO]: 500,
[TenantPlan.ENTERPRISE]: 50000,
};
let Tenant = class Tenant {
id;
name;
email;
plan;
firebaseUid;
role;
photoUrl;
isActive;
createdAt;
updatedAt;
@@ -49,6 +70,22 @@ __decorate([
}),
__metadata("design:type", String)
], Tenant.prototype, "plan", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true, unique: true }),
__metadata("design:type", String)
], Tenant.prototype, "firebaseUid", void 0);
__decorate([
(0, typeorm_1.Column)({
type: 'enum',
enum: TenantRole,
default: TenantRole.USER,
}),
__metadata("design:type", String)
], Tenant.prototype, "role", void 0);
__decorate([
(0, typeorm_1.Column)({ nullable: true }),
__metadata("design:type", String)
], Tenant.prototype, "photoUrl", void 0);
__decorate([
(0, typeorm_1.Column)({ default: true }),
__metadata("design:type", Boolean)
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"tenant.entity.js","sourceRoot":"","sources":["../../../src/auth/entities/tenant.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAgH;AAChH,qDAA0C;AAE1C,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,iCAAmB,CAAA;IACnB,uCAAyB,CAAA;AAC3B,CAAC,EAJW,UAAU,0BAAV,UAAU,QAIrB;AAGM,IAAM,MAAM,GAAZ,MAAM,MAAM;IAEjB,EAAE,CAAS;IAGX,IAAI,CAAS;IAGb,KAAK,CAAS;IAOd,IAAI,CAAa;IAGjB,QAAQ,CAAU;IAGlB,SAAS,CAAO;IAGhB,SAAS,CAAO;IAGhB,OAAO,CAAW;CACnB,CAAA;AA5BY,wBAAM;AAEjB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;kCACpB;AAGX;IADC,IAAA,gBAAM,GAAE;;oCACI;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;qCACX;AAOd;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,UAAU,CAAC,IAAI;KACzB,CAAC;;oCACe;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCACR;AAGlB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,uBAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;;uCACjC;iBA3BP,MAAM;IADlB,IAAA,gBAAM,EAAC,SAAS,CAAC;GACL,MAAM,CA4BlB"}
{"version":3,"file":"tenant.entity.js","sourceRoot":"","sources":["../../../src/auth/entities/tenant.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAgH;AAChH,qDAA0C;AAE1C,IAAY,UAKX;AALD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,iCAAmB,CAAA;IACnB,yBAAW,CAAA;IACX,uCAAyB,CAAA;AAC3B,CAAC,EALW,UAAU,0BAAV,UAAU,QAKrB;AAED,IAAY,UAGX;AAHD,WAAY,UAAU;IACpB,2BAAa,CAAA;IACb,6BAAe,CAAA;AACjB,CAAC,EAHW,UAAU,0BAAV,UAAU,QAGrB;AAEY,QAAA,YAAY,GAA+B;IACtD,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI;IACvB,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK;IAC3B,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM;IACxB,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM;CAChC,CAAC;AAEW,QAAA,WAAW,GAA+B;IACrD,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IACpB,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG;IACzB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG;IACrB,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,KAAK;CAC/B,CAAC;AAGK,IAAM,MAAM,GAAZ,MAAM,MAAM;IAEjB,EAAE,CAAS;IAGX,IAAI,CAAS;IAGb,KAAK,CAAS;IAOd,IAAI,CAAa;IAGjB,WAAW,CAAS;IAOpB,IAAI,CAAa;IAGjB,QAAQ,CAAS;IAGjB,QAAQ,CAAU;IAGlB,SAAS,CAAO;IAGhB,SAAS,CAAO;IAGhB,OAAO,CAAW;CACnB,CAAA;AAzCY,wBAAM;AAEjB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;kCACpB;AAGX;IADC,IAAA,gBAAM,GAAE;;oCACI;AAGb;IADC,IAAA,gBAAM,EAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;qCACX;AAOd;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,UAAU,CAAC,IAAI;KACzB,CAAC;;oCACe;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;2CACrB;AAOpB;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,UAAU,CAAC,IAAI;KACzB,CAAC;;oCACe;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;wCACV;AAGjB;IADC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCACR;AAGlB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,0BAAgB,GAAE;8BACR,IAAI;yCAAC;AAGhB;IADC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,uBAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;;uCACjC;iBAxCP,MAAM;IADlB,IAAA,gBAAM,EAAC,SAAS,CAAC;GACL,MAAM,CAyClB"}
+11
View File
@@ -0,0 +1,11 @@
import { OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as admin from 'firebase-admin';
export declare class FirebaseAdminService implements OnModuleInit {
private configService;
private readonly logger;
private firebaseApp;
constructor(configService: ConfigService);
onModuleInit(): void;
verifyIdToken(token: string): Promise<admin.auth.DecodedIdToken>;
}
+99
View File
@@ -0,0 +1,99 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var FirebaseAdminService_1;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FirebaseAdminService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const admin = __importStar(require("firebase-admin"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
let FirebaseAdminService = FirebaseAdminService_1 = class FirebaseAdminService {
configService;
logger = new common_1.Logger(FirebaseAdminService_1.name);
firebaseApp;
constructor(configService) {
this.configService = configService;
}
onModuleInit() {
const keyPath = this.configService.get('FIREBASE_SERVICE_ACCOUNT_PATH');
if (!keyPath) {
this.logger.error('FIREBASE_SERVICE_ACCOUNT_PATH is not defined in environment variables');
return;
}
try {
const absolutePath = path.isAbsolute(keyPath)
? keyPath
: path.resolve(process.cwd(), keyPath);
if (!fs.existsSync(absolutePath)) {
this.logger.error(`Firebase service account key not found at: ${absolutePath}`);
return;
}
const serviceAccount = JSON.parse(fs.readFileSync(absolutePath, 'utf8'));
this.firebaseApp = admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
this.logger.log('✅ Firebase Admin SDK initialized successfully');
}
catch (error) {
this.logger.error(`Failed to initialize Firebase Admin SDK: ${error.message}`);
}
}
async verifyIdToken(token) {
try {
return await admin.auth().verifyIdToken(token);
}
catch (error) {
this.logger.error(`Token verification failed: ${error.message}`);
throw error;
}
}
};
exports.FirebaseAdminService = FirebaseAdminService;
exports.FirebaseAdminService = FirebaseAdminService = FirebaseAdminService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeof (_a = typeof config_1.ConfigService !== "undefined" && config_1.ConfigService) === "function" ? _a : Object])
], FirebaseAdminService);
//# sourceMappingURL=firebase-admin.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"firebase-admin.service.js","sourceRoot":"","sources":["../../src/auth/firebase-admin.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAkE;AAClE,2CAA+C;AAC/C,sDAAwC;AACxC,uCAAyB;AACzB,2CAA6B;AAGtB,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAIX;IAHH,MAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IACxD,WAAW,CAAgB;IAEnC,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAEpD,YAAY;QACV,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAS,+BAA+B,CAAC,CAAC;QAEhF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC;YAC3F,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YAEH,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAC3C,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;YAEzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,YAAY,EAAE,CAAC,CAAC;gBAChF,OAAO;YACT,CAAC;YAED,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;YAEzE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,aAAa,CAAC;gBACrC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC;aAClD,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA4C,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAAa;QAC/B,IAAI,CAAC;YACH,OAAO,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACjE,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF,CAAA;AA7CY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;yDAKwB,sBAAa,oBAAb,sBAAa;GAJrC,oBAAoB,CA6ChC"}
+10
View File
@@ -0,0 +1,10 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { FirebaseAdminService } from '../firebase-admin.service';
import { AuthService } from '../auth.service';
export declare class FirebaseAuthGuard implements CanActivate {
private firebaseAdminService;
private authService;
private readonly logger;
constructor(firebaseAdminService: FirebaseAdminService, authService: AuthService);
canActivate(context: ExecutionContext): Promise<boolean>;
}
+54
View File
@@ -0,0 +1,54 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var FirebaseAuthGuard_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FirebaseAuthGuard = void 0;
const common_1 = require("@nestjs/common");
const firebase_admin_service_1 = require("../firebase-admin.service");
const auth_service_1 = require("../auth.service");
let FirebaseAuthGuard = FirebaseAuthGuard_1 = class FirebaseAuthGuard {
firebaseAdminService;
authService;
logger = new common_1.Logger(FirebaseAuthGuard_1.name);
constructor(firebaseAdminService, authService) {
this.firebaseAdminService = firebaseAdminService;
this.authService = authService;
}
async canActivate(context) {
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new common_1.UnauthorizedException('Missing or invalid Authorization header');
}
const token = authHeader.split('Bearer ')[1];
try {
const decodedToken = await this.firebaseAdminService.verifyIdToken(token);
const tenant = await this.authService.findOrCreateByFirebaseUid(decodedToken.uid, decodedToken.email || '', decodedToken.name || decodedToken.email?.split('@')[0] || 'Unknown User', decodedToken.picture);
if (!tenant.isActive) {
throw new common_1.UnauthorizedException('Tenant account is disabled');
}
request['tenant'] = tenant;
request['user'] = decodedToken;
return true;
}
catch (error) {
this.logger.error(`Firebase Auth failed: ${error.message}`);
throw new common_1.UnauthorizedException(error.message || 'Authentication failed');
}
}
};
exports.FirebaseAuthGuard = FirebaseAuthGuard;
exports.FirebaseAuthGuard = FirebaseAuthGuard = FirebaseAuthGuard_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [firebase_admin_service_1.FirebaseAdminService,
auth_service_1.AuthService])
], FirebaseAuthGuard);
//# sourceMappingURL=firebase-auth.guard.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"firebase-auth.guard.js","sourceRoot":"","sources":["../../../src/auth/guards/firebase-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAMwB;AACxB,sEAAiE;AACjE,kDAA8C;AAGvC,IAAM,iBAAiB,yBAAvB,MAAM,iBAAiB;IAIlB;IACA;IAJO,MAAM,GAAG,IAAI,eAAM,CAAC,mBAAiB,CAAC,IAAI,CAAC,CAAC;IAE7D,YACU,oBAA0C,EAC1C,WAAwB;QADxB,yBAAoB,GAApB,oBAAoB,CAAsB;QAC1C,gBAAW,GAAX,WAAW,CAAa;IAC/B,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACpD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAEjD,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,8BAAqB,CAAC,yCAAyC,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7C,IAAI,CAAC;YAEH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YAG1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,yBAAyB,CAC7D,YAAY,CAAC,GAAG,EAChB,YAAY,CAAC,KAAK,IAAI,EAAE,EACxB,YAAY,CAAC,IAAI,IAAI,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,cAAc,EACxE,YAAY,CAAC,OAAO,CACrB,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACrB,MAAM,IAAI,8BAAqB,CAAC,4BAA4B,CAAC,CAAC;YAChE,CAAC;YAGD,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC;YAE/B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,MAAM,IAAI,8BAAqB,CAAC,KAAK,CAAC,OAAO,IAAI,uBAAuB,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;CACF,CAAA;AA5CY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;qCAKqB,6CAAoB;QAC7B,0BAAW;GALvB,iBAAiB,CA4C7B"}
+9
View File
@@ -0,0 +1,9 @@
import { AuthService } from './auth.service';
import { CreateKeyDto } from './dto/management/create-key.dto';
export declare class TenantController {
private readonly authService;
constructor(authService: AuthService);
createKey(req: any, dto: CreateKeyDto): Promise<import("./entities/api-key.entity").ApiKey>;
getKeys(req: any): Promise<import("./entities/api-key.entity").ApiKey[]>;
getMe(req: any): Promise<any>;
}
+74
View File
@@ -0,0 +1,74 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TenantController = void 0;
const common_1 = require("@nestjs/common");
const auth_service_1 = require("./auth.service");
const create_key_dto_1 = require("./dto/management/create-key.dto");
const swagger_1 = require("@nestjs/swagger");
const firebase_auth_guard_1 = require("./guards/firebase-auth.guard");
let TenantController = class TenantController {
authService;
constructor(authService) {
this.authService = authService;
}
async createKey(req, dto) {
if (req.user && req.user.email_verified === false) {
throw new common_1.ForbiddenException('You must verify your email address before creating an API key.');
}
const tenantId = req.tenant.id;
return this.authService.createApiKey(tenantId, dto.name, dto.rateLimit, dto.allowedOrigins);
}
async getKeys(req) {
const tenantId = req.tenant.id;
return this.authService.getApiKeys(tenantId);
}
async getMe(req) {
return req.tenant;
}
};
exports.TenantController = TenantController;
__decorate([
(0, common_1.Post)('keys'),
(0, swagger_1.ApiOperation)({ summary: 'Create a new API key' }),
__param(0, (0, common_1.Req)()),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, create_key_dto_1.CreateKeyDto]),
__metadata("design:returntype", Promise)
], TenantController.prototype, "createKey", null);
__decorate([
(0, common_1.Get)('keys'),
(0, swagger_1.ApiOperation)({ summary: 'Get all API keys for the authenticated tenant' }),
__param(0, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], TenantController.prototype, "getKeys", null);
__decorate([
(0, common_1.Get)('me'),
(0, swagger_1.ApiOperation)({ summary: 'Get current authenticated tenant info' }),
__param(0, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], TenantController.prototype, "getMe", null);
exports.TenantController = TenantController = __decorate([
(0, swagger_1.ApiTags)('auth'),
(0, swagger_1.ApiBearerAuth)(),
(0, common_1.UseGuards)(firebase_auth_guard_1.FirebaseAuthGuard),
(0, common_1.Controller)('auth/management'),
__metadata("design:paramtypes", [auth_service_1.AuthService])
], TenantController);
//# sourceMappingURL=tenant.controller.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"tenant.controller.js","sourceRoot":"","sources":["../../src/auth/tenant.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAwG;AACxG,iDAA6C;AAC7C,oEAA+D;AAC/D,6CAAuE;AACvE,sEAAiE;AAM1D,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IACE;IAA7B,YAA6B,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAInD,AAAN,KAAK,CAAC,SAAS,CACN,GAAQ,EACP,GAAiB;QAEzB,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;YAClD,MAAM,IAAI,2BAAkB,CAAC,gEAAgE,CAAC,CAAC;QACjG,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,YAAY,CAClC,QAAQ,EACR,GAAG,CAAC,IAAI,EACR,GAAG,CAAC,SAAS,EACb,GAAG,CAAC,cAAc,CACnB,CAAC;IACJ,CAAC;IAIK,AAAN,KAAK,CAAC,OAAO,CAAQ,GAAQ;QAC3B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAIK,AAAN,KAAK,CAAC,KAAK,CAAQ,GAAQ;QACzB,OAAO,GAAG,CAAC,MAAM,CAAC;IACpB,CAAC;CACF,CAAA;AAlCY,4CAAgB;AAKrB;IAFL,IAAA,aAAI,EAAC,MAAM,CAAC;IACZ,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAE/C,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,6BAAY;;iDAa1B;AAIK;IAFL,IAAA,YAAG,EAAC,MAAM,CAAC;IACX,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,+CAA+C,EAAE,CAAC;IAC5D,WAAA,IAAA,YAAG,GAAE,CAAA;;;;+CAGnB;AAIK;IAFL,IAAA,YAAG,EAAC,IAAI,CAAC;IACT,IAAA,sBAAY,EAAC,EAAE,OAAO,EAAE,uCAAuC,EAAE,CAAC;IACtD,WAAA,IAAA,YAAG,GAAE,CAAA;;;;6CAEjB;2BAjCU,gBAAgB;IAJ5B,IAAA,iBAAO,EAAC,MAAM,CAAC;IACf,IAAA,uBAAa,GAAE;IACf,IAAA,kBAAS,EAAC,uCAAiB,CAAC;IAC5B,IAAA,mBAAU,EAAC,iBAAiB,CAAC;qCAEc,0BAAW;GAD1C,gBAAgB,CAkC5B"}