Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | import { Injectable, Logger, BadRequestException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { CodeDefinition, CodeGeneration, CodeScan, } from './schemas/code.schema'; import * as crypto from 'crypto'; @Injectable() export class CodeService { private readonly logger = new Logger(CodeService.name); constructor( @InjectModel(CodeDefinition.name) private readonly definitionModel: Model<CodeDefinition>, @InjectModel(CodeGeneration.name) private readonly generationModel: Model<CodeGeneration>, @InjectModel(CodeScan.name) private readonly scanModel: Model<CodeScan>, ) {} private getSigningSecret(): string { return ( process.env.CODE_SIGNING_SECRET || 'be-vision-qr-default-secret-change-in-production' ); } async generateCode( tenantId: string, definitionId: string, payload: string, expiresInSeconds?: number, ): Promise<{ payload: string; signature: string; qrDataUrl: string }> { const def = await this.definitionModel .findOne({ tenantId, _id: definitionId, isActive: true }) .exec(); if (!def) { throw new BadRequestException('Invalid or inactive code definition'); } const expiresAt = expiresInSeconds ? new Date(Date.now() + expiresInSeconds * 1000) : null; // Cryptographic signature for tamper verification const secret = this.getSigningSecret(); const signature = crypto .createHmac('sha256', secret) .update(`${tenantId}:${payload}:${expiresAt ? expiresAt.getTime() : ''}`) .digest('hex'); await this.generationModel.create({ tenantId, definitionId, payload, signature, expiresAt, }); // In production we would use standard qr-image or qrcode packages to build data urls. // For local VM/VPS, we export target signed payloads cleanly: const qrDataUrl = `https://api.bevision.io/v1/scan?tenantId=${tenantId}&payload=${encodeURIComponent(payload)}&sig=${signature}`; return { payload, signature, qrDataUrl, }; } async validateScan( tenantId: string, payload: string, signature: string, scannedBy?: string, ): Promise<{ isValid: boolean; status: string }> { const gen = await this.generationModel .findOne({ tenantId, payload }) .exec(); let status = 'valid'; if (!gen) { status = 'tampered'; } else if (gen.isRevoked) { status = 'revoked'; } else if (gen.expiresAt && gen.expiresAt < new Date()) { status = 'expired'; } else { // HMAC signature validation verification const secret = this.getSigningSecret(); const calculatedSig = crypto .createHmac('sha256', secret) .update( `${tenantId}:${payload}:${gen.expiresAt ? gen.expiresAt.getTime() : ''}`, ) .digest('hex'); if (calculatedSig !== signature) { status = 'tampered'; } } await this.scanModel.create({ tenantId, payload, scannedBy: scannedBy || 'system', scannedAt: new Date(), status, }); return { isValid: status === 'valid', status, }; } } |