mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
feat(eims): add invoice mapper and signed EIMS transport
Map EDR invoices onto the MoR EIMS /v1/register document and add the cryptographic transport needed to talk to core.mor.gov.et. Mapper: DTOs mirror the supplied Postman collection section by section. Tax is resolved per line via a caller-supplied resolver and throws when unresolved -- the app models no tax at all (invoice.taxAmount is always 0, invoice_lines and the rate catalogue carry no fiscal columns), so a zero-rated default would assert a tax position the codebase cannot support. Seller identity, document number, counters and previous IRN are passed in explicitly; the mapper stays pure. Transport: config, credential loading, RSA-SHA512 signing and /auth/login with an in-memory token cache. Signing reproduces the process that produced a working live token -- compact JSON of the inner request only, exact UTF-8 bytes, base64 signature, and base64 of the certificate file's exact bytes with no parsing or re-encoding. Concurrent callers share one login via an in-flight promise. Refresh is deliberately unimplemented: the collection shows an unsigned refresh body but also ships unsigned examples of calls that do require signing, so an expired token re-logs in instead. Errors normalise to EimsApiException carrying only the gateway's own error fields; secrets, signature, certificate and tokens never reach logs. Key and certificate file patterns are gitignored. Nothing calls EIMS automatically and no invoice entity, migration or UI is touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
40
apps/edr-freight-api/src/scripts/eims-login.ts
Normal file
40
apps/edr-freight-api/src/scripts/eims-login.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import "dotenv/config";
|
||||
import axios from "axios";
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import eimsConfig, { EimsConfig } from "../config/eims.config";
|
||||
import { EimsAuthService } from "../modules/eims/eims-auth.service";
|
||||
import { EimsCredentialsProvider } from "../modules/eims/eims-credentials.provider";
|
||||
import { EimsSignerService } from "../modules/eims/eims-signer.service";
|
||||
|
||||
/**
|
||||
* Manual, developer-run live check of EIMS authentication.
|
||||
*
|
||||
* Run explicitly: pnpm --filter @edr/freight-api eims:login
|
||||
*
|
||||
* Reads credentials from the local .env only. Never runs at boot, never runs in the test suite,
|
||||
* and prints no token, secret, signature or certificate — only whether login succeeded.
|
||||
*/
|
||||
async function main(): Promise<void> {
|
||||
const config = eimsConfig() as EimsConfig;
|
||||
if (!config.enabled) {
|
||||
throw new Error("EIMS_ENABLED is not true — set it in .env before running this check");
|
||||
}
|
||||
|
||||
const configService = { get: () => config } as unknown as ConfigService;
|
||||
const http = new HttpService(axios.create());
|
||||
const credentials = new EimsCredentialsProvider(configService);
|
||||
const auth = new EimsAuthService(http, configService, new EimsSignerService(credentials));
|
||||
|
||||
console.log(`POST ${config.baseUrl}/auth/login (tin=${config.tin})`);
|
||||
const token = await auth.getValidAccessToken();
|
||||
console.log(`✔ login succeeded — access token received (${token.length} chars, not printed)`);
|
||||
|
||||
const cached = await auth.getValidAccessToken();
|
||||
console.log(`✔ second call served from cache: ${cached === token}`);
|
||||
}
|
||||
|
||||
main().catch((err: Error) => {
|
||||
console.error(`✘ ${err.message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Reference in New Issue
Block a user