@heavybit/shared-lib 2.1.0 → 2.7.0
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/README.md +163 -2
- package/dist/cjs/access/index.js +9 -1
- package/dist/cjs/access/index.js.map +1 -1
- package/dist/cjs/approvals/index.js +137 -0
- package/dist/cjs/approvals/index.js.map +1 -0
- package/dist/cjs/audit/index.js +74 -0
- package/dist/cjs/audit/index.js.map +1 -0
- package/dist/cjs/cache/index.js +90 -16
- package/dist/cjs/cache/index.js.map +1 -1
- package/dist/cjs/cache/keyspace.js +101 -0
- package/dist/cjs/cache/keyspace.js.map +1 -0
- package/dist/cjs/common/circuit-breaker.js +74 -0
- package/dist/cjs/common/circuit-breaker.js.map +1 -0
- package/dist/cjs/common/index.js +4 -0
- package/dist/cjs/common/index.js.map +1 -1
- package/dist/cjs/common/internal-client.js +177 -0
- package/dist/cjs/common/internal-client.js.map +1 -0
- package/dist/cjs/common/internal-constants.js +12 -0
- package/dist/cjs/common/internal-constants.js.map +1 -0
- package/dist/cjs/common/internal-service-auth.js +160 -0
- package/dist/cjs/common/internal-service-auth.js.map +1 -0
- package/dist/cjs/common/money.js +215 -0
- package/dist/cjs/common/money.js.map +1 -0
- package/dist/cjs/common/phone.js +41 -22
- package/dist/cjs/common/phone.js.map +1 -1
- package/dist/cjs/common/time.js +28 -2
- package/dist/cjs/common/time.js.map +1 -1
- package/dist/cjs/config/index.js +139 -19
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/contracts/notification-dispatch.js +1 -1
- package/dist/cjs/database/db.js +37 -0
- package/dist/cjs/database/db.js.map +1 -0
- package/dist/cjs/database/index.js +22 -51
- package/dist/cjs/database/index.js.map +1 -1
- package/dist/cjs/database/pagination.js +88 -0
- package/dist/cjs/database/pagination.js.map +1 -0
- package/dist/cjs/database/pool.js +60 -0
- package/dist/cjs/database/pool.js.map +1 -0
- package/dist/cjs/database/scoped-repository.js +132 -0
- package/dist/cjs/database/scoped-repository.js.map +1 -0
- package/dist/cjs/database/scopes.js +166 -0
- package/dist/cjs/database/scopes.js.map +1 -0
- package/dist/cjs/database/table-names.js +14 -0
- package/dist/cjs/database/table-names.js.map +1 -0
- package/dist/cjs/database/tables.js +71 -0
- package/dist/cjs/database/tables.js.map +1 -0
- package/dist/cjs/express/auth.js +64 -50
- package/dist/cjs/express/auth.js.map +1 -1
- package/dist/cjs/express/index.js +2 -0
- package/dist/cjs/express/index.js.map +1 -1
- package/dist/cjs/express/internal-caller.js +52 -0
- package/dist/cjs/express/internal-caller.js.map +1 -0
- package/dist/cjs/express/step-up.js +41 -0
- package/dist/cjs/express/step-up.js.map +1 -0
- package/dist/cjs/http/index.js +3 -71
- package/dist/cjs/http/index.js.map +1 -1
- package/dist/cjs/identity/index.js +269 -67
- package/dist/cjs/identity/index.js.map +1 -1
- package/dist/cjs/identity/step-up.js +52 -0
- package/dist/cjs/identity/step-up.js.map +1 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/knex/columns.js +36 -0
- package/dist/cjs/knex/columns.js.map +1 -0
- package/dist/cjs/knex/index.js +26 -43
- package/dist/cjs/knex/index.js.map +1 -1
- package/dist/cjs/knex/migration-helpers.js +57 -0
- package/dist/cjs/knex/migration-helpers.js.map +1 -0
- package/dist/cjs/logging/redact.js +6 -11
- package/dist/cjs/logging/redact.js.map +1 -1
- package/dist/cjs/messaging/base-consumer.js +36 -0
- package/dist/cjs/messaging/base-consumer.js.map +1 -0
- package/dist/cjs/messaging/consumer.js +142 -73
- package/dist/cjs/messaging/consumer.js.map +1 -1
- package/dist/cjs/messaging/envelope.js +53 -10
- package/dist/cjs/messaging/envelope.js.map +1 -1
- package/dist/cjs/messaging/idempotency.js +95 -8
- package/dist/cjs/messaging/idempotency.js.map +1 -1
- package/dist/cjs/messaging/index.js +2 -1
- package/dist/cjs/messaging/index.js.map +1 -1
- package/dist/cjs/messaging/outbox.js +97 -55
- package/dist/cjs/messaging/outbox.js.map +1 -1
- package/dist/cjs/messaging/publisher.js +86 -38
- package/dist/cjs/messaging/publisher.js.map +1 -1
- package/dist/cjs/messaging/staff-notifier.js +4 -4
- package/dist/cjs/messaging/staff-notifier.js.map +1 -1
- package/dist/cjs/messaging/topology.js +41 -5
- package/dist/cjs/messaging/topology.js.map +1 -1
- package/dist/cjs/rate-limit/index.js +57 -6
- package/dist/cjs/rate-limit/index.js.map +1 -1
- package/dist/cjs/rate-limit/keys.js +47 -0
- package/dist/cjs/rate-limit/keys.js.map +1 -0
- package/dist/cjs/socket/index.js +127 -16
- package/dist/cjs/socket/index.js.map +1 -1
- package/dist/cjs/socket/ticket.js +124 -0
- package/dist/cjs/socket/ticket.js.map +1 -0
- package/dist/cjs/testing/index.js +23 -14
- package/dist/cjs/testing/index.js.map +1 -1
- package/dist/esm/access/index.js +9 -1
- package/dist/esm/access/index.js.map +1 -1
- package/dist/esm/approvals/index.js +127 -0
- package/dist/esm/approvals/index.js.map +1 -0
- package/dist/esm/audit/index.js +70 -0
- package/dist/esm/audit/index.js.map +1 -0
- package/dist/esm/cache/index.js +70 -13
- package/dist/esm/cache/index.js.map +1 -1
- package/dist/esm/cache/keyspace.js +95 -0
- package/dist/esm/cache/keyspace.js.map +1 -0
- package/dist/esm/common/circuit-breaker.js +70 -0
- package/dist/esm/common/circuit-breaker.js.map +1 -0
- package/dist/esm/common/index.js +4 -0
- package/dist/esm/common/index.js.map +1 -1
- package/dist/esm/common/internal-client.js +173 -0
- package/dist/esm/common/internal-client.js.map +1 -0
- package/dist/esm/common/internal-constants.js +9 -0
- package/dist/esm/common/internal-constants.js.map +1 -0
- package/dist/esm/common/internal-service-auth.js +150 -0
- package/dist/esm/common/internal-service-auth.js.map +1 -0
- package/dist/esm/common/money.js +207 -0
- package/dist/esm/common/money.js.map +1 -0
- package/dist/esm/common/phone.js +41 -22
- package/dist/esm/common/phone.js.map +1 -1
- package/dist/esm/common/time.js +27 -2
- package/dist/esm/common/time.js.map +1 -1
- package/dist/esm/config/index.js +135 -18
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/contracts/notification-dispatch.js +1 -1
- package/dist/esm/database/db.js +32 -0
- package/dist/esm/database/db.js.map +1 -0
- package/dist/esm/database/index.js +9 -45
- package/dist/esm/database/index.js.map +1 -1
- package/dist/esm/database/pagination.js +81 -0
- package/dist/esm/database/pagination.js.map +1 -0
- package/dist/esm/database/pool.js +51 -0
- package/dist/esm/database/pool.js.map +1 -0
- package/dist/esm/database/scoped-repository.js +128 -0
- package/dist/esm/database/scoped-repository.js.map +1 -0
- package/dist/esm/database/scopes.js +157 -0
- package/dist/esm/database/scopes.js.map +1 -0
- package/dist/esm/database/table-names.js +11 -0
- package/dist/esm/database/table-names.js.map +1 -0
- package/dist/esm/database/tables.js +61 -0
- package/dist/esm/database/tables.js.map +1 -0
- package/dist/esm/express/auth.js +62 -48
- package/dist/esm/express/auth.js.map +1 -1
- package/dist/esm/express/index.js +2 -0
- package/dist/esm/express/index.js.map +1 -1
- package/dist/esm/express/internal-caller.js +49 -0
- package/dist/esm/express/internal-caller.js.map +1 -0
- package/dist/esm/express/step-up.js +38 -0
- package/dist/esm/express/step-up.js.map +1 -0
- package/dist/esm/http/index.js +3 -70
- package/dist/esm/http/index.js.map +1 -1
- package/dist/esm/identity/index.js +246 -64
- package/dist/esm/identity/index.js.map +1 -1
- package/dist/esm/identity/step-up.js +47 -0
- package/dist/esm/identity/step-up.js.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/knex/columns.js +28 -0
- package/dist/esm/knex/columns.js.map +1 -0
- package/dist/esm/knex/index.js +8 -34
- package/dist/esm/knex/index.js.map +1 -1
- package/dist/esm/knex/migration-helpers.js +51 -0
- package/dist/esm/knex/migration-helpers.js.map +1 -0
- package/dist/esm/logging/redact.js +3 -7
- package/dist/esm/logging/redact.js.map +1 -1
- package/dist/esm/messaging/base-consumer.js +32 -0
- package/dist/esm/messaging/base-consumer.js.map +1 -0
- package/dist/esm/messaging/consumer.js +139 -71
- package/dist/esm/messaging/consumer.js.map +1 -1
- package/dist/esm/messaging/envelope.js +50 -10
- package/dist/esm/messaging/envelope.js.map +1 -1
- package/dist/esm/messaging/idempotency.js +91 -7
- package/dist/esm/messaging/idempotency.js.map +1 -1
- package/dist/esm/messaging/index.js +2 -1
- package/dist/esm/messaging/index.js.map +1 -1
- package/dist/esm/messaging/outbox.js +97 -54
- package/dist/esm/messaging/outbox.js.map +1 -1
- package/dist/esm/messaging/publisher.js +86 -38
- package/dist/esm/messaging/publisher.js.map +1 -1
- package/dist/esm/messaging/staff-notifier.js +4 -4
- package/dist/esm/messaging/staff-notifier.js.map +1 -1
- package/dist/esm/messaging/topology.js +39 -4
- package/dist/esm/messaging/topology.js.map +1 -1
- package/dist/esm/rate-limit/index.js +41 -6
- package/dist/esm/rate-limit/index.js.map +1 -1
- package/dist/esm/rate-limit/keys.js +43 -0
- package/dist/esm/rate-limit/keys.js.map +1 -0
- package/dist/esm/socket/index.js +112 -17
- package/dist/esm/socket/index.js.map +1 -1
- package/dist/esm/socket/ticket.js +117 -0
- package/dist/esm/socket/ticket.js.map +1 -0
- package/dist/esm/testing/index.js +24 -15
- package/dist/esm/testing/index.js.map +1 -1
- package/dist/types/access/index.d.ts +9 -3
- package/dist/types/access/index.d.ts.map +1 -1
- package/dist/types/approvals/index.d.ts +99 -0
- package/dist/types/approvals/index.d.ts.map +1 -0
- package/dist/types/audit/index.d.ts +82 -0
- package/dist/types/audit/index.d.ts.map +1 -0
- package/dist/types/cache/index.d.ts +64 -14
- package/dist/types/cache/index.d.ts.map +1 -1
- package/dist/types/cache/keyspace.d.ts +84 -0
- package/dist/types/cache/keyspace.d.ts.map +1 -0
- package/dist/types/common/circuit-breaker.d.ts +51 -0
- package/dist/types/common/circuit-breaker.d.ts.map +1 -0
- package/dist/types/common/index.d.ts +4 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/internal-client.d.ts +116 -0
- package/dist/types/common/internal-client.d.ts.map +1 -0
- package/dist/types/common/internal-constants.d.ts +9 -0
- package/dist/types/common/internal-constants.d.ts.map +1 -0
- package/dist/types/common/internal-service-auth.d.ts +102 -0
- package/dist/types/common/internal-service-auth.d.ts.map +1 -0
- package/dist/types/common/money.d.ts +104 -0
- package/dist/types/common/money.d.ts.map +1 -0
- package/dist/types/common/phone.d.ts +25 -15
- package/dist/types/common/phone.d.ts.map +1 -1
- package/dist/types/common/time.d.ts +20 -2
- package/dist/types/common/time.d.ts.map +1 -1
- package/dist/types/config/index.d.ts +251 -27
- package/dist/types/config/index.d.ts.map +1 -1
- package/dist/types/contracts/notification-dispatch.d.ts +13 -13
- package/dist/types/database/db.d.ts +46 -0
- package/dist/types/database/db.d.ts.map +1 -0
- package/dist/types/database/index.d.ts +9 -22
- package/dist/types/database/index.d.ts.map +1 -1
- package/dist/types/database/pagination.d.ts +68 -0
- package/dist/types/database/pagination.d.ts.map +1 -0
- package/dist/types/database/pool.d.ts +39 -0
- package/dist/types/database/pool.d.ts.map +1 -0
- package/dist/types/database/scoped-repository.d.ts +92 -0
- package/dist/types/database/scoped-repository.d.ts.map +1 -0
- package/dist/types/database/scopes.d.ts +159 -0
- package/dist/types/database/scopes.d.ts.map +1 -0
- package/dist/types/database/table-names.d.ts +13 -0
- package/dist/types/database/table-names.d.ts.map +1 -0
- package/dist/types/database/tables.d.ts +748 -0
- package/dist/types/database/tables.d.ts.map +1 -0
- package/dist/types/express/auth.d.ts +40 -24
- package/dist/types/express/auth.d.ts.map +1 -1
- package/dist/types/express/index.d.ts +2 -0
- package/dist/types/express/index.d.ts.map +1 -1
- package/dist/types/express/internal-caller.d.ts +53 -0
- package/dist/types/express/internal-caller.d.ts.map +1 -0
- package/dist/types/express/step-up.d.ts +38 -0
- package/dist/types/express/step-up.d.ts.map +1 -0
- package/dist/types/http/index.d.ts +3 -31
- package/dist/types/http/index.d.ts.map +1 -1
- package/dist/types/identity/index.d.ts +159 -21
- package/dist/types/identity/index.d.ts.map +1 -1
- package/dist/types/identity/step-up.d.ts +41 -0
- package/dist/types/identity/step-up.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/knex/columns.d.ts +15 -0
- package/dist/types/knex/columns.d.ts.map +1 -0
- package/dist/types/knex/index.d.ts +3 -11
- package/dist/types/knex/index.d.ts.map +1 -1
- package/dist/types/knex/migration-helpers.d.ts +28 -0
- package/dist/types/knex/migration-helpers.d.ts.map +1 -0
- package/dist/types/logging/redact.d.ts +2 -2
- package/dist/types/logging/redact.d.ts.map +1 -1
- package/dist/types/messaging/base-consumer.d.ts +33 -0
- package/dist/types/messaging/base-consumer.d.ts.map +1 -0
- package/dist/types/messaging/consumer.d.ts +59 -21
- package/dist/types/messaging/consumer.d.ts.map +1 -1
- package/dist/types/messaging/envelope.d.ts +50 -11
- package/dist/types/messaging/envelope.d.ts.map +1 -1
- package/dist/types/messaging/idempotency.d.ts +60 -3
- package/dist/types/messaging/idempotency.d.ts.map +1 -1
- package/dist/types/messaging/index.d.ts +7 -0
- package/dist/types/messaging/index.d.ts.map +1 -1
- package/dist/types/messaging/outbox.d.ts +62 -22
- package/dist/types/messaging/outbox.d.ts.map +1 -1
- package/dist/types/messaging/publisher.d.ts +54 -21
- package/dist/types/messaging/publisher.d.ts.map +1 -1
- package/dist/types/messaging/staff-notifier.d.ts +9 -9
- package/dist/types/messaging/staff-notifier.d.ts.map +1 -1
- package/dist/types/messaging/staff-recipients.d.ts +1 -1
- package/dist/types/messaging/topology.d.ts +40 -8
- package/dist/types/messaging/topology.d.ts.map +1 -1
- package/dist/types/rate-limit/index.d.ts +40 -4
- package/dist/types/rate-limit/index.d.ts.map +1 -1
- package/dist/types/rate-limit/keys.d.ts +52 -0
- package/dist/types/rate-limit/keys.d.ts.map +1 -0
- package/dist/types/socket/index.d.ts +86 -2
- package/dist/types/socket/index.d.ts.map +1 -1
- package/dist/types/socket/ticket.d.ts +90 -0
- package/dist/types/socket/ticket.d.ts.map +1 -0
- package/dist/types/testing/index.d.ts +31 -10
- package/dist/types/testing/index.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +26 -14
- package/dist/types/types/index.d.ts.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { DEFAULT_OUTBOX_TABLE, DEFAULT_PROCESSED_EVENTS_TABLE, DEFAULT_SCHEDULER_RUNS_TABLE, OUTBOX_EXCHANGES, SCHEDULER_RUN_STATUSES } from './table-names.js';
|
|
3
|
+
import { char, datetime, index, int, json, mysqlEnum, mysqlTable, uniqueIndex, varchar } from 'drizzle-orm/mysql-core';
|
|
4
|
+
/**
|
|
5
|
+
* Drizzle definitions of the infrastructure tables every service may own. Each matches the Knex
|
|
6
|
+
* helper of the same name in `@heavybit/shared-lib/knex` (`outboxColumns`, `processedEventsColumns`,
|
|
7
|
+
* `schedulerRunsColumns`); a test keeps the two in step. Pass a custom name when a service needs one.
|
|
8
|
+
*/
|
|
9
|
+
const ms = (name) => datetime(name, { mode: 'date', fsp: 3 });
|
|
10
|
+
const createdNow = (name) => ms(name).notNull().default(sql `CURRENT_TIMESTAMP(3)`);
|
|
11
|
+
export { DEFAULT_OUTBOX_TABLE, DEFAULT_PROCESSED_EVENTS_TABLE, DEFAULT_SCHEDULER_RUNS_TABLE, OUTBOX_EXCHANGES, SCHEDULER_RUN_STATUSES };
|
|
12
|
+
/** Transactional outbox (`event_outbox` by default): domain change and message in one transaction. */
|
|
13
|
+
export function outboxTable(name = DEFAULT_OUTBOX_TABLE) {
|
|
14
|
+
return mysqlTable(name, {
|
|
15
|
+
id: char('id', { length: 26 }).primaryKey(),
|
|
16
|
+
routingKey: varchar('routing_key', { length: 191 }).notNull(),
|
|
17
|
+
/** Logical exchange: `events` (topic fan-out) or `commands` (direct, one owner). */
|
|
18
|
+
exchange: mysqlEnum('exchange', OUTBOX_EXCHANGES).notNull().default('events'),
|
|
19
|
+
payload: json('payload').notNull(),
|
|
20
|
+
/** Correlation id of the request that staged the row; the relay sends it as `x-correlation-id`. */
|
|
21
|
+
correlationId: varchar('correlation_id', { length: 64 }),
|
|
22
|
+
attempts: int('attempts', { unsigned: true }).notNull().default(0),
|
|
23
|
+
lastError: varchar('last_error', { length: 500 }),
|
|
24
|
+
createdAt: createdNow('created_at'),
|
|
25
|
+
publishedAt: ms('published_at'),
|
|
26
|
+
}, (t) => [index(`${name}_published_at_id_index`).on(t.publishedAt, t.id)]);
|
|
27
|
+
}
|
|
28
|
+
/** Consumer idempotency ledger: one row per (consumer, event id). */
|
|
29
|
+
export function processedEventsTable(name = DEFAULT_PROCESSED_EVENTS_TABLE) {
|
|
30
|
+
return mysqlTable(name, {
|
|
31
|
+
id: char('id', { length: 26 }).primaryKey(),
|
|
32
|
+
consumer: varchar('consumer', { length: 191 }).notNull(),
|
|
33
|
+
eventId: varchar('event_id', { length: 64 }).notNull(),
|
|
34
|
+
processedAt: createdNow('processed_at'),
|
|
35
|
+
}, (t) => [
|
|
36
|
+
uniqueIndex(`${name}_consumer_event_id_unique`).on(t.consumer, t.eventId),
|
|
37
|
+
index(`${name}_processed_at_index`).on(t.processedAt),
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
/** Scheduled-job runs: one row per (job, run key), so a run is never executed twice. */
|
|
41
|
+
export function schedulerRunsTable(name = DEFAULT_SCHEDULER_RUNS_TABLE) {
|
|
42
|
+
return mysqlTable(name, {
|
|
43
|
+
id: char('id', { length: 26 }).primaryKey(),
|
|
44
|
+
job: varchar('job', { length: 191 }).notNull(),
|
|
45
|
+
runKey: varchar('run_key', { length: 191 }).notNull(),
|
|
46
|
+
status: mysqlEnum('status', SCHEDULER_RUN_STATUSES).notNull().default('running'),
|
|
47
|
+
error: varchar('error', { length: 500 }),
|
|
48
|
+
startedAt: createdNow('started_at'),
|
|
49
|
+
finishedAt: ms('finished_at'),
|
|
50
|
+
}, (t) => [
|
|
51
|
+
uniqueIndex(`${name}_job_run_key_unique`).on(t.job, t.runKey),
|
|
52
|
+
index(`${name}_job_started_at_index`).on(t.job, t.startedAt),
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
/** `event_outbox` with the default name. */
|
|
56
|
+
export const outbox = outboxTable();
|
|
57
|
+
/** `processed_events` with the default name. */
|
|
58
|
+
export const processedEvents = processedEventsTable();
|
|
59
|
+
/** `scheduler_runs` with the default name. */
|
|
60
|
+
export const schedulerRuns = schedulerRunsTable();
|
|
61
|
+
//# sourceMappingURL=tables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.js","sourceRoot":"","sources":["../../../src/database/tables.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/express/auth.js
CHANGED
|
@@ -1,57 +1,77 @@
|
|
|
1
1
|
import { hasAllPermissions, hasPermission, hasRole } from '../access/index.js';
|
|
2
|
-
import { safeEqual } from '../common/crypto.js';
|
|
3
2
|
import { errorBody } from '../common/response.js';
|
|
4
3
|
import { correlationId } from './middleware.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return 'ok';
|
|
11
|
-
}
|
|
12
|
-
if (result.status === 'invalid') {
|
|
13
|
-
options.logger?.warn('Rejected forged or stale gateway identity', { reason: result.reason, path: req.path });
|
|
14
|
-
return 'invalid';
|
|
15
|
-
}
|
|
16
|
-
return 'anonymous';
|
|
4
|
+
import { updateRequestContext } from '../context/index.js';
|
|
5
|
+
import { assertGatewaySecret, canonicalPath, verifyGatewayIdentity, } from '../identity/index.js';
|
|
6
|
+
/** Collapses the user's scope id lists into the request context's `key → id` map (lists are comma-joined). */
|
|
7
|
+
function contextScopes(user) {
|
|
8
|
+
return Object.fromEntries(Object.entries(user.scopes).filter(([, ids]) => ids.length).map(([key, ids]) => [key, ids.join(',')]));
|
|
17
9
|
}
|
|
10
|
+
function createResolver(options) {
|
|
11
|
+
const secret = assertGatewaySecret(options.secret ?? process.env.GATEWAY_SIGNING_SECRET);
|
|
12
|
+
const reject = (req, reason) => {
|
|
13
|
+
const event = { reason, method: req.method, path: canonicalPath(req.originalUrl), requestId: correlationId(req) };
|
|
14
|
+
options.logger?.warn('Rejected gateway identity', { reason, method: event.method, path: event.path });
|
|
15
|
+
if (!options.onRejected)
|
|
16
|
+
return;
|
|
17
|
+
Promise.resolve()
|
|
18
|
+
.then(() => options.onRejected?.(event, req))
|
|
19
|
+
.catch((error) => options.logger?.error('Identity onRejected hook failed', { error: error.message }));
|
|
20
|
+
};
|
|
21
|
+
return (req) => {
|
|
22
|
+
const result = verifyGatewayIdentity({
|
|
23
|
+
headers: req.headers, method: req.method, path: req.originalUrl, secret, protocol: options.protocol,
|
|
24
|
+
maxAgeMs: options.maxAgeMs, maxFutureMs: options.maxFutureMs,
|
|
25
|
+
});
|
|
26
|
+
if (result.status === 'authenticated') {
|
|
27
|
+
req.user = result.user;
|
|
28
|
+
updateRequestContext({ userId: result.user.id, scopes: contextScopes(result.user) });
|
|
29
|
+
return 'ok';
|
|
30
|
+
}
|
|
31
|
+
if (result.status === 'invalid')
|
|
32
|
+
reject(req, result.reason);
|
|
33
|
+
return result.status === 'invalid' ? 'invalid' : 'anonymous';
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const unauthenticated = (req, res, message = 'Authentication required') => res.status(401).json(errorBody('UNAUTHENTICATED', message, { requestId: correlationId(req) }));
|
|
18
37
|
/**
|
|
19
|
-
* Requires a valid gateway-signed identity and sets `req.user`; 401 UNAUTHENTICATED (with `requestId`)
|
|
38
|
+
* Requires a valid gateway-signed (v2) identity and sets `req.user`; 401 UNAUTHENTICATED (with `requestId`)
|
|
39
|
+
* otherwise. Also fills the request context's `userId` and `scopes` so log lines carry them.
|
|
40
|
+
* Throws at startup when the secret is missing or shorter than 32 characters.
|
|
20
41
|
*
|
|
21
|
-
* @param options
|
|
42
|
+
* @param options Protocol, secret, replay window, logger and rejection hook.
|
|
22
43
|
* @returns Express middleware.
|
|
23
|
-
* @example router.use(authenticateFromGateway({
|
|
44
|
+
* @example router.use(authenticateFromGateway({ protocol, logger, onRejected: publishSecurityEvent }));
|
|
24
45
|
*/
|
|
25
46
|
export function authenticateFromGateway(options) {
|
|
47
|
+
const resolve = createResolver(options);
|
|
26
48
|
return (req, res, next) => {
|
|
27
|
-
if (
|
|
49
|
+
if (resolve(req) === 'ok')
|
|
28
50
|
return next();
|
|
29
|
-
|
|
51
|
+
unauthenticated(req, res);
|
|
30
52
|
};
|
|
31
53
|
}
|
|
32
54
|
/**
|
|
33
|
-
* Attaches `req.user` when a valid identity is present and continues anonymously when none is sent
|
|
34
|
-
* A forged or stale identity is still rejected with 401, never
|
|
55
|
+
* Attaches `req.user` when a valid identity is present and continues anonymously when none is sent,
|
|
56
|
+
* for public routes that personalise. A forged or stale identity is still rejected with 401, never
|
|
57
|
+
* downgraded to anonymous.
|
|
35
58
|
*
|
|
36
|
-
* @param options
|
|
59
|
+
* @param options Same as {@link authenticateFromGateway}.
|
|
37
60
|
* @returns Express middleware.
|
|
38
|
-
* @example router.get('/
|
|
61
|
+
* @example router.get('/items', optionalAuthFromGateway({ protocol }), controller.list);
|
|
39
62
|
*/
|
|
40
63
|
export function optionalAuthFromGateway(options) {
|
|
64
|
+
const resolve = createResolver(options);
|
|
41
65
|
return (req, res, next) => {
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
66
|
+
if (resolve(req) === 'invalid')
|
|
67
|
+
return unauthenticated(req, res, 'Invalid identity');
|
|
46
68
|
next();
|
|
47
69
|
};
|
|
48
70
|
}
|
|
49
71
|
function guard(check, message = 'You do not have permission to perform this action') {
|
|
50
72
|
return (req, res, next) => {
|
|
51
|
-
if (!req.user)
|
|
52
|
-
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
73
|
+
if (!req.user)
|
|
74
|
+
return unauthenticated(req, res);
|
|
55
75
|
if (!check(req.user)) {
|
|
56
76
|
res.status(403).json(errorBody('FORBIDDEN', message, { requestId: correlationId(req) }));
|
|
57
77
|
return;
|
|
@@ -61,36 +81,30 @@ function guard(check, message = 'You do not have permission to perform this acti
|
|
|
61
81
|
}
|
|
62
82
|
/**
|
|
63
83
|
* Allows callers holding any of `permissions` (401 without identity, 403 FORBIDDEN otherwise).
|
|
84
|
+
* A super admin passes every permission guard.
|
|
64
85
|
*
|
|
65
86
|
* @example router.post('/refunds', requirePermission('refunds.create'), controller.create);
|
|
66
87
|
*/
|
|
67
88
|
export const requirePermission = (...permissions) => guard((u) => hasPermission(u, ...permissions));
|
|
68
89
|
/**
|
|
69
|
-
* Allows callers holding every one of `permissions`.
|
|
90
|
+
* Allows callers holding every one of `permissions`. Accepts a list or rest arguments.
|
|
70
91
|
*
|
|
71
92
|
* @example router.delete('/x/:id', requireAllPermissions('x.read', 'x.delete'), controller.remove);
|
|
72
93
|
*/
|
|
73
|
-
export const requireAllPermissions = (...permissions) =>
|
|
94
|
+
export const requireAllPermissions = (...permissions) => {
|
|
95
|
+
const required = permissions.flat();
|
|
96
|
+
return guard((u) => hasAllPermissions(u, ...required));
|
|
97
|
+
};
|
|
74
98
|
/**
|
|
75
|
-
* Allows callers
|
|
99
|
+
* Allows callers whose session passed the one-time-code (second factor) check; 403 FORBIDDEN otherwise.
|
|
76
100
|
*
|
|
77
|
-
* @example router.
|
|
101
|
+
* @example router.post('/security/settings', requireOtpVerified(), controller.update);
|
|
78
102
|
*/
|
|
79
|
-
export const
|
|
103
|
+
export const requireOtpVerified = () => guard((u) => u.otpVerified === true, 'One-time code verification is required');
|
|
80
104
|
/**
|
|
81
|
-
*
|
|
82
|
-
* 401 UNAUTHENTICATED with `requestId` otherwise.
|
|
105
|
+
* Allows callers with any of `roles`. Prefer permissions; roles are for coarse gates only.
|
|
83
106
|
*
|
|
84
|
-
* @
|
|
85
|
-
* @returns Express middleware.
|
|
86
|
-
* @example router.use('/internal', requireInternalServiceSecret(config.internalSecret));
|
|
107
|
+
* @example router.use('/ops', requireRole('platform_admin'));
|
|
87
108
|
*/
|
|
88
|
-
export
|
|
89
|
-
return (req, res, next) => {
|
|
90
|
-
const provided = req.headers[CONTEXT_HEADERS.INTERNAL_SECRET];
|
|
91
|
-
if (typeof provided === 'string' && secret && safeEqual(provided, secret))
|
|
92
|
-
return next();
|
|
93
|
-
res.status(401).json(errorBody('UNAUTHENTICATED', 'Invalid internal service credentials', { requestId: correlationId(req) }));
|
|
94
|
-
};
|
|
95
|
-
}
|
|
109
|
+
export const requireRole = (...roles) => guard((u) => hasRole(u, ...roles), 'Insufficient role');
|
|
96
110
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/express/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/express/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,mBAAmB,EAAE,aAAa,EAAE,qBAAqB,GAC1D,MAAM,sBAAsB,CAAC;AAuC9B,8GAA8G;AAC9G,SAAS,aAAa,CAAC,IAAc;IACnC,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACnI,CAAC;AAED,SAAS,cAAc,CAAC,OAA2B;IACjD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACzF,MAAM,MAAM,GAAG,CAAC,GAAY,EAAE,MAAyB,EAAE,EAAE;QACzD,MAAM,KAAK,GAA0B,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACzI,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,2BAA2B,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACtG,IAAI,CAAC,OAAO,CAAC,UAAU;YAAE,OAAO;QAChC,OAAO,CAAC,OAAO,EAAE;aACd,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;aAC5C,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,iCAAiC,EAAE,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC9H,CAAC,CAAC;IACF,OAAO,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,qBAAqB,CAAC;YACnC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ;YACnG,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW;SAC7D,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;YACtC,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACvB,oBAAoB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACrF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;YAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IAC/D,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,eAAe,GAAG,CAAC,GAAY,EAAE,GAAa,EAAE,OAAO,GAAG,yBAAyB,EAAE,EAAE,CAC3F,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAEjG;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA2B;IACjE,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI;YAAE,OAAO,IAAI,EAAE,CAAC;QACzC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC5B,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA2B;IACjE,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,OAAO,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACrF,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,KAAkC,EAAE,OAAO,GAAG,mDAAmD;IAC9G,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,IAAI,CAAC,GAAG,CAAC,IAAI;YAAE,OAAO,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAG,WAAqB,EAAkB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC;AAE9H;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,WAAqC,EAAkB,EAAE;IAChG,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;IACpC,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAmB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,IAAI,EAAE,wCAAwC,CAAC,CAAC;AAEvI;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAG,KAAe,EAAkB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/express/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/express/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { errorBody } from '../common/response.js';
|
|
2
|
+
import { internalServiceAuthConfigFromEnv, verifyInternalCaller } from '../common/internal-service-auth.js';
|
|
3
|
+
import { canonicalPath } from '../identity/index.js';
|
|
4
|
+
import { correlationId } from './middleware.js';
|
|
5
|
+
const isUnderPrefix = (path, prefix) => path === prefix || path.startsWith(`${prefix}/`);
|
|
6
|
+
/**
|
|
7
|
+
* Builds the internal-caller guard for a service, bound to its internal path prefix and accepted credentials.
|
|
8
|
+
*
|
|
9
|
+
* @param options Path prefix, credentials and logger.
|
|
10
|
+
* @returns `{ requireInternalCaller }`.
|
|
11
|
+
* @throws Error when the prefix is not an absolute path; ConfigError when env credentials are malformed.
|
|
12
|
+
* @example
|
|
13
|
+
* const { requireInternalCaller } = createInternalCallerGuards({ pathPrefix: '/internal', logger });
|
|
14
|
+
* router.get('/internal/records/:id', requireInternalCaller(['alpha-service']), controller.get);
|
|
15
|
+
*/
|
|
16
|
+
export function createInternalCallerGuards(options) {
|
|
17
|
+
const prefix = options.pathPrefix.replace(/\/+$/, '');
|
|
18
|
+
if (!prefix.startsWith('/'))
|
|
19
|
+
throw new Error('createInternalCallerGuards: pathPrefix must be an absolute path such as /internal');
|
|
20
|
+
const credentials = options.credentials ?? internalServiceAuthConfigFromEnv().credentials;
|
|
21
|
+
return {
|
|
22
|
+
requireInternalCaller(allowed) {
|
|
23
|
+
if (!allowed.length)
|
|
24
|
+
throw new Error('requireInternalCaller: list at least one caller');
|
|
25
|
+
const callers = [...allowed];
|
|
26
|
+
return (req, res, next) => {
|
|
27
|
+
const path = canonicalPath(req.originalUrl);
|
|
28
|
+
const requestId = correlationId(req);
|
|
29
|
+
if (!isUnderPrefix(path, prefix)) {
|
|
30
|
+
options.logger?.warn('Internal guard outside the internal prefix', { path });
|
|
31
|
+
res.status(403).json(errorBody('FORBIDDEN', 'Not an internal route', { requestId }));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const verdict = verifyInternalCaller({ headers: req.headers, credentials, allowed: callers });
|
|
35
|
+
if (verdict.ok) {
|
|
36
|
+
req.internalCaller = verdict.caller;
|
|
37
|
+
return next();
|
|
38
|
+
}
|
|
39
|
+
options.logger?.warn('Rejected internal caller', { reason: verdict.reason, caller: verdict.caller, path });
|
|
40
|
+
if (verdict.status === 401) {
|
|
41
|
+
res.status(401).json(errorBody('UNAUTHENTICATED', 'Invalid internal service credentials', { requestId }));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
res.status(403).json(errorBody('FORBIDDEN', 'This service may not call this route', { requestId }));
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=internal-caller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-caller.js","sourceRoot":"","sources":["../../../src/express/internal-caller.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,gCAAgC,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC5G,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAkChD,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,MAAc,EAAW,EAAE,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;AAElH;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAmC;IAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mFAAmF,CAAC,CAAC;IAClI,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,gCAAgC,EAAE,CAAC,WAAW,CAAC;IAE1F,OAAO;QACL,qBAAqB,CAAC,OAAO;YAC3B,IAAI,CAAC,OAAO,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACxF,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;YAC7B,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;gBACzD,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBAC5C,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;gBACrC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;oBACjC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,4CAA4C,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC7E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,uBAAuB,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;oBACrF,OAAO;gBACT,CAAC;gBACD,MAAM,OAAO,GAAG,oBAAoB,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC9F,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;oBACf,GAAG,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;oBACpC,OAAO,IAAI,EAAE,CAAC;gBAChB,CAAC;gBACD,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3G,IAAI,OAAO,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;oBAC3B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,sCAAsC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;oBAC1G,OAAO;gBACT,CAAC;gBACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,sCAAsC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YACtG,CAAC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { errorBody } from '../common/response.js';
|
|
2
|
+
import { DEFAULT_STEP_UP_WINDOW_MS, STEP_UP_REQUIRED_CODE, stepUpStatus, } from '../identity/step-up.js';
|
|
3
|
+
import { correlationId } from './middleware.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the step-up guard for an app's catalogue. Must run after `authenticateFromGateway`, which sets
|
|
6
|
+
* `req.user.stepUpAt` only from the signed `x-step-up-at` header. Single-use proofs are consumed by the
|
|
7
|
+
* gateway; the service sees only the verified time. Throws at startup for an unknown scope or a bad window.
|
|
8
|
+
*
|
|
9
|
+
* @param options Catalogue, freshness window and clock.
|
|
10
|
+
* @returns `requireStepUp(scope, route?)` and `declaredStepUpRoutes()`.
|
|
11
|
+
* @example
|
|
12
|
+
* const { requireStepUp } = createStepUpGuards({ catalogue: stepUpScopes });
|
|
13
|
+
* router.post('/refunds/:id', requirePermission('refunds.create'), requireStepUp('refund-large', 'POST /refunds/:id'), controller.refund);
|
|
14
|
+
*/
|
|
15
|
+
export function createStepUpGuards(options) {
|
|
16
|
+
const windowMs = options.windowMs ?? DEFAULT_STEP_UP_WINDOW_MS;
|
|
17
|
+
if (!Number.isFinite(windowMs) || windowMs <= 0)
|
|
18
|
+
throw new Error(`createStepUpGuards: windowMs must be positive (got ${windowMs})`);
|
|
19
|
+
const now = options.now ?? Date.now;
|
|
20
|
+
const declared = [];
|
|
21
|
+
const requireStepUp = (scope, route) => {
|
|
22
|
+
const rules = options.catalogue[scope];
|
|
23
|
+
if (!rules)
|
|
24
|
+
throw new Error(`requireStepUp: unknown step-up scope "${scope}"`);
|
|
25
|
+
declared.push({ scope, singleUse: rules.singleUse === true, ...(route ? { route } : {}) });
|
|
26
|
+
return (req, res, next) => {
|
|
27
|
+
if (!req.user) {
|
|
28
|
+
res.status(401).json(errorBody('UNAUTHENTICATED', 'Authentication required', { requestId: correlationId(req) }));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (stepUpStatus(req.user.stepUpAt, windowMs, now()) === 'fresh')
|
|
32
|
+
return next();
|
|
33
|
+
res.status(403).json({ ...errorBody(STEP_UP_REQUIRED_CODE, 'Please confirm it is you to continue', { requestId: correlationId(req) }), scope });
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
return { requireStepUp, declaredStepUpRoutes: () => declared.map((entry) => ({ ...entry })) };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=step-up.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up.js","sourceRoot":"","sources":["../../../src/express/step-up.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EACL,yBAAyB,EAAE,qBAAqB,EAAE,YAAY,GAC/D,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA4BhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAmB,OAA8B;IACjF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,yBAAyB,CAAC;IAC/D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,QAAQ,GAAG,CAAC,CAAC;IACpI,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,QAAQ,GAAkC,EAAE,CAAC;IAEnD,MAAM,aAAa,GAAG,CAAC,KAAQ,EAAE,KAAc,EAAkB,EAAE;QACjE,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,KAAK,GAAG,CAAC,CAAC;QAC/E,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3F,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;YACzD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,yBAAyB,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;gBACjH,OAAO;YACT,CAAC;YACD,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO;gBAAE,OAAO,IAAI,EAAE,CAAC;YAChF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC,qBAAqB,EAAE,sCAAsC,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAClJ,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;AAChG,CAAC"}
|
package/dist/esm/http/index.js
CHANGED
|
@@ -1,73 +1,6 @@
|
|
|
1
|
-
import { AppError, ServiceUnavailableError } from '../common/errors.js';
|
|
2
|
-
import { sleep } from '../common/async.js';
|
|
3
|
-
import { CONTEXT_HEADERS } from '../identity/index.js';
|
|
4
|
-
import { silentLogger } from '../logging/index.js';
|
|
5
|
-
export * from './safe-fetch.js';
|
|
6
|
-
const RETRYABLE_STATUS = new Set([502, 503, 504]);
|
|
7
|
-
const IDEMPOTENT = new Set(['GET', 'PUT', 'DELETE']);
|
|
8
1
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* and timeouts into 503s.
|
|
2
|
+
* Outbound HTTP helpers. Service-to-service calls use `createInternalClient` (root export); calls to
|
|
3
|
+
* tenant-supplied URLs go through the SSRF-guarded {@link createSafeFetcher}.
|
|
12
4
|
*/
|
|
13
|
-
export
|
|
14
|
-
const logger = options.logger ?? silentLogger;
|
|
15
|
-
const base = options.baseUrl.replace(/\/+$/, '');
|
|
16
|
-
async function request(method, path, opts = {}) {
|
|
17
|
-
const url = new URL(base + path);
|
|
18
|
-
for (const [key, value] of Object.entries(opts.query ?? {}))
|
|
19
|
-
if (value !== undefined)
|
|
20
|
-
url.searchParams.set(key, String(value));
|
|
21
|
-
const retries = IDEMPOTENT.has(method) ? options.retries ?? 2 : 0;
|
|
22
|
-
for (let attempt = 0;; attempt += 1) {
|
|
23
|
-
const controller = new AbortController();
|
|
24
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? options.timeoutMs ?? 5000);
|
|
25
|
-
try {
|
|
26
|
-
const response = await fetch(url, {
|
|
27
|
-
method,
|
|
28
|
-
signal: controller.signal,
|
|
29
|
-
headers: {
|
|
30
|
-
accept: 'application/json',
|
|
31
|
-
...(opts.body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
32
|
-
[CONTEXT_HEADERS.INTERNAL_SECRET]: options.internalSecret,
|
|
33
|
-
[CONTEXT_HEADERS.CALLER_SERVICE]: options.callerService,
|
|
34
|
-
...(opts.correlationId ? { [CONTEXT_HEADERS.CORRELATION_ID]: opts.correlationId } : {}),
|
|
35
|
-
...opts.headers,
|
|
36
|
-
},
|
|
37
|
-
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
38
|
-
});
|
|
39
|
-
if (RETRYABLE_STATUS.has(response.status) && attempt < retries) {
|
|
40
|
-
await sleep(200 * 2 ** attempt);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
const text = await response.text();
|
|
44
|
-
const payload = text ? JSON.parse(text) : { success: response.ok, message: '' };
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
throw new AppError(response.status, payload.message || `Upstream error ${response.status}`, { code: payload.code, errors: payload.errors });
|
|
47
|
-
}
|
|
48
|
-
return (payload.data ?? payload);
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
if (error instanceof AppError)
|
|
52
|
-
throw error;
|
|
53
|
-
if (attempt < retries) {
|
|
54
|
-
await sleep(200 * 2 ** attempt);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
logger.warn('Internal call failed', { method, url: url.pathname, error: error.message });
|
|
58
|
-
throw new ServiceUnavailableError('Upstream service unavailable', { cause: error });
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
get: (path, opts) => request('GET', path, opts),
|
|
67
|
-
post: (path, opts) => request('POST', path, opts),
|
|
68
|
-
put: (path, opts) => request('PUT', path, opts),
|
|
69
|
-
patch: (path, opts) => request('PATCH', path, opts),
|
|
70
|
-
delete: (path, opts) => request('DELETE', path, opts),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
5
|
+
export * from './safe-fetch.js';
|
|
73
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/http/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/http/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,iBAAiB,CAAC"}
|