settld 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/CONFIG.md +12 -0
- package/docs/README.md +3 -0
- package/docs/ops/HOSTED_BASELINE_R2.md +4 -2
- package/docs/ops/MINIMUM_PRODUCTION_TOPOLOGY.md +19 -7
- package/docs/ops/PRODUCTION_DEPLOYMENT_CHECKLIST.md +8 -3
- package/package.json +3 -1
- package/scripts/ci/run-public-onboarding-gate.mjs +136 -0
- package/scripts/setup/login.mjs +67 -1
- package/scripts/setup/onboard.mjs +159 -28
- package/scripts/setup/onboarding-failure-taxonomy.mjs +96 -0
- package/scripts/setup/onboarding-state-machine.mjs +102 -0
- package/services/magic-link/README.md +343 -0
- package/services/magic-link/assets/samples/closepack/known-bad/acceptance/acceptance_criteria.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/acceptance/acceptance_evaluation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/attestation/bundle_head_attestation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/evidence/evidence_index.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/governance/policy.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/governance/revocations.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/manifest.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/attestation/bundle_head_attestation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/governance/policy.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/governance/revocations.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/invoice/invoice_claim.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/manifest.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/metering/metering_report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/attestation/bundle_head_attestation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/events/events.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/events/payload_material.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/events.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/payload_material.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/global/snapshot.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/policy.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/revocations.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/events.jsonl +0 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/payload_material.jsonl +0 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/snapshot.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/job/snapshot.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/keys/public_keys.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/manifest.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/verify/report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/payload/job_proof_bundle/verify/verification_report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/pricing/pricing_matrix.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/pricing/pricing_matrix_signatures.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/settld.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/payload/invoice_bundle/verify/verification_report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/settld.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/sla/sla_definition.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/sla/sla_evaluation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-bad/verify/verification_report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/acceptance/acceptance_criteria.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/acceptance/acceptance_evaluation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/attestation/bundle_head_attestation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/evidence/evidence_index.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/governance/policy.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/governance/revocations.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/manifest.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/attestation/bundle_head_attestation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/governance/policy.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/governance/revocations.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/invoice/invoice_claim.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/manifest.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/metering/metering_report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/attestation/bundle_head_attestation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/events/events.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/events/payload_material.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/events.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/global/events/payload_material.jsonl +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/global/snapshot.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/policy.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/revocations.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/events.jsonl +0 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/events/payload_material.jsonl +0 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/governance/tenant/snapshot.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/job/snapshot.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/keys/public_keys.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/manifest.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/verify/report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/payload/job_proof_bundle/verify/verification_report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/pricing/pricing_matrix.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/pricing/pricing_matrix_signatures.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/settld.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/verify/verification_report.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/settld.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/sla/sla_definition.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/sla/sla_evaluation.json +1 -0
- package/services/magic-link/assets/samples/closepack/known-good/verify/verification_report.json +1 -0
- package/services/magic-link/assets/samples/trust.json +11 -0
- package/services/magic-link/src/audit-log.js +24 -0
- package/services/magic-link/src/buyer-auth.js +220 -0
- package/services/magic-link/src/buyer-notifications.js +402 -0
- package/services/magic-link/src/buyer-users.js +129 -0
- package/services/magic-link/src/decision-otp.js +156 -0
- package/services/magic-link/src/decisions.js +92 -0
- package/services/magic-link/src/ingest-keys.js +137 -0
- package/services/magic-link/src/maintenance.js +70 -0
- package/services/magic-link/src/onboarding-email-sequence.js +331 -0
- package/services/magic-link/src/payment-triggers.js +733 -0
- package/services/magic-link/src/pdf.js +149 -0
- package/services/magic-link/src/policy.js +69 -0
- package/services/magic-link/src/redaction.js +6 -0
- package/services/magic-link/src/render-model.js +70 -0
- package/services/magic-link/src/retention-gc.js +158 -0
- package/services/magic-link/src/run-records.js +496 -0
- package/services/magic-link/src/s3.js +171 -0
- package/services/magic-link/src/server.js +15788 -0
- package/services/magic-link/src/settlement-decisions.js +84 -0
- package/services/magic-link/src/smtp.js +202 -0
- package/services/magic-link/src/storage-cli.js +88 -0
- package/services/magic-link/src/storage-format.js +59 -0
- package/services/magic-link/src/tenant-billing.js +115 -0
- package/services/magic-link/src/tenant-onboarding.js +467 -0
- package/services/magic-link/src/tenant-settings.js +1140 -0
- package/services/magic-link/src/usage.js +80 -0
- package/services/magic-link/src/verify-queue.js +179 -0
- package/services/magic-link/src/verify-worker.js +157 -0
- package/services/magic-link/src/webhook-retries.js +542 -0
- package/services/magic-link/src/webhooks.js +218 -0
- package/src/api/app.js +129 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"generatedAt":"2026-02-02T00:00:00.000Z","listHash":"5c286871ea76001026b765f52296df51f76f4e040b537963fde671850684cf87","listId":"revocations_default_v1","revocations":[],"rotations":[],"schemaVersion":"RevocationList.v1","signature":"IG/Mwb9syG1HtzD7YwEM+eG4dPPhcIqy75SC3GjzoZZcepeWY/YpYVVzxxwBINkaFrr/mtUmq+s+Kizogs75Bw==","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_4bd1ee0813b265cb6670a1cc"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"createdAt":"2026-02-02T00:00:00.000Z","currency":"USD","invoiceId":"invoice_fixture_1","jobProof":{"embeddedPath":"payload/job_proof_bundle","headAttestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","manifestHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8"},"lineItems":[{"amountCents":"1500","code":"WORK_MINUTES","quantity":"10","unitPriceCents":"150"}],"schemaVersion":"InvoiceClaim.v1","subtotalCents":"1500","tenantId":"tenant_fixture","totalCents":"1500"}
|
package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/manifest.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"createdAt":"2026-02-02T00:00:00.000Z","files":[{"bytes":949,"name":"governance/policy.json","sha256":"ecc617ae2edbddddecbc3580b509b6e2f218787238e883a348a575f278f1f662"},{"bytes":409,"name":"governance/revocations.json","sha256":"7dd89653b90b485f06e17990b064a5cb2d2d1e39e144f019f0ab164432d008b2"},{"bytes":519,"name":"invoice/invoice_claim.json","sha256":"fe64b5bee3e55bfdf61628896a1e92df5b4c9a48973e6ffd594e7448d250537f"},{"bytes":537,"name":"metering/metering_report.json","sha256":"1f19f5309e33a4c133fd60fae5dbe10f50b9a12d9541fc8bb975eff432c72c06"},{"bytes":864,"name":"payload/job_proof_bundle/attestation/bundle_head_attestation.json","sha256":"641947fd28044f926bae840ad343bb4d75d6be44b29e329098210f0af4c28a43"},{"bytes":515,"name":"payload/job_proof_bundle/events/events.jsonl","sha256":"1fbc8ef4a9d8a42efc02e2885b043578f3a0584d4054d98c3f06b1cfa276a126"},{"bytes":186,"name":"payload/job_proof_bundle/events/payload_material.jsonl","sha256":"338953ab5054bdc4a7d83c9ef1fef8d3c83ee78916f32c250636238208de62d3"},{"bytes":778,"name":"payload/job_proof_bundle/governance/global/events/events.jsonl","sha256":"a56f271f30f0c485a0cb80ab65f74b65fd438422d1f9d2e7e1664e5f028ce520"},{"bytes":449,"name":"payload/job_proof_bundle/governance/global/events/payload_material.jsonl","sha256":"1e2b95a0192f324d49a45c4378936d5a32c6548f44648d72ed79522186b0867f"},{"bytes":152,"name":"payload/job_proof_bundle/governance/global/snapshot.json","sha256":"4a5b0d84a0b96dd0fec4650d1f5483b8a4795d0c891595d730bf74a556f4368b"},{"bytes":951,"name":"payload/job_proof_bundle/governance/policy.json","sha256":"e88b6fdf8494a31264f18df85a4f2de96d69be1c03ddbfc901157a03cf02341c"},{"bytes":409,"name":"payload/job_proof_bundle/governance/revocations.json","sha256":"7dd89653b90b485f06e17990b064a5cb2d2d1e39e144f019f0ab164432d008b2"},{"bytes":0,"name":"payload/job_proof_bundle/governance/tenant/events/events.jsonl","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"bytes":0,"name":"payload/job_proof_bundle/governance/tenant/events/payload_material.jsonl","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"bytes":66,"name":"payload/job_proof_bundle/governance/tenant/snapshot.json","sha256":"353bc53464b75d5a61a810a29014b9a2fcdcdeb0762d18da745ef15765919400"},{"bytes":138,"name":"payload/job_proof_bundle/job/snapshot.json","sha256":"819c087bd8fc5a6fbf833ff13e31915433bc1687f32e8231bd6263718f31bab8"},{"bytes":470,"name":"payload/job_proof_bundle/keys/public_keys.json","sha256":"e88c404c67d245925a0c7e21d5381428b2ef3df69fd31041a39105c5ec7849eb"},{"bytes":1930,"name":"payload/job_proof_bundle/manifest.json","sha256":"844451c8a0efb43b28c5f4ea44dd291e908590fa00583d7d9cc6934c2143c7fb"},{"bytes":297,"name":"payload/job_proof_bundle/verify/report.json","sha256":"1f7725687929166e2258c0f68f4f50d5419a59a0ef490abd407fbcfc9a4123f8"},{"bytes":1312,"name":"payload/job_proof_bundle/verify/verification_report.json","sha256":"86c22381caf30735783418f603518c8f7301b53cb2e8f4e49326e1e1d46c9f06"},{"bytes":112,"name":"pricing/pricing_matrix.json","sha256":"c43476f394086b2f66ac1df090a303f32713547b64ecf29e83ac6762ddbc1fb3"},{"bytes":346,"name":"pricing/pricing_matrix_signatures.json","sha256":"25c1ebf3c5af800807a1fe1a9b824760099c5171765fb4ef8610cfb91d20564e"},{"bytes":601,"name":"settld.json","sha256":"b6033ab19043026b8c5df1c41ce33446088edebc96a0c1cf564ab3d4484b79af"}],"hashing":{"excludes":["verify/**"],"fileOrder":"path_asc","schemaVersion":"InvoiceBundleManifestHash.v1"},"invoiceId":"invoice_fixture_1","manifestHash":"7b348ab1bb5a84f7899b718a08bd7827bb6b7b213b97ef74142061270fb2233a","protocol":"1.0","schemaVersion":"InvoiceBundleManifest.v1","tenantId":"tenant_fixture","type":"InvoiceBundle.v1"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"evidenceRefs":[{"path":"job/snapshot.json","sha256":"819c087bd8fc5a6fbf833ff13e31915433bc1687f32e8231bd6263718f31bab8"}],"generatedAt":"2026-02-02T00:00:00.000Z","invoiceId":"invoice_fixture_1","items":[{"code":"WORK_MINUTES","quantity":"10"}],"jobProof":{"embeddedPath":"payload/job_proof_bundle","headAttestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","manifestHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8"},"schemaVersion":"MeteringReport.v1","tenantId":"tenant_fixture"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"attestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","generatedAt":"2026-02-02T00:00:00.000Z","heads":{"governance":{"global":{"lastChainHash":"92b4cd341b48c58fc8dffea3d6bbc0f62f979965d9271339ced8b8f7bf258e60","lastEventId":"evt_gov_serverA_registered"},"tenant":{"lastChainHash":null,"lastEventId":null}},"job":{"lastChainHash":"e698a6e1d597a4be85d31bdac90a34a66b9d43886ad43f59c0cc4e77efdde78d","lastEventId":"evt_job_created"}},"kind":"JobProofBundle.v1","manifestHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","schemaVersion":"BundleHeadAttestation.v1","scope":{"jobId":"job_fixture_1"},"signature":"WqNZduwUjQcSlBF7PnWoRyaBuyeD73eevVB3jz58Fa3xd6J+HT6ivilEdUMaATF4QxA+s2p4sMhJYrHeqKB3CQ==","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_9ed38005b42d9364601ffab3","tenantId":"tenant_fixture"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"actor":{"id":"proxy","type":"system"},"at":"2026-02-02T00:00:00.000Z","chainHash":"e698a6e1d597a4be85d31bdac90a34a66b9d43886ad43f59c0cc4e77efdde78d","id":"evt_job_created","payload":{"jobId":"job_fixture_1"},"payloadHash":"6b46a068dfe3479d2f09f84ae0ed29bb7e2cd9e1ec4c17bccca1c1307a04c47c","prevChainHash":null,"signature":"ZmciBEg0pm549K4dpIE988oH5xwRDeimD9g73v5hHXKeGYa0RXZ3hFkJ6BzxCxODFDlUQzzyJkDkjKSpeOkvDg==","signerKeyId":"key_9ed38005b42d9364601ffab3","streamId":"job_fixture_1","type":"JOB_CREATED","v":1}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"actor":{"id":"proxy","type":"system"},"at":"2026-02-02T00:00:00.000Z","id":"evt_job_created","payload":{"jobId":"job_fixture_1"},"streamId":"job_fixture_1","type":"JOB_CREATED","v":1}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"actor":{"id":"proxy","type":"system"},"at":"2026-01-01T00:00:00.000Z","chainHash":"92b4cd341b48c58fc8dffea3d6bbc0f62f979965d9271339ced8b8f7bf258e60","id":"evt_gov_serverA_registered","payload":{"keyId":"key_9ed38005b42d9364601ffab3","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAQ70D1FtwopWnvJeFsiKdThSP/9QADgSeaZYKip79KQI=\n-----END PUBLIC KEY-----\n","reason":"fixture","registeredAt":"2026-01-01T00:00:00.000Z","tenantId":"tenant_default"},"payloadHash":"d996dc5347a113a6050ed453e7b5feb2f1edb93a5ffb68d4fbca7b015336c61b","prevChainHash":null,"signature":"ovzEbrAqeEhQ7xrz/cIIor85ygkD7bgBvbWxKsQetxKkWjCBDoVUfmPn9UrqF/iNtIZcqBK9TA/SXrNk/VjHAg==","signerKeyId":"key_9ed38005b42d9364601ffab3","streamId":"governance","type":"SERVER_SIGNER_KEY_REGISTERED","v":1}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"actor":{"id":"proxy","type":"system"},"at":"2026-01-01T00:00:00.000Z","id":"evt_gov_serverA_registered","payload":{"keyId":"key_9ed38005b42d9364601ffab3","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAQ70D1FtwopWnvJeFsiKdThSP/9QADgSeaZYKip79KQI=\n-----END PUBLIC KEY-----\n","reason":"fixture","registeredAt":"2026-01-01T00:00:00.000Z","tenantId":"tenant_default"},"streamId":"governance","type":"SERVER_SIGNER_KEY_REGISTERED","v":1}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"lastChainHash":"92b4cd341b48c58fc8dffea3d6bbc0f62f979965d9271339ced8b8f7bf258e60","lastEventId":"evt_gov_serverA_registered","streamId":"governance"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"algorithms":["ed25519"],"bundleHeadAttestationSigners":[{"allowedKeyIds":["key_9ed38005b42d9364601ffab3"],"allowedScopes":["global","tenant"],"requireGoverned":true,"requiredPurpose":"server","subjectType":"JobProofBundle.v1"}],"generatedAt":"2026-02-02T00:00:00.000Z","policyHash":"f8588a9eef597dd67d29035ff157b00a79bbf2d17056ce063e9a65a390d005d7","policyId":"governance_policy_default_v2","revocationList":{"path":"governance/revocations.json","sha256":"5c286871ea76001026b765f52296df51f76f4e040b537963fde671850684cf87"},"schemaVersion":"GovernancePolicy.v2","signature":"lxr9K5+X7/oVnIFATriFJW/+i96UUDXPP5ATFseAtqlSe/0juicaoad8U0kMT4Xks6xDHrLLOD/ASEm/DRcVDA==","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_4bd1ee0813b265cb6670a1cc","verificationReportSigners":[{"allowedKeyIds":["key_9ed38005b42d9364601ffab3"],"allowedScopes":["global","tenant"],"requireGoverned":true,"requiredPurpose":"server","subjectType":"JobProofBundle.v1"}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"generatedAt":"2026-02-02T00:00:00.000Z","listHash":"5c286871ea76001026b765f52296df51f76f4e040b537963fde671850684cf87","listId":"revocations_default_v1","revocations":[],"rotations":[],"schemaVersion":"RevocationList.v1","signature":"IG/Mwb9syG1HtzD7YwEM+eG4dPPhcIqy75SC3GjzoZZcepeWY/YpYVVzxxwBINkaFrr/mtUmq+s+Kizogs75Bw==","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_4bd1ee0813b265cb6670a1cc"}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"lastChainHash":null,"lastEventId":null,"streamId":"governance"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"job_fixture_1","lastChainHash":"e698a6e1d597a4be85d31bdac90a34a66b9d43886ad43f59c0cc4e77efdde78d","lastEventId":"evt_job_created"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"generatedAt":"2026-02-02T00:00:00.000Z","keys":[{"createdAt":null,"description":null,"keyId":"key_9ed38005b42d9364601ffab3","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAQ70D1FtwopWnvJeFsiKdThSP/9QADgSeaZYKip79KQI=\n-----END PUBLIC KEY-----\n","purpose":"server","revokedAt":null,"rotatedAt":null,"status":null,"tenantId":"tenant_fixture","validFrom":null,"validTo":null}],"order":"keyId_asc","schemaVersion":"PublicKeys.v1","tenantId":"tenant_fixture"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"files":[{"bytes":515,"name":"events/events.jsonl","sha256":"1fbc8ef4a9d8a42efc02e2885b043578f3a0584d4054d98c3f06b1cfa276a126"},{"bytes":186,"name":"events/payload_material.jsonl","sha256":"338953ab5054bdc4a7d83c9ef1fef8d3c83ee78916f32c250636238208de62d3"},{"bytes":778,"name":"governance/global/events/events.jsonl","sha256":"a56f271f30f0c485a0cb80ab65f74b65fd438422d1f9d2e7e1664e5f028ce520"},{"bytes":449,"name":"governance/global/events/payload_material.jsonl","sha256":"1e2b95a0192f324d49a45c4378936d5a32c6548f44648d72ed79522186b0867f"},{"bytes":152,"name":"governance/global/snapshot.json","sha256":"4a5b0d84a0b96dd0fec4650d1f5483b8a4795d0c891595d730bf74a556f4368b"},{"bytes":951,"name":"governance/policy.json","sha256":"e88b6fdf8494a31264f18df85a4f2de96d69be1c03ddbfc901157a03cf02341c"},{"bytes":409,"name":"governance/revocations.json","sha256":"7dd89653b90b485f06e17990b064a5cb2d2d1e39e144f019f0ab164432d008b2"},{"bytes":0,"name":"governance/tenant/events/events.jsonl","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"bytes":0,"name":"governance/tenant/events/payload_material.jsonl","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},{"bytes":66,"name":"governance/tenant/snapshot.json","sha256":"353bc53464b75d5a61a810a29014b9a2fcdcdeb0762d18da745ef15765919400"},{"bytes":138,"name":"job/snapshot.json","sha256":"819c087bd8fc5a6fbf833ff13e31915433bc1687f32e8231bd6263718f31bab8"},{"bytes":470,"name":"keys/public_keys.json","sha256":"e88c404c67d245925a0c7e21d5381428b2ef3df69fd31041a39105c5ec7849eb"}],"generatedAt":"2026-02-02T00:00:00.000Z","hashing":{"excludes":["verify/**"],"fileOrder":"path_asc","schemaVersion":"ProofBundleManifestHash.v1"},"kind":"JobProofBundle.v1","manifestHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","schemaVersion":"ProofBundleManifest.v1","scope":{"jobId":"job_fixture_1"},"tenantId":"tenant_fixture"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"artifacts":[],"compiledPolicies":[],"eventChain":{"ok":true},"generatedAt":"2026-02-02T00:00:00.000Z","kind":"JobProofBundle.v1","schemaVersion":"ProofBundleVerifyReport.v1","scope":{"jobId":"job_fixture_1"},"settlementProofRefs":{"checked":0,"errors":[],"ok":true},"tenantId":"tenant_fixture"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"bundleHeadAttestation":{"attestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","manifestHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","schemaVersion":"BundleHeadAttestation.v1","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_9ed38005b42d9364601ffab3"},"profile":"strict","reportHash":"7bfcc64af8ec9c5df4d5b87150cab3954d5748deb725be672a0cd464c20b3b29","schemaVersion":"VerificationReport.v1","signature":"4QIO0WgDiudPESt/mGSFC7zXcU7RJnF4MftOQHTB7BvtVenQ7SubgBkl1Kc8jj+GPQdtGbIxWZQRR5pL9OrKBQ==","signedAt":"2026-02-02T00:00:00.000Z","signer":{"governanceEventRef":{"chainHash":"92b4cd341b48c58fc8dffea3d6bbc0f62f979965d9271339ced8b8f7bf258e60","eventId":"evt_gov_serverA_registered","payloadHash":"d996dc5347a113a6050ed453e7b5feb2f1edb93a5ffb68d4fbca7b015336c61b","type":"SERVER_SIGNER_KEY_REGISTERED"},"keyId":"key_9ed38005b42d9364601ffab3","scope":"global"},"signerKeyId":"key_9ed38005b42d9364601ffab3","subject":{"createdAt":"2026-02-02T00:00:00.000Z","manifestHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","scope":{"jobId":"job_fixture_1"},"tenantId":"tenant_fixture","type":"JobProofBundle.v1"},"tool":{"commit":"0123456789abcdef0123456789abcdef01234567","name":"settld","version":"0.0.0-fixture"},"warnings":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"currency":"USD","prices":[{"code":"WORK_MINUTES","unitPriceCents":"150"}],"schemaVersion":"PricingMatrix.v1"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"pricingMatrixCanonicalHash":"0fdad09bbc49efc35602932ef73950509de0f4f929bac0af35efeeb4d138c128","schemaVersion":"PricingMatrixSignatures.v2","signatures":[{"signature":"BzQbDsMHYcKavlJznp5nc7++boaRGMzFrfTPst79HrTMp7cIfL5foPq7zYzvOc7QsIqyycFCi/WOzIqha6/KAg==","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_4bd1ee0813b265cb6670a1cc"}]}
|
package/services/magic-link/assets/samples/closepack/known-good/payload/invoice_bundle/settld.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"createdAt":"2026-02-02T00:00:00.000Z","inputs":{"invoiceClaimHash":"fe64b5bee3e55bfdf61628896a1e92df5b4c9a48973e6ffd594e7448d250537f","jobProofBundleHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","jobProofHeadAttestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","meteringReportHash":"1f19f5309e33a4c133fd60fae5dbe10f50b9a12d9541fc8bb975eff432c72c06","pricingMatrixHash":"c43476f394086b2f66ac1df090a303f32713547b64ecf29e83ac6762ddbc1fb3"},"invoiceId":"invoice_fixture_1","protocol":"1.0","tenantId":"tenant_fixture","type":"InvoiceBundle.v1"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"bundleHeadAttestation":{"attestationHash":"8b15727b811d865a5187aa9da01066a3271dd6465f6c18e6c8d1b59c0a955755","manifestHash":"7b348ab1bb5a84f7899b718a08bd7827bb6b7b213b97ef74142061270fb2233a","schemaVersion":"BundleHeadAttestation.v1","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_9ed38005b42d9364601ffab3"},"inputs":{"invoiceClaimHash":"fe64b5bee3e55bfdf61628896a1e92df5b4c9a48973e6ffd594e7448d250537f","jobProofBundleHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","jobProofHeadAttestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","meteringReportHash":"1f19f5309e33a4c133fd60fae5dbe10f50b9a12d9541fc8bb975eff432c72c06","pricingMatrixHash":"c43476f394086b2f66ac1df090a303f32713547b64ecf29e83ac6762ddbc1fb3"},"policy":null,"profile":"strict","reportHash":"4ce80dafad18c9a29898f65df7e501d10e81c5705982db859ac2e90d0cf72d97","schemaVersion":"VerificationReport.v1","signature":"7mKxfGQ/dSufj2WY9aWJsuhbAe6PgLkGZdBpU4F5ojU9kyjEXEOLZx2wosmzPGVy6b9isC/kgyqw3kx65kwGDQ==","signedAt":"2026-02-02T00:00:00.000Z","signer":{"governanceEventRef":null,"keyId":"key_9ed38005b42d9364601ffab3","scope":"global"},"signerKeyId":"key_9ed38005b42d9364601ffab3","subject":{"createdAt":"2026-02-02T00:00:00.000Z","invoiceId":"invoice_fixture_1","manifestHash":"7b348ab1bb5a84f7899b718a08bd7827bb6b7b213b97ef74142061270fb2233a","protocol":"1.0","tenantId":"tenant_fixture","type":"InvoiceBundle.v1"},"tool":{"commit":"0123456789abcdef0123456789abcdef01234567","name":"settld","version":"0.0.0-fixture"},"warnings":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"createdAt":"2026-02-02T00:00:00.000Z","inputs":{"invoiceBundleCreatedAt":"2026-02-02T00:00:00.000Z","invoiceBundleHeadAttestationHash":"8b15727b811d865a5187aa9da01066a3271dd6465f6c18e6c8d1b59c0a955755","invoiceBundleManifestHash":"7b348ab1bb5a84f7899b718a08bd7827bb6b7b213b97ef74142061270fb2233a","invoiceBundleTenantId":"tenant_fixture","invoiceBundleType":"InvoiceBundle.v1","invoiceClaimHash":"fe64b5bee3e55bfdf61628896a1e92df5b4c9a48973e6ffd594e7448d250537f","jobProofBundleHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","jobProofHeadAttestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","meteringReportHash":"1f19f5309e33a4c133fd60fae5dbe10f50b9a12d9541fc8bb975eff432c72c06","pricingMatrixHash":"c43476f394086b2f66ac1df090a303f32713547b64ecf29e83ac6762ddbc1fb3"},"invoiceBundle":{"embeddedPath":"payload/invoice_bundle","headAttestationHash":"8b15727b811d865a5187aa9da01066a3271dd6465f6c18e6c8d1b59c0a955755","manifestHash":"7b348ab1bb5a84f7899b718a08bd7827bb6b7b213b97ef74142061270fb2233a"},"invoiceId":"invoice_fixture_1","protocol":"1.0","tenantId":"tenant_fixture","type":"ClosePack.v1"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"generatedAt":"2026-02-02T00:00:00.000Z","rules":[],"schemaVersion":"SlaDefinition.v1"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"generatedAt":"2026-02-02T00:00:00.000Z","overallStatus":"ok","results":[],"schemaVersion":"SlaEvaluation.v1"}
|
package/services/magic-link/assets/samples/closepack/known-good/verify/verification_report.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"bundleHeadAttestation":{"attestationHash":"eaf7436e7aef6529f7d95031ffc7f741ed68a65d8d2b5e3048f9593076ae6652","manifestHash":"0821f3de3cf3bbe659a61a0c14ca38fc82b22239f039e908a9e4b36e4f27744a","schemaVersion":"BundleHeadAttestation.v1","signedAt":"2026-02-02T00:00:00.000Z","signerKeyId":"key_9ed38005b42d9364601ffab3"},"inputs":{"invoiceBundleCreatedAt":"2026-02-02T00:00:00.000Z","invoiceBundleHeadAttestationHash":"8b15727b811d865a5187aa9da01066a3271dd6465f6c18e6c8d1b59c0a955755","invoiceBundleManifestHash":"7b348ab1bb5a84f7899b718a08bd7827bb6b7b213b97ef74142061270fb2233a","invoiceBundleTenantId":"tenant_fixture","invoiceBundleType":"InvoiceBundle.v1","invoiceClaimHash":"fe64b5bee3e55bfdf61628896a1e92df5b4c9a48973e6ffd594e7448d250537f","jobProofBundleHash":"6775ce8a23cb436fdd6e2809bf79d2b1ac05f76a820094342b0cc68f425ef9d8","jobProofHeadAttestationHash":"c70b62f9e28ac91fa07073c802495d1801921c0d01924a2538f981a7e18f8d5b","meteringReportHash":"1f19f5309e33a4c133fd60fae5dbe10f50b9a12d9541fc8bb975eff432c72c06","pricingMatrixHash":"c43476f394086b2f66ac1df090a303f32713547b64ecf29e83ac6762ddbc1fb3"},"policy":null,"profile":"strict","reportHash":"3f1570dffe2e987105a6fcdabcd6e494006bf4ba0f1ff512b358bb21edac3c6a","schemaVersion":"VerificationReport.v1","signature":"WQPcVQLKsn0ivoKQa41yHDeAjzVrbkUgLCh0KWuQFKNthdT3Dvs7cDWLsp75BatG8Ymu9lq9kjc973mDinFjDA==","signedAt":"2026-02-02T00:00:00.000Z","signer":{"governanceEventRef":null,"keyId":"key_9ed38005b42d9364601ffab3","scope":"global"},"signerKeyId":"key_9ed38005b42d9364601ffab3","subject":{"createdAt":"2026-02-02T00:00:00.000Z","invoiceId":"invoice_fixture_1","manifestHash":"0821f3de3cf3bbe659a61a0c14ca38fc82b22239f039e908a9e4b36e4f27744a","protocol":"1.0","scope":{"invoiceId":"invoice_fixture_1"},"tenantId":"tenant_fixture","type":"ClosePack.v1"},"tool":{"commit":"0123456789abcdef0123456789abcdef01234567","name":"settld","version":"0.0.0-fixture"},"warnings":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"governanceRoots": {
|
|
3
|
+
"key_4bd1ee0813b265cb6670a1cc": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAFzw7qxt31VJOSAJCw7zy267twYhbJzf9H0dwm3ZmCDM=\n-----END PUBLIC KEY-----\n"
|
|
4
|
+
},
|
|
5
|
+
"pricingSigners": {
|
|
6
|
+
"key_4bd1ee0813b265cb6670a1cc": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAFzw7qxt31VJOSAJCw7zy267twYhbJzf9H0dwm3ZmCDM=\n-----END PUBLIC KEY-----\n"
|
|
7
|
+
},
|
|
8
|
+
"timeAuthorities": {
|
|
9
|
+
"key_0c6b00150d3433d8d612eef2": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA9CRBU82m4eS1uHNL6PtKBKPLN0YdtubYFP/f9flGVd0=\n-----END PUBLIC KEY-----\n"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
function monthKeyUtc(d) {
|
|
5
|
+
const year = d.getUTCFullYear();
|
|
6
|
+
const month = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
7
|
+
return `${year}-${month}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function auditJsonlPath({ dataDir, tenantId, monthKey }) {
|
|
11
|
+
return path.join(dataDir, "audit", tenantId, `${monthKey}.jsonl`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function appendAuditRecord({ dataDir, tenantId, record } = {}) {
|
|
15
|
+
const at = typeof record?.at === "string" ? record.at : new Date().toISOString();
|
|
16
|
+
const atMs = Date.parse(at);
|
|
17
|
+
const monthKey = Number.isFinite(atMs) ? monthKeyUtc(new Date(atMs)) : monthKeyUtc(new Date());
|
|
18
|
+
|
|
19
|
+
const fp = auditJsonlPath({ dataDir, tenantId, monthKey });
|
|
20
|
+
await fs.mkdir(path.dirname(fp), { recursive: true });
|
|
21
|
+
await fs.appendFile(fp, JSON.stringify({ schemaVersion: "MagicLinkAuditRecord.v1", ...record, at }) + "\n", "utf8");
|
|
22
|
+
return { ok: true, monthKey };
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { sendSmtpMail } from "./smtp.js";
|
|
6
|
+
|
|
7
|
+
function nowIso() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function sha256Hex(text) {
|
|
12
|
+
return crypto.createHash("sha256").update(String(text ?? ""), "utf8").digest("hex");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function clampText(v, { max }) {
|
|
16
|
+
const s = String(v ?? "").trim();
|
|
17
|
+
if (!s) return null;
|
|
18
|
+
return s.length <= max ? s : s.slice(0, Math.max(0, max - 1)) + "…";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeEmailLower(value) {
|
|
22
|
+
const raw = clampText(value, { max: 320 });
|
|
23
|
+
if (!raw) return null;
|
|
24
|
+
const email = raw.toLowerCase();
|
|
25
|
+
if (!email.includes("@")) return null;
|
|
26
|
+
const [local, domain, ...rest] = email.split("@");
|
|
27
|
+
if (!local || !domain || rest.length) return null;
|
|
28
|
+
if (/\s/.test(email)) return null;
|
|
29
|
+
return email;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function otpRecordPath({ dataDir, tenantId, email }) {
|
|
33
|
+
const key = sha256Hex(`${tenantId}\n${email}`);
|
|
34
|
+
return path.join(dataDir, "buyer-otp", tenantId, `${key}.json`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function otpOutboxPath({ dataDir, tenantId, email }) {
|
|
38
|
+
const key = sha256Hex(`${tenantId}\n${email}`);
|
|
39
|
+
return path.join(dataDir, "buyer-otp-outbox", `${tenantId}_${key}.json`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function issueCode6() {
|
|
43
|
+
const n = crypto.randomInt(0, 1_000_000);
|
|
44
|
+
return String(n).padStart(6, "0");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function issueBuyerOtp({ dataDir, tenantId, email, ttlSeconds, deliveryMode, smtp } = {}) {
|
|
48
|
+
const emailNorm = normalizeEmailLower(email);
|
|
49
|
+
if (!emailNorm) return { ok: false, error: "INVALID_EMAIL", message: "invalid email" };
|
|
50
|
+
|
|
51
|
+
const t = String(tenantId ?? "").trim();
|
|
52
|
+
if (!t) return { ok: false, error: "INVALID_TENANT", message: "tenantId is required" };
|
|
53
|
+
|
|
54
|
+
const ttl = Number.parseInt(String(ttlSeconds ?? ""), 10);
|
|
55
|
+
if (!Number.isInteger(ttl) || ttl <= 0) throw new TypeError("ttlSeconds must be a positive integer");
|
|
56
|
+
|
|
57
|
+
const code = issueCode6();
|
|
58
|
+
const createdAt = nowIso();
|
|
59
|
+
const expiresAt = new Date(Date.now() + ttl * 1000).toISOString();
|
|
60
|
+
const codeSha256 = sha256Hex(`${t}\n${emailNorm}\n${code}`);
|
|
61
|
+
|
|
62
|
+
const record = {
|
|
63
|
+
schemaVersion: "BuyerOtpRecord.v1",
|
|
64
|
+
tenantId: t,
|
|
65
|
+
email: emailNorm,
|
|
66
|
+
codeSha256,
|
|
67
|
+
createdAt,
|
|
68
|
+
expiresAt,
|
|
69
|
+
consumedAt: null,
|
|
70
|
+
attempts: 0
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const fp = otpRecordPath({ dataDir, tenantId: t, email: emailNorm });
|
|
74
|
+
await fs.mkdir(path.dirname(fp), { recursive: true });
|
|
75
|
+
await fs.writeFile(fp, JSON.stringify(record, null, 2) + "\n", "utf8");
|
|
76
|
+
|
|
77
|
+
const mode = String(deliveryMode ?? "record").trim().toLowerCase();
|
|
78
|
+
if (mode === "record") {
|
|
79
|
+
const outFp = otpOutboxPath({ dataDir, tenantId: t, email: emailNorm });
|
|
80
|
+
await fs.mkdir(path.dirname(outFp), { recursive: true });
|
|
81
|
+
await fs.writeFile(
|
|
82
|
+
outFp,
|
|
83
|
+
JSON.stringify(
|
|
84
|
+
{ schemaVersion: "BuyerOtpOutboxRecord.v1", tenantId: t, email: emailNorm, code, createdAt, expiresAt },
|
|
85
|
+
null,
|
|
86
|
+
2
|
|
87
|
+
) + "\n",
|
|
88
|
+
"utf8"
|
|
89
|
+
);
|
|
90
|
+
} else if (mode === "log") {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log(`buyer otp tenant=${t} email=${emailNorm} code=${code} expiresAt=${expiresAt}`);
|
|
93
|
+
} else if (mode === "smtp") {
|
|
94
|
+
const from = typeof smtp?.from === "string" ? smtp.from.trim() : "";
|
|
95
|
+
if (!from) return { ok: false, error: "SMTP_NOT_CONFIGURED", message: "smtp.from is required" };
|
|
96
|
+
try {
|
|
97
|
+
await sendSmtpMail({
|
|
98
|
+
host: smtp?.host,
|
|
99
|
+
port: smtp?.port,
|
|
100
|
+
secure: Boolean(smtp?.secure),
|
|
101
|
+
starttls: smtp?.starttls === undefined ? true : Boolean(smtp?.starttls),
|
|
102
|
+
auth: smtp?.user && smtp?.pass ? { user: smtp.user, pass: smtp.pass } : null,
|
|
103
|
+
from,
|
|
104
|
+
to: emailNorm,
|
|
105
|
+
subject: "Your Settld login code",
|
|
106
|
+
text: `Your login code is: ${code}\n\nThis code expires at: ${expiresAt}\n\nIf you did not request this code, you can ignore this email.\n`
|
|
107
|
+
});
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return { ok: false, error: "SMTP_SEND_FAILED", message: err?.message ?? String(err ?? "smtp failed") };
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error("invalid deliveryMode");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { ok: true, tenantId: t, email: emailNorm, expiresAt };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function verifyAndConsumeBuyerOtp({ dataDir, tenantId, email, code, maxAttempts }) {
|
|
119
|
+
const emailNorm = normalizeEmailLower(email);
|
|
120
|
+
const codeNorm = clampText(code, { max: 32 });
|
|
121
|
+
const t = String(tenantId ?? "").trim();
|
|
122
|
+
if (!t || !emailNorm || !codeNorm) return { ok: false, error: "OTP_INVALID", message: "tenantId, email, and code are required" };
|
|
123
|
+
|
|
124
|
+
const max = Number.parseInt(String(maxAttempts ?? ""), 10);
|
|
125
|
+
if (!Number.isInteger(max) || max < 1) throw new TypeError("maxAttempts must be an integer >= 1");
|
|
126
|
+
|
|
127
|
+
const fp = otpRecordPath({ dataDir, tenantId: t, email: emailNorm });
|
|
128
|
+
let rec = null;
|
|
129
|
+
try {
|
|
130
|
+
rec = JSON.parse(await fs.readFile(fp, "utf8"));
|
|
131
|
+
} catch {
|
|
132
|
+
rec = null;
|
|
133
|
+
}
|
|
134
|
+
if (!rec || typeof rec !== "object" || Array.isArray(rec) || rec.schemaVersion !== "BuyerOtpRecord.v1") {
|
|
135
|
+
return { ok: false, error: "OTP_MISSING", message: "no active otp" };
|
|
136
|
+
}
|
|
137
|
+
if (String(rec.tenantId ?? "") !== t || String(rec.email ?? "") !== emailNorm) {
|
|
138
|
+
return { ok: false, error: "OTP_MISSING", message: "no active otp" };
|
|
139
|
+
}
|
|
140
|
+
if (typeof rec.consumedAt === "string" && rec.consumedAt) return { ok: false, error: "OTP_CONSUMED", message: "otp already used" };
|
|
141
|
+
|
|
142
|
+
const expiresMs = Date.parse(String(rec.expiresAt ?? ""));
|
|
143
|
+
if (!Number.isFinite(expiresMs) || Date.now() > expiresMs) return { ok: false, error: "OTP_EXPIRED", message: "otp expired" };
|
|
144
|
+
|
|
145
|
+
const attempts = Number.parseInt(String(rec.attempts ?? "0"), 10);
|
|
146
|
+
if (Number.isInteger(attempts) && attempts >= max) return { ok: false, error: "OTP_LOCKED", message: "too many attempts" };
|
|
147
|
+
|
|
148
|
+
const expected = String(rec.codeSha256 ?? "");
|
|
149
|
+
const actual = sha256Hex(`${t}\n${emailNorm}\n${codeNorm}`);
|
|
150
|
+
if (expected !== actual) {
|
|
151
|
+
rec.attempts = (Number.isInteger(attempts) ? attempts : 0) + 1;
|
|
152
|
+
await fs.writeFile(fp, JSON.stringify(rec, null, 2) + "\n", "utf8");
|
|
153
|
+
return { ok: false, error: "OTP_INVALID", message: "invalid code" };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
rec.consumedAt = nowIso();
|
|
157
|
+
rec.attempts = Number.isInteger(attempts) ? attempts : 0;
|
|
158
|
+
await fs.writeFile(fp, JSON.stringify(rec, null, 2) + "\n", "utf8");
|
|
159
|
+
return { ok: true, tenantId: t, email: emailNorm };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function hmacBase64Url(key, text) {
|
|
163
|
+
return crypto.createHmac("sha256", key).update(String(text ?? ""), "utf8").digest("base64url");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function safeEqualBase64Url(a, b) {
|
|
167
|
+
try {
|
|
168
|
+
const aa = Buffer.from(String(a ?? ""), "base64url");
|
|
169
|
+
const bb = Buffer.from(String(b ?? ""), "base64url");
|
|
170
|
+
if (aa.length !== bb.length) return false;
|
|
171
|
+
return crypto.timingSafeEqual(aa, bb);
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function createBuyerSessionToken({ sessionKey, tenantId, email, ttlSeconds }) {
|
|
178
|
+
const t = String(tenantId ?? "").trim();
|
|
179
|
+
const e = normalizeEmailLower(email);
|
|
180
|
+
const ttl = Number.parseInt(String(ttlSeconds ?? ""), 10);
|
|
181
|
+
if (!t || !e) return { ok: false, error: "INVALID_SESSION_INPUT" };
|
|
182
|
+
if (!Buffer.isBuffer(sessionKey) || sessionKey.length < 16) return { ok: false, error: "SESSION_KEY_MISSING" };
|
|
183
|
+
if (!Number.isInteger(ttl) || ttl <= 0) return { ok: false, error: "INVALID_TTL" };
|
|
184
|
+
|
|
185
|
+
const issuedAt = nowIso();
|
|
186
|
+
const expiresAt = new Date(Date.now() + ttl * 1000).toISOString();
|
|
187
|
+
const nonce = crypto.randomBytes(16).toString("hex");
|
|
188
|
+
const payload = { schemaVersion: "MagicLinkBuyerSession.v1", tenantId: t, email: e, issuedAt, expiresAt, nonce };
|
|
189
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
190
|
+
const sigB64 = hmacBase64Url(sessionKey, payloadB64);
|
|
191
|
+
return { ok: true, token: `${payloadB64}.${sigB64}`, payload };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function verifyBuyerSessionToken({ sessionKey, token }) {
|
|
195
|
+
if (!Buffer.isBuffer(sessionKey) || sessionKey.length < 16) return { ok: false, error: "SESSION_KEY_MISSING" };
|
|
196
|
+
const raw = String(token ?? "").trim();
|
|
197
|
+
const parts = raw.split(".");
|
|
198
|
+
if (parts.length !== 2) return { ok: false, error: "SESSION_INVALID" };
|
|
199
|
+
const [payloadB64, sigB64] = parts;
|
|
200
|
+
const expected = hmacBase64Url(sessionKey, payloadB64);
|
|
201
|
+
if (!safeEqualBase64Url(expected, sigB64)) return { ok: false, error: "SESSION_INVALID" };
|
|
202
|
+
|
|
203
|
+
let payload = null;
|
|
204
|
+
try {
|
|
205
|
+
payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
|
|
206
|
+
} catch {
|
|
207
|
+
payload = null;
|
|
208
|
+
}
|
|
209
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { ok: false, error: "SESSION_INVALID" };
|
|
210
|
+
if (String(payload.schemaVersion ?? "") !== "MagicLinkBuyerSession.v1") return { ok: false, error: "SESSION_INVALID" };
|
|
211
|
+
|
|
212
|
+
const tenantId = String(payload.tenantId ?? "").trim();
|
|
213
|
+
const email = normalizeEmailLower(payload.email);
|
|
214
|
+
if (!tenantId || !email) return { ok: false, error: "SESSION_INVALID" };
|
|
215
|
+
|
|
216
|
+
const expiresMs = Date.parse(String(payload.expiresAt ?? ""));
|
|
217
|
+
if (!Number.isFinite(expiresMs) || Date.now() > expiresMs) return { ok: false, error: "SESSION_EXPIRED" };
|
|
218
|
+
|
|
219
|
+
return { ok: true, payload: { ...payload, tenantId, email } };
|
|
220
|
+
}
|