@shipfox/api-auth 9.2.0 → 9.3.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.
Files changed (71) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +25 -8
  4. package/dist/config.d.ts +1 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +4 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/administration.d.ts +27 -0
  9. package/dist/core/administration.d.ts.map +1 -0
  10. package/dist/core/administration.js +122 -0
  11. package/dist/core/administration.js.map +1 -0
  12. package/dist/core/auth.d.ts.map +1 -1
  13. package/dist/core/auth.js +2 -5
  14. package/dist/core/auth.js.map +1 -1
  15. package/dist/core/errors.d.ts +15 -0
  16. package/dist/core/errors.d.ts.map +1 -1
  17. package/dist/core/errors.js +30 -0
  18. package/dist/core/errors.js.map +1 -1
  19. package/dist/db/admin-grants.d.ts +18 -0
  20. package/dist/db/admin-grants.d.ts.map +1 -1
  21. package/dist/db/admin-grants.js +145 -1
  22. package/dist/db/admin-grants.js.map +1 -1
  23. package/dist/db/db.d.ts +256 -0
  24. package/dist/db/db.d.ts.map +1 -1
  25. package/dist/db/db.js +2 -0
  26. package/dist/db/db.js.map +1 -1
  27. package/dist/db/schema/admin-command-results.d.ts +142 -0
  28. package/dist/db/schema/admin-command-results.d.ts.map +1 -0
  29. package/dist/db/schema/admin-command-results.js +21 -0
  30. package/dist/db/schema/admin-command-results.js.map +1 -0
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +11 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/metrics/instance.d.ts +1 -1
  36. package/dist/metrics/instance.d.ts.map +1 -1
  37. package/dist/metrics/instance.js.map +1 -1
  38. package/dist/presentation/routes/administration.d.ts +3 -0
  39. package/dist/presentation/routes/administration.d.ts.map +1 -0
  40. package/dist/presentation/routes/administration.js +186 -0
  41. package/dist/presentation/routes/administration.js.map +1 -0
  42. package/dist/presentation/routes/rate-limit.d.ts +1 -0
  43. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  44. package/dist/presentation/routes/rate-limit.js +20 -1
  45. package/dist/presentation/routes/rate-limit.js.map +1 -1
  46. package/dist/tsconfig.test.tsbuildinfo +1 -1
  47. package/drizzle/0002_burly_whizzer.sql +12 -0
  48. package/drizzle/meta/0002_snapshot.json +791 -0
  49. package/drizzle/meta/_journal.json +7 -0
  50. package/package.json +10 -9
  51. package/src/config.ts +4 -0
  52. package/src/core/administration.ts +172 -0
  53. package/src/core/auth-default-signup-policy.test.ts +65 -0
  54. package/src/core/auth.test.ts +5 -0
  55. package/src/core/auth.ts +2 -3
  56. package/src/core/errors.ts +35 -0
  57. package/src/db/admin-grants.ts +258 -1
  58. package/src/db/db.ts +2 -0
  59. package/src/db/schema/admin-command-results.ts +41 -0
  60. package/src/index.test.ts +6 -2
  61. package/src/index.ts +20 -2
  62. package/src/metrics/instance.ts +1 -1
  63. package/src/presentation/auth/refresh-cookie.test.ts +4 -0
  64. package/src/presentation/e2eRoutes/index.test.ts +1 -0
  65. package/src/presentation/routes/administration.test.ts +253 -0
  66. package/src/presentation/routes/administration.ts +195 -0
  67. package/src/presentation/routes/index.test.ts +1 -0
  68. package/src/presentation/routes/rate-limit.ts +23 -2
  69. package/test/globalSetup.ts +6 -2
  70. package/test/routes.ts +7 -1
  71. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,142 @@
1
+ import type { AdminRole } from '@shipfox/api-auth-dto';
2
+ export interface StoredAdminGrant {
3
+ id: string;
4
+ userId: string;
5
+ role: AdminRole;
6
+ revokedAt: string | null;
7
+ createdAt: string;
8
+ updatedAt: string;
9
+ }
10
+ export interface StoredAdminCommandResult {
11
+ grant: StoredAdminGrant;
12
+ }
13
+ export declare const adminCommandResults: import("drizzle-orm/pg-core").PgTableWithColumns<{
14
+ name: "admin_command_results";
15
+ schema: undefined;
16
+ columns: {
17
+ id: import("drizzle-orm/pg-core").PgColumn<{
18
+ name: "id";
19
+ tableName: "admin_command_results";
20
+ dataType: "string";
21
+ columnType: "PgUUID";
22
+ data: string;
23
+ driverParam: string;
24
+ notNull: true;
25
+ hasDefault: true;
26
+ isPrimaryKey: true;
27
+ isAutoincrement: false;
28
+ hasRuntimeDefault: false;
29
+ enumValues: undefined;
30
+ baseColumn: never;
31
+ identity: undefined;
32
+ generated: undefined;
33
+ }, {}, {}>;
34
+ actorId: import("drizzle-orm/pg-core").PgColumn<{
35
+ name: "actor_id";
36
+ tableName: "admin_command_results";
37
+ dataType: "string";
38
+ columnType: "PgUUID";
39
+ data: string;
40
+ driverParam: string;
41
+ notNull: true;
42
+ hasDefault: false;
43
+ isPrimaryKey: false;
44
+ isAutoincrement: false;
45
+ hasRuntimeDefault: false;
46
+ enumValues: undefined;
47
+ baseColumn: never;
48
+ identity: undefined;
49
+ generated: undefined;
50
+ }, {}, {}>;
51
+ idempotencyKeyFingerprint: import("drizzle-orm/pg-core").PgColumn<{
52
+ name: "idempotency_key_fingerprint";
53
+ tableName: "admin_command_results";
54
+ dataType: "string";
55
+ columnType: "PgText";
56
+ data: string;
57
+ driverParam: string;
58
+ notNull: true;
59
+ hasDefault: false;
60
+ isPrimaryKey: false;
61
+ isAutoincrement: false;
62
+ hasRuntimeDefault: false;
63
+ enumValues: [string, ...string[]];
64
+ baseColumn: never;
65
+ identity: undefined;
66
+ generated: undefined;
67
+ }, {}, {}>;
68
+ command: import("drizzle-orm/pg-core").PgColumn<{
69
+ name: "command";
70
+ tableName: "admin_command_results";
71
+ dataType: "string";
72
+ columnType: "PgText";
73
+ data: string;
74
+ driverParam: string;
75
+ notNull: true;
76
+ hasDefault: false;
77
+ isPrimaryKey: false;
78
+ isAutoincrement: false;
79
+ hasRuntimeDefault: false;
80
+ enumValues: [string, ...string[]];
81
+ baseColumn: never;
82
+ identity: undefined;
83
+ generated: undefined;
84
+ }, {}, {}>;
85
+ requestFingerprint: import("drizzle-orm/pg-core").PgColumn<{
86
+ name: "request_fingerprint";
87
+ tableName: "admin_command_results";
88
+ dataType: "string";
89
+ columnType: "PgText";
90
+ data: string;
91
+ driverParam: string;
92
+ notNull: true;
93
+ hasDefault: false;
94
+ isPrimaryKey: false;
95
+ isAutoincrement: false;
96
+ hasRuntimeDefault: false;
97
+ enumValues: [string, ...string[]];
98
+ baseColumn: never;
99
+ identity: undefined;
100
+ generated: undefined;
101
+ }, {}, {}>;
102
+ result: import("drizzle-orm/pg-core").PgColumn<{
103
+ name: "result";
104
+ tableName: "admin_command_results";
105
+ dataType: "json";
106
+ columnType: "PgJsonb";
107
+ data: StoredAdminCommandResult;
108
+ driverParam: unknown;
109
+ notNull: true;
110
+ hasDefault: false;
111
+ isPrimaryKey: false;
112
+ isAutoincrement: false;
113
+ hasRuntimeDefault: false;
114
+ enumValues: undefined;
115
+ baseColumn: never;
116
+ identity: undefined;
117
+ generated: undefined;
118
+ }, {}, {
119
+ $type: StoredAdminCommandResult;
120
+ }>;
121
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
122
+ name: "created_at";
123
+ tableName: "admin_command_results";
124
+ dataType: "date";
125
+ columnType: "PgTimestamp";
126
+ data: Date;
127
+ driverParam: string;
128
+ notNull: true;
129
+ hasDefault: true;
130
+ isPrimaryKey: false;
131
+ isAutoincrement: false;
132
+ hasRuntimeDefault: false;
133
+ enumValues: undefined;
134
+ baseColumn: never;
135
+ identity: undefined;
136
+ generated: undefined;
137
+ }, {}, {}>;
138
+ };
139
+ dialect: "pg";
140
+ }>;
141
+ export type AdminCommandResultDb = typeof adminCommandResults.$inferSelect;
142
+ //# sourceMappingURL=admin-command-results.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-command-results.d.ts","sourceRoot":"","sources":["../../../src/db/schema/admin-command-results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAMrD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmB/B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { uuidv7PrimaryKey } from '@shipfox/node-drizzle';
2
+ import { jsonb, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
3
+ import { pgTable } from './common.js';
4
+ import { users } from './users.js';
5
+ export const adminCommandResults = pgTable('admin_command_results', {
6
+ id: uuidv7PrimaryKey(),
7
+ actorId: uuid('actor_id').notNull().references(()=>users.id, {
8
+ onDelete: 'cascade'
9
+ }),
10
+ idempotencyKeyFingerprint: text('idempotency_key_fingerprint').notNull(),
11
+ command: text('command').notNull(),
12
+ requestFingerprint: text('request_fingerprint').notNull(),
13
+ result: jsonb('result').$type().notNull(),
14
+ createdAt: timestamp('created_at', {
15
+ withTimezone: true
16
+ }).notNull().defaultNow()
17
+ }, (table)=>[
18
+ uniqueIndex('auth_admin_command_results_actor_key_unique').on(table.actorId, table.idempotencyKeyFingerprint)
19
+ ]);
20
+
21
+ //# sourceMappingURL=admin-command-results.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/db/schema/admin-command-results.ts"],"sourcesContent":["import type {AdminRole} from '@shipfox/api-auth-dto';\nimport {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {jsonb, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport {pgTable} from './common.js';\nimport {users} from './users.js';\n\nexport interface StoredAdminGrant {\n id: string;\n userId: string;\n role: AdminRole;\n revokedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface StoredAdminCommandResult {\n grant: StoredAdminGrant;\n}\n\nexport const adminCommandResults = pgTable(\n 'admin_command_results',\n {\n id: uuidv7PrimaryKey(),\n actorId: uuid('actor_id')\n .notNull()\n .references(() => users.id, {onDelete: 'cascade'}),\n idempotencyKeyFingerprint: text('idempotency_key_fingerprint').notNull(),\n command: text('command').notNull(),\n requestFingerprint: text('request_fingerprint').notNull(),\n result: jsonb('result').$type<StoredAdminCommandResult>().notNull(),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('auth_admin_command_results_actor_key_unique').on(\n table.actorId,\n table.idempotencyKeyFingerprint,\n ),\n ],\n);\n\nexport type AdminCommandResultDb = typeof adminCommandResults.$inferSelect;\n"],"names":["uuidv7PrimaryKey","jsonb","text","timestamp","uniqueIndex","uuid","pgTable","users","adminCommandResults","id","actorId","notNull","references","onDelete","idempotencyKeyFingerprint","command","requestFingerprint","result","$type","createdAt","withTimezone","defaultNow","table","on"],"mappings":"AACA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAC9E,SAAQC,OAAO,QAAO,cAAc;AACpC,SAAQC,KAAK,QAAO,aAAa;AAejC,OAAO,MAAMC,sBAAsBF,QACjC,yBACA;IACEG,IAAIT;IACJU,SAASL,KAAK,YACXM,OAAO,GACPC,UAAU,CAAC,IAAML,MAAME,EAAE,EAAE;QAACI,UAAU;IAAS;IAClDC,2BAA2BZ,KAAK,+BAA+BS,OAAO;IACtEI,SAASb,KAAK,WAAWS,OAAO;IAChCK,oBAAoBd,KAAK,uBAAuBS,OAAO;IACvDM,QAAQhB,MAAM,UAAUiB,KAAK,GAA6BP,OAAO;IACjEQ,WAAWhB,UAAU,cAAc;QAACiB,cAAc;IAAI,GAAGT,OAAO,GAAGU,UAAU;AAC/E,GACA,CAACC,QAAU;QACTlB,YAAY,+CAA+CmB,EAAE,CAC3DD,MAAMZ,OAAO,EACbY,MAAMR,yBAAyB;KAElC,EACD"}
package/dist/index.d.ts CHANGED
@@ -2,13 +2,14 @@ import type { ShipfoxModule } from '@shipfox/node-module';
2
2
  import type { SignupPolicy } from '#core/ports.js';
3
3
  export type { AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims } from '@shipfox/api-auth-dto';
4
4
  export { ADMIN_ROLES, getCurrentAdminRole, hasMinimumAdminRole, highestAdminRole, requireAdminRole, revokeAdminGrant, } from '#core/admin-role.js';
5
+ export { bootstrapFirstAdminOwner, grantAdministratorRole, listAdministratorGrants, revokeAdministratorGrant, } from '#core/administration.js';
5
6
  export type { CreateSessionForUserError, CreateSessionForUserParams, CreateSessionForUserResult, ProvisionUserParams, } from '#core/auth.js';
6
7
  export { createSessionForUser, provisionUser } from '#core/auth.js';
7
8
  export type { EmailOwner, FindUserByEmailParams } from '#core/email-owner.js';
8
9
  export { findUserByEmail } from '#core/email-owner.js';
9
10
  export type { AdminGrant } from '#core/entities/admin-grant.js';
10
11
  export type { User, UserStatus } from '#core/entities/user.js';
11
- export { AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError, } from '#core/errors.js';
12
+ export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidAdminBootstrapTokenError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError, } from '#core/errors.js';
12
13
  export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken, } from '#core/job-lease-token.js';
13
14
  export type { SignupPolicy } from '#core/ports.js';
14
15
  export { issueRunnerSessionToken, verifyRunnerSessionToken, } from '#core/runner-session-token.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAGxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAcjD,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,sBAAsB,EACtB,8BAA8B,EAC9B,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,8BAA8B,EAC9B,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;IAC3F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,YAA8C,GAC/C,EAAE,uBAAuB,GAAG,aAAa,CAYzC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAGxD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAiBjD,YAAY,EAAC,SAAS,EAAE,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACpG,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,UAAU,EAAC,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC7B,sBAAsB,EACtB,8BAA8B,EAC9B,qBAAqB,EACrB,+BAA+B,EAC/B,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,kCAAkC,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,8BAA8B,EAC9B,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;IAC3F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,YAA8C,GAC/C,EAAE,uBAAuB,GAAG,aAAa,CAezC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AUTH_PASSWORD_RESET_SEND_REQUESTED, authEventSchemas } from '@shipfox/api-auth-dto';
2
+ import { administrationActionEventSchemas } from '@shipfox/api-common-dto';
2
3
  import { subscriberFactory } from '@shipfox/node-module';
3
4
  import { config } from '#config.js';
4
5
  import { createEnvironmentSignupPolicy } from '#core/signup-policy.js';
@@ -10,13 +11,19 @@ import { createLeaseTokenAuthMethod } from '#presentation/auth/lease-token-auth.
10
11
  import { createRunnerSessionAuthMethod } from '#presentation/auth/runner-session-auth.js';
11
12
  import { createAuthE2eRoutes } from '#presentation/e2eRoutes/index.js';
12
13
  import { createAuthInterModulePresentation } from '#presentation/inter-module.js';
14
+ import { administrationRoutes } from '#presentation/routes/administration.js';
13
15
  import { buildAuthRoutes } from '#presentation/routes/index.js';
14
16
  import { onPasswordResetSendRequested } from '#presentation/subscribers/index.js';
15
17
  import { passwordLoginMethods } from './login-methods.js';
18
+ const authPublisherEventSchemas = {
19
+ ...authEventSchemas,
20
+ ...administrationActionEventSchemas
21
+ };
16
22
  export { ADMIN_ROLES, getCurrentAdminRole, hasMinimumAdminRole, highestAdminRole, requireAdminRole, revokeAdminGrant } from '#core/admin-role.js';
23
+ export { bootstrapFirstAdminOwner, grantAdministratorRole, listAdministratorGrants, revokeAdministratorGrant } from '#core/administration.js';
17
24
  export { createSessionForUser, provisionUser } from '#core/auth.js';
18
25
  export { findUserByEmail } from '#core/email-owner.js';
19
- export { AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError } from '#core/errors.js';
26
+ export { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidAdminBootstrapTokenError, InvalidCredentialsError, LastAdminOwnerError, SignupNotAllowedError, UserNotFoundError } from '#core/errors.js';
20
27
  export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken } from '#core/job-lease-token.js';
21
28
  export { issueRunnerSessionToken, verifyRunnerSessionToken } from '#core/runner-session-token.js';
22
29
  export { createEnvironmentSignupPolicy, DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE } from '#core/signup-policy.js';
@@ -40,7 +47,8 @@ export function createAuthModule({ workspaces, signupPolicy = createEnvironmentS
40
47
  ],
41
48
  loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),
42
49
  routes: [
43
- buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy)
50
+ buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),
51
+ administrationRoutes
44
52
  ],
45
53
  e2eRoutes: [
46
54
  createAuthE2eRoutes(workspaces)
@@ -50,7 +58,7 @@ export function createAuthModule({ workspaces, signupPolicy = createEnvironmentS
50
58
  name: 'auth',
51
59
  table: authOutbox,
52
60
  db,
53
- eventSchemas: authEventSchemas
61
+ eventSchemas: authPublisherEventSchemas
54
62
  }
55
63
  ],
56
64
  subscribers: [
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport type {SignupPolicy} from '#core/ports.js';\nimport {createEnvironmentSignupPolicy} from '#core/signup-policy.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nexport type {AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport {\n ADMIN_ROLES,\n getCurrentAdminRole,\n hasMinimumAdminRole,\n highestAdminRole,\n requireAdminRole,\n revokeAdminGrant,\n} from '#core/admin-role.js';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {AdminGrant} from '#core/entities/admin-grant.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AdminRoleRequiredError,\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidCredentialsError,\n LastAdminOwnerError,\n SignupNotAllowedError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport type {SignupPolicy} from '#core/ports.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {\n createEnvironmentSignupPolicy,\n DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE,\n} from '#core/signup-policy.js';\nexport {\n type AuthenticatedSessionContext,\n createJwtAuthMethod,\n getAuthenticatedSessionContext,\n type RefreshSessionId,\n type UserId,\n} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport interface CreateAuthModuleOptions {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n signupPolicy?: SignupPolicy;\n}\n\nexport function createAuthModule({\n workspaces,\n signupPolicy = createEnvironmentSignupPolicy(),\n}: CreateAuthModuleOptions): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath, databaseNamespace: 'auth'},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy)],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","subscriberFactory","config","createEnvironmentSignupPolicy","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","createSessionForUser","provisionUser","findUserByEmail","AdminRoleRequiredError","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidCredentialsError","LastAdminOwnerError","SignupNotAllowedError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","getAuthenticatedSessionContext","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","signupPolicy","name","database","databaseNamespace","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAE/B,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAElC,SAAQC,6BAA6B,QAAO,yBAAyB;AACrE,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAGxD,SACEC,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAO7B,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAGrD,SACEC,sBAAsB,EACtBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,uBAAuB,EACvBC,mBAAmB,EACnBC,qBAAqB,EACrBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAElC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SACEhC,6BAA6B,EAC7BiC,kCAAkC,QAC7B,yBAAyB;AAChC,SAEE7B,mBAAmB,EACnB8B,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQ7B,0BAA0B,QAAO,yCAAyC;AAClF,SACE8B,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQhC,6BAA6B,QAAO,4CAA4C;AAExF,MAAMiC,aAAazC;AAOnB,OAAO,SAAS0C,iBAAiB,EAC/BC,UAAU,EACVC,eAAe1C,+BAA+B,EACtB;IACxB,OAAO;QACL2C,MAAM;QACNC,UAAU;YAAC3C;YAAIC;YAAgB2C,mBAAmB;QAAM;QACxDC,MAAM;YAAC1C;YAAuBC;YAA8BC;SAAgC;QAC5FyC,cAAcpC,qBAAqBZ,OAAOiD,qBAAqB;QAC/DC,QAAQ;YAACxC,gBAAgBV,OAAOiD,qBAAqB,EAAEP,YAAYC;SAAc;QACjFQ,WAAW;YAAC3C,oBAAoBkC;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAOjD;gBAAYF;gBAAIoD,cAAcxD;YAAgB;SAAE;QACnFyD,aAAa;YAACf,WAAW3C,oCAAoCc;SAA8B;QAC3F6C,0BAA0B;YAAC/C;SAAoC;IACjE;AACF"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport {administrationActionEventSchemas} from '@shipfox/api-common-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport type {SignupPolicy} from '#core/ports.js';\nimport {createEnvironmentSignupPolicy} from '#core/signup-policy.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {administrationRoutes} from '#presentation/routes/administration.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nconst authPublisherEventSchemas = {...authEventSchemas, ...administrationActionEventSchemas};\n\nexport type {AdminRole, JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport {\n ADMIN_ROLES,\n getCurrentAdminRole,\n hasMinimumAdminRole,\n highestAdminRole,\n requireAdminRole,\n revokeAdminGrant,\n} from '#core/admin-role.js';\nexport {\n bootstrapFirstAdminOwner,\n grantAdministratorRole,\n listAdministratorGrants,\n revokeAdministratorGrant,\n} from '#core/administration.js';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {AdminGrant} from '#core/entities/admin-grant.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n AdminRoleRequiredError,\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidAdminBootstrapTokenError,\n InvalidCredentialsError,\n LastAdminOwnerError,\n SignupNotAllowedError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport type {SignupPolicy} from '#core/ports.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {\n createEnvironmentSignupPolicy,\n DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE,\n} from '#core/signup-policy.js';\nexport {\n type AuthenticatedSessionContext,\n createJwtAuthMethod,\n getAuthenticatedSessionContext,\n type RefreshSessionId,\n type UserId,\n} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport interface CreateAuthModuleOptions {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n signupPolicy?: SignupPolicy;\n}\n\nexport function createAuthModule({\n workspaces,\n signupPolicy = createEnvironmentSignupPolicy(),\n}: CreateAuthModuleOptions): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath, databaseNamespace: 'auth'},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [\n buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces, signupPolicy),\n administrationRoutes,\n ],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authPublisherEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","administrationActionEventSchemas","subscriberFactory","config","createEnvironmentSignupPolicy","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","administrationRoutes","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","authPublisherEventSchemas","ADMIN_ROLES","getCurrentAdminRole","hasMinimumAdminRole","highestAdminRole","requireAdminRole","revokeAdminGrant","bootstrapFirstAdminOwner","grantAdministratorRole","listAdministratorGrants","revokeAdministratorGrant","createSessionForUser","provisionUser","findUserByEmail","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","AdminRoleRequiredError","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidAdminBootstrapTokenError","InvalidCredentialsError","LastAdminOwnerError","SignupNotAllowedError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","DEFAULT_SIGNUP_NOT_ALLOWED_MESSAGE","getAuthenticatedSessionContext","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","signupPolicy","name","database","databaseNamespace","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAC/B,SAAQC,gCAAgC,QAAO,0BAA0B;AAEzE,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAElC,SAAQC,6BAA6B,QAAO,yBAAyB;AACrE,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SAAQC,oBAAoB,QAAO,yCAAyC;AAC5E,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AAExD,MAAMC,4BAA4B;IAAC,GAAGjB,gBAAgB;IAAE,GAAGC,gCAAgC;AAAA;AAG3F,SACEiB,WAAW,EACXC,mBAAmB,EACnBC,mBAAmB,EACnBC,gBAAgB,EAChBC,gBAAgB,EAChBC,gBAAgB,QACX,sBAAsB;AAC7B,SACEC,wBAAwB,EACxBC,sBAAsB,EACtBC,uBAAuB,EACvBC,wBAAwB,QACnB,0BAA0B;AAOjC,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAGrD,SACEC,yBAAyB,EACzBC,4BAA4B,EAC5BC,uBAAuB,EACvBC,6BAA6B,EAC7BC,sBAAsB,EACtBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,+BAA+B,EAC/BC,uBAAuB,EACvBC,mBAAmB,EACnBC,qBAAqB,EACrBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAElC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SACE3C,6BAA6B,EAC7B4C,kCAAkC,QAC7B,yBAAyB;AAChC,SAEExC,mBAAmB,EACnByC,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQxC,0BAA0B,QAAO,yCAAyC;AAClF,SACEyC,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQ3C,6BAA6B,QAAO,4CAA4C;AAExF,MAAM4C,aAAapD;AAOnB,OAAO,SAASqD,iBAAiB,EAC/BC,UAAU,EACVC,eAAerD,+BAA+B,EACtB;IACxB,OAAO;QACLsD,MAAM;QACNC,UAAU;YAACtD;YAAIC;YAAgBsD,mBAAmB;QAAM;QACxDC,MAAM;YAACrD;YAAuBC;YAA8BC;SAAgC;QAC5FoD,cAAc9C,qBAAqBb,OAAO4D,qBAAqB;QAC/DC,QAAQ;YACNlD,gBAAgBX,OAAO4D,qBAAqB,EAAEP,YAAYC;YAC1D5C;SACD;QACDoD,WAAW;YAACtD,oBAAoB6C;SAAY;QAC5CU,YAAY;YAAC;gBAACR,MAAM;gBAAQS,OAAO5D;gBAAYF;gBAAI+D,cAAcnD;YAAyB;SAAE;QAC5FoD,aAAa;YAACf,WAAWvD,oCAAoCgB;SAA8B;QAC3FuD,0BAA0B;YAAC1D;SAAoC;IACjE;AACF"}
@@ -1,7 +1,7 @@
1
1
  export type AuthTokenType = 'session' | 'job_lease' | 'runner_session';
2
2
  export type AuthTokenVerificationOutcome = 'ok' | 'rejected';
3
3
  export type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';
4
- export type AuthRateLimitAction = 'login' | 'email-send';
4
+ export type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap';
5
5
  export type AuthRateLimitScope = 'ip' | 'email';
6
6
  export type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';
7
7
  export declare function recordTokenIssued(tokenType: AuthTokenType): void;
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACvE,MAAM,MAAM,4BAA4B,GAAG,IAAI,GAAG,UAAU,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,YAAY,CAAC;AACzD,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAsCzE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAEhE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,4BAA4B,GACpC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAE3E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;CAC/B,GAAG,IAAI,CAQP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,gBAAgB,CAAC;AACvE,MAAM,MAAM,4BAA4B,GAAG,IAAI,GAAG,UAAU,CAAC;AAC7D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,YAAY,GAAG,WAAW,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAsCzE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAEhE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,4BAA4B,GACpC,IAAI,CAEN;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAE3E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,oBAAoB,CAAC;CAC/B,GAAG,IAAI,CAQP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type AuthTokenType = 'session' | 'job_lease' | 'runner_session';\nexport type AuthTokenVerificationOutcome = 'ok' | 'rejected';\nexport type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';\nexport type AuthRateLimitAction = 'login' | 'email-send';\nexport type AuthRateLimitScope = 'ip' | 'email';\nexport type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\n\nconst meter = instanceMetrics.getMeter('auth');\n\nconst tokenIssuedCount = meter.createCounter<{token_type: AuthTokenType}>('auth_token_issued', {\n description: 'Tokens issued by token type',\n});\n\nconst tokenVerifiedCount = meter.createCounter<{\n token_type: AuthTokenType;\n outcome: AuthTokenVerificationOutcome;\n}>('auth_token_verified', {description: 'Token verification attempts by token type and outcome'});\n\nconst tokenRefreshedCount = meter.createCounter<{outcome: AuthTokenRefreshOutcome}>(\n 'auth_token_refreshed',\n {description: 'Refresh-token exchanges by outcome'},\n);\n\nconst rateLimitCheckCount = meter.createCounter<{\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}>('auth_rate_limit_checks', {\n description: 'Authentication rate limit checks by action, scope, and outcome',\n});\n\nconst rateLimitPruneFailureCount = meter.createCounter('auth_rate_limit_prune_failures', {\n description: 'Authentication rate limit prune failures',\n});\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect authentication outcomes.\n }\n}\n\nexport function recordTokenIssued(tokenType: AuthTokenType): void {\n recordMetric(() => tokenIssuedCount.add(1, {token_type: tokenType}));\n}\n\nexport function recordTokenVerified(\n tokenType: AuthTokenType,\n outcome: AuthTokenVerificationOutcome,\n): void {\n recordMetric(() => tokenVerifiedCount.add(1, {token_type: tokenType, outcome}));\n}\n\nexport function recordTokenRefreshed(outcome: AuthTokenRefreshOutcome): void {\n recordMetric(() => tokenRefreshedCount.add(1, {outcome}));\n}\n\nexport function recordAuthRateLimitCheck(params: {\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}): void {\n recordMetric(() =>\n rateLimitCheckCount.add(1, {\n action: params.action,\n scope: params.scope,\n outcome: params.outcome,\n }),\n );\n}\n\nexport function recordAuthRateLimitPruneFailure(): void {\n recordMetric(() => rateLimitPruneFailureCount.add(1));\n}\n"],"names":["instanceMetrics","meter","getMeter","tokenIssuedCount","createCounter","description","tokenVerifiedCount","tokenRefreshedCount","rateLimitCheckCount","rateLimitPruneFailureCount","recordMetric","record","recordTokenIssued","tokenType","add","token_type","recordTokenVerified","outcome","recordTokenRefreshed","recordAuthRateLimitCheck","params","action","scope","recordAuthRateLimitPruneFailure"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAS5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAEvC,MAAMC,mBAAmBF,MAAMG,aAAa,CAA8B,qBAAqB;IAC7FC,aAAa;AACf;AAEA,MAAMC,qBAAqBL,MAAMG,aAAa,CAG3C,uBAAuB;IAACC,aAAa;AAAuD;AAE/F,MAAME,sBAAsBN,MAAMG,aAAa,CAC7C,wBACA;IAACC,aAAa;AAAoC;AAGpD,MAAMG,sBAAsBP,MAAMG,aAAa,CAI5C,0BAA0B;IAC3BC,aAAa;AACf;AAEA,MAAMI,6BAA6BR,MAAMG,aAAa,CAAC,kCAAkC;IACvFC,aAAa;AACf;AAEA,SAASK,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,mDAAmD;IACrD;AACF;AAEA,OAAO,SAASC,kBAAkBC,SAAwB;IACxDH,aAAa,IAAMP,iBAAiBW,GAAG,CAAC,GAAG;YAACC,YAAYF;QAAS;AACnE;AAEA,OAAO,SAASG,oBACdH,SAAwB,EACxBI,OAAqC;IAErCP,aAAa,IAAMJ,mBAAmBQ,GAAG,CAAC,GAAG;YAACC,YAAYF;YAAWI;QAAO;AAC9E;AAEA,OAAO,SAASC,qBAAqBD,OAAgC;IACnEP,aAAa,IAAMH,oBAAoBO,GAAG,CAAC,GAAG;YAACG;QAAO;AACxD;AAEA,OAAO,SAASE,yBAAyBC,MAIxC;IACCV,aAAa,IACXF,oBAAoBM,GAAG,CAAC,GAAG;YACzBO,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBL,SAASG,OAAOH,OAAO;QACzB;AAEJ;AAEA,OAAO,SAASM;IACdb,aAAa,IAAMD,2BAA2BK,GAAG,CAAC;AACpD"}
1
+ {"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nexport type AuthTokenType = 'session' | 'job_lease' | 'runner_session';\nexport type AuthTokenVerificationOutcome = 'ok' | 'rejected';\nexport type AuthTokenRefreshOutcome = 'rotated' | 'grace' | 'rejected';\nexport type AuthRateLimitAction = 'login' | 'email-send' | 'bootstrap';\nexport type AuthRateLimitScope = 'ip' | 'email';\nexport type AuthRateLimitOutcome = 'allowed' | 'blocked' | 'unavailable';\n\nconst meter = instanceMetrics.getMeter('auth');\n\nconst tokenIssuedCount = meter.createCounter<{token_type: AuthTokenType}>('auth_token_issued', {\n description: 'Tokens issued by token type',\n});\n\nconst tokenVerifiedCount = meter.createCounter<{\n token_type: AuthTokenType;\n outcome: AuthTokenVerificationOutcome;\n}>('auth_token_verified', {description: 'Token verification attempts by token type and outcome'});\n\nconst tokenRefreshedCount = meter.createCounter<{outcome: AuthTokenRefreshOutcome}>(\n 'auth_token_refreshed',\n {description: 'Refresh-token exchanges by outcome'},\n);\n\nconst rateLimitCheckCount = meter.createCounter<{\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}>('auth_rate_limit_checks', {\n description: 'Authentication rate limit checks by action, scope, and outcome',\n});\n\nconst rateLimitPruneFailureCount = meter.createCounter('auth_rate_limit_prune_failures', {\n description: 'Authentication rate limit prune failures',\n});\n\nfunction recordMetric(record: () => void): void {\n try {\n record();\n } catch {\n // Metrics must not affect authentication outcomes.\n }\n}\n\nexport function recordTokenIssued(tokenType: AuthTokenType): void {\n recordMetric(() => tokenIssuedCount.add(1, {token_type: tokenType}));\n}\n\nexport function recordTokenVerified(\n tokenType: AuthTokenType,\n outcome: AuthTokenVerificationOutcome,\n): void {\n recordMetric(() => tokenVerifiedCount.add(1, {token_type: tokenType, outcome}));\n}\n\nexport function recordTokenRefreshed(outcome: AuthTokenRefreshOutcome): void {\n recordMetric(() => tokenRefreshedCount.add(1, {outcome}));\n}\n\nexport function recordAuthRateLimitCheck(params: {\n action: AuthRateLimitAction;\n scope: AuthRateLimitScope;\n outcome: AuthRateLimitOutcome;\n}): void {\n recordMetric(() =>\n rateLimitCheckCount.add(1, {\n action: params.action,\n scope: params.scope,\n outcome: params.outcome,\n }),\n );\n}\n\nexport function recordAuthRateLimitPruneFailure(): void {\n recordMetric(() => rateLimitPruneFailureCount.add(1));\n}\n"],"names":["instanceMetrics","meter","getMeter","tokenIssuedCount","createCounter","description","tokenVerifiedCount","tokenRefreshedCount","rateLimitCheckCount","rateLimitPruneFailureCount","recordMetric","record","recordTokenIssued","tokenType","add","token_type","recordTokenVerified","outcome","recordTokenRefreshed","recordAuthRateLimitCheck","params","action","scope","recordAuthRateLimitPruneFailure"],"mappings":"AAAA,SAAQA,eAAe,QAAO,8BAA8B;AAS5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAEvC,MAAMC,mBAAmBF,MAAMG,aAAa,CAA8B,qBAAqB;IAC7FC,aAAa;AACf;AAEA,MAAMC,qBAAqBL,MAAMG,aAAa,CAG3C,uBAAuB;IAACC,aAAa;AAAuD;AAE/F,MAAME,sBAAsBN,MAAMG,aAAa,CAC7C,wBACA;IAACC,aAAa;AAAoC;AAGpD,MAAMG,sBAAsBP,MAAMG,aAAa,CAI5C,0BAA0B;IAC3BC,aAAa;AACf;AAEA,MAAMI,6BAA6BR,MAAMG,aAAa,CAAC,kCAAkC;IACvFC,aAAa;AACf;AAEA,SAASK,aAAaC,MAAkB;IACtC,IAAI;QACFA;IACF,EAAE,OAAM;IACN,mDAAmD;IACrD;AACF;AAEA,OAAO,SAASC,kBAAkBC,SAAwB;IACxDH,aAAa,IAAMP,iBAAiBW,GAAG,CAAC,GAAG;YAACC,YAAYF;QAAS;AACnE;AAEA,OAAO,SAASG,oBACdH,SAAwB,EACxBI,OAAqC;IAErCP,aAAa,IAAMJ,mBAAmBQ,GAAG,CAAC,GAAG;YAACC,YAAYF;YAAWI;QAAO;AAC9E;AAEA,OAAO,SAASC,qBAAqBD,OAAgC;IACnEP,aAAa,IAAMH,oBAAoBO,GAAG,CAAC,GAAG;YAACG;QAAO;AACxD;AAEA,OAAO,SAASE,yBAAyBC,MAIxC;IACCV,aAAa,IACXF,oBAAoBM,GAAG,CAAC,GAAG;YACzBO,QAAQD,OAAOC,MAAM;YACrBC,OAAOF,OAAOE,KAAK;YACnBL,SAASG,OAAOH,OAAO;QACzB;AAEJ;AAEA,OAAO,SAASM;IACdb,aAAa,IAAMD,2BAA2BK,GAAG,CAAC;AACpD"}
@@ -0,0 +1,3 @@
1
+ import { type RouteGroup } from '@shipfox/node-fastify';
2
+ export declare const administrationRoutes: RouteGroup;
3
+ //# sourceMappingURL=administration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"administration.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/administration.ts"],"names":[],"mappings":"AAUA,OAAO,EAA2B,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAoLhF,eAAO,MAAM,oBAAoB,EAAE,UAIlC,CAAC"}
@@ -0,0 +1,186 @@
1
+ import { AUTH_USER } from '@shipfox/api-auth-context';
2
+ import { bootstrapAdminOwnerBodySchema, bootstrapAdminOwnerResponseSchema, grantAdminRoleBodySchema, grantAdminRoleResponseSchema, listAdminGrantsResponseSchema, revokeAdminGrantBodySchema, revokeAdminGrantResponseSchema } from '@shipfox/api-auth-dto';
3
+ import { ClientError, defineRoute } from '@shipfox/node-fastify';
4
+ import { z } from 'zod';
5
+ import { bootstrapFirstAdminOwner, grantAdministratorRole, listAdministratorGrants, revokeAdministratorGrant } from '#core/administration.js';
6
+ import { AdminBootstrapClosedError, AdminGrantAlreadyExistsError, AdminGrantNotFoundError, AdminIdempotencyKeyReuseError, AdminRoleRequiredError, InvalidAdminBootstrapTokenError, LastAdminOwnerError, UserNotFoundError } from '#core/errors.js';
7
+ import { getClientContext } from '#presentation/auth/jwt-auth.js';
8
+ import { createAuthIpRateLimitPreHandler } from './rate-limit.js';
9
+ const idempotencyKeyMaxLength = 256;
10
+ function requireActorId(request) {
11
+ const client = getClientContext(request);
12
+ if (!client) {
13
+ throw new ClientError('Authentication required', 'unauthorized', {
14
+ status: 401
15
+ });
16
+ }
17
+ return client.userId;
18
+ }
19
+ function requireIdempotencyKey(request) {
20
+ const value = request.headers['idempotency-key'];
21
+ const key = Array.isArray(value) ? value[0] : value;
22
+ if (!key || key.trim().length === 0 || key.length > idempotencyKeyMaxLength) {
23
+ throw new ClientError('Idempotency-Key header is required', 'idempotency-key-required', {
24
+ status: 400
25
+ });
26
+ }
27
+ return key;
28
+ }
29
+ function toAdminGrantDto(grant) {
30
+ return {
31
+ id: grant.id,
32
+ user_id: grant.userId,
33
+ role: grant.role,
34
+ revoked_at: grant.revokedAt?.toISOString() ?? null,
35
+ created_at: grant.createdAt.toISOString(),
36
+ updated_at: grant.updatedAt.toISOString()
37
+ };
38
+ }
39
+ function translateAdministrationError(error) {
40
+ if (error instanceof AdminRoleRequiredError) {
41
+ throw new ClientError('Administrator owner role required', 'forbidden', {
42
+ status: 403,
43
+ details: {
44
+ required_role: error.minimumRole
45
+ }
46
+ });
47
+ }
48
+ if (error instanceof InvalidAdminBootstrapTokenError) {
49
+ throw new ClientError('Bootstrap token is invalid', 'bootstrap-token-invalid', {
50
+ status: 403
51
+ });
52
+ }
53
+ if (error instanceof AdminBootstrapClosedError) {
54
+ throw new ClientError('First administrator owner already exists', 'bootstrap-closed', {
55
+ status: 409
56
+ });
57
+ }
58
+ if (error instanceof AdminGrantAlreadyExistsError) {
59
+ throw new ClientError('Administrator grant already exists', 'grant-already-exists', {
60
+ status: 409
61
+ });
62
+ }
63
+ if (error instanceof AdminGrantNotFoundError) {
64
+ throw new ClientError('Administrator grant not found', 'not-found', {
65
+ status: 404
66
+ });
67
+ }
68
+ if (error instanceof UserNotFoundError) {
69
+ throw new ClientError('User not found', 'not-found', {
70
+ status: 404
71
+ });
72
+ }
73
+ if (error instanceof LastAdminOwnerError) {
74
+ throw new ClientError('Cannot remove the final active administrator owner', 'last-owner', {
75
+ status: 409
76
+ });
77
+ }
78
+ if (error instanceof AdminIdempotencyKeyReuseError) {
79
+ throw new ClientError('Idempotency-Key was already used for a different command', 'idempotency-key-reused', {
80
+ status: 409
81
+ });
82
+ }
83
+ throw error;
84
+ }
85
+ const bootstrapRoute = defineRoute({
86
+ method: 'POST',
87
+ path: '/bootstrap',
88
+ description: 'Claim the first administrator owner role with the deployment bootstrap token.',
89
+ schema: {
90
+ body: bootstrapAdminOwnerBodySchema,
91
+ response: {
92
+ 201: bootstrapAdminOwnerResponseSchema
93
+ }
94
+ },
95
+ preHandler: createAuthIpRateLimitPreHandler('bootstrap'),
96
+ errorHandler: translateAdministrationError,
97
+ handler: async (request, reply)=>{
98
+ const actorId = requireActorId(request);
99
+ const grant = await bootstrapFirstAdminOwner({
100
+ actorId,
101
+ bootstrapToken: request.body.bootstrap_token,
102
+ idempotencyKey: requireIdempotencyKey(request),
103
+ correlationId: request.id
104
+ });
105
+ reply.code(201);
106
+ return toAdminGrantDto(grant);
107
+ }
108
+ });
109
+ const listRoute = defineRoute({
110
+ method: 'GET',
111
+ path: '/',
112
+ description: 'List local administrator grants.',
113
+ schema: {
114
+ response: {
115
+ 200: listAdminGrantsResponseSchema
116
+ }
117
+ },
118
+ errorHandler: translateAdministrationError,
119
+ handler: async (request)=>({
120
+ grants: (await listAdministratorGrants({
121
+ actorId: requireActorId(request)
122
+ })).map(toAdminGrantDto)
123
+ })
124
+ });
125
+ const grantRoute = defineRoute({
126
+ method: 'POST',
127
+ path: '/',
128
+ description: 'Grant a local administrator role to an active user.',
129
+ schema: {
130
+ body: grantAdminRoleBodySchema,
131
+ response: {
132
+ 201: grantAdminRoleResponseSchema
133
+ }
134
+ },
135
+ errorHandler: translateAdministrationError,
136
+ handler: async (request, reply)=>{
137
+ const actorId = requireActorId(request);
138
+ const grant = await grantAdministratorRole({
139
+ actorId,
140
+ userId: request.body.user_id,
141
+ role: request.body.role,
142
+ reason: request.body.reason,
143
+ idempotencyKey: requireIdempotencyKey(request),
144
+ correlationId: request.id
145
+ });
146
+ reply.code(201);
147
+ return toAdminGrantDto(grant);
148
+ }
149
+ });
150
+ const revokeRoute = defineRoute({
151
+ method: 'DELETE',
152
+ path: '/:grantId',
153
+ description: 'Revoke a local administrator grant.',
154
+ schema: {
155
+ params: z.object({
156
+ grantId: z.string().uuid()
157
+ }),
158
+ body: revokeAdminGrantBodySchema,
159
+ response: {
160
+ 200: revokeAdminGrantResponseSchema
161
+ }
162
+ },
163
+ errorHandler: translateAdministrationError,
164
+ handler: async (request)=>{
165
+ const grant = await revokeAdministratorGrant({
166
+ actorId: requireActorId(request),
167
+ grantId: request.params.grantId,
168
+ reason: request.body.reason,
169
+ idempotencyKey: requireIdempotencyKey(request),
170
+ correlationId: request.id
171
+ });
172
+ return toAdminGrantDto(grant);
173
+ }
174
+ });
175
+ export const administrationRoutes = {
176
+ prefix: '/admin/v1/auth/admin-grants',
177
+ auth: AUTH_USER,
178
+ routes: [
179
+ bootstrapRoute,
180
+ listRoute,
181
+ grantRoute,
182
+ revokeRoute
183
+ ]
184
+ };
185
+
186
+ //# sourceMappingURL=administration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/presentation/routes/administration.ts"],"sourcesContent":["import {AUTH_USER} from '@shipfox/api-auth-context';\nimport {\n bootstrapAdminOwnerBodySchema,\n bootstrapAdminOwnerResponseSchema,\n grantAdminRoleBodySchema,\n grantAdminRoleResponseSchema,\n listAdminGrantsResponseSchema,\n revokeAdminGrantBodySchema,\n revokeAdminGrantResponseSchema,\n} from '@shipfox/api-auth-dto';\nimport {ClientError, defineRoute, type RouteGroup} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport {z} from 'zod';\nimport {\n bootstrapFirstAdminOwner,\n grantAdministratorRole,\n listAdministratorGrants,\n revokeAdministratorGrant,\n} from '#core/administration.js';\nimport type {AdminGrant} from '#core/entities/admin-grant.js';\nimport {\n AdminBootstrapClosedError,\n AdminGrantAlreadyExistsError,\n AdminGrantNotFoundError,\n AdminIdempotencyKeyReuseError,\n AdminRoleRequiredError,\n InvalidAdminBootstrapTokenError,\n LastAdminOwnerError,\n UserNotFoundError,\n} from '#core/errors.js';\nimport {getClientContext} from '#presentation/auth/jwt-auth.js';\nimport {createAuthIpRateLimitPreHandler} from './rate-limit.js';\n\nconst idempotencyKeyMaxLength = 256;\n\nfunction requireActorId(request: FastifyRequest): string {\n const client = getClientContext(request);\n if (!client) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n return client.userId;\n}\n\nfunction requireIdempotencyKey(request: FastifyRequest): string {\n const value = request.headers['idempotency-key'];\n const key = Array.isArray(value) ? value[0] : value;\n if (!key || key.trim().length === 0 || key.length > idempotencyKeyMaxLength) {\n throw new ClientError('Idempotency-Key header is required', 'idempotency-key-required', {\n status: 400,\n });\n }\n return key;\n}\n\nfunction toAdminGrantDto(grant: AdminGrant) {\n return {\n id: grant.id,\n user_id: grant.userId,\n role: grant.role,\n revoked_at: grant.revokedAt?.toISOString() ?? null,\n created_at: grant.createdAt.toISOString(),\n updated_at: grant.updatedAt.toISOString(),\n };\n}\n\nfunction translateAdministrationError(error: unknown): never {\n if (error instanceof AdminRoleRequiredError) {\n throw new ClientError('Administrator owner role required', 'forbidden', {\n status: 403,\n details: {required_role: error.minimumRole},\n });\n }\n if (error instanceof InvalidAdminBootstrapTokenError) {\n throw new ClientError('Bootstrap token is invalid', 'bootstrap-token-invalid', {\n status: 403,\n });\n }\n if (error instanceof AdminBootstrapClosedError) {\n throw new ClientError('First administrator owner already exists', 'bootstrap-closed', {\n status: 409,\n });\n }\n if (error instanceof AdminGrantAlreadyExistsError) {\n throw new ClientError('Administrator grant already exists', 'grant-already-exists', {\n status: 409,\n });\n }\n if (error instanceof AdminGrantNotFoundError) {\n throw new ClientError('Administrator grant not found', 'not-found', {status: 404});\n }\n if (error instanceof UserNotFoundError) {\n throw new ClientError('User not found', 'not-found', {status: 404});\n }\n if (error instanceof LastAdminOwnerError) {\n throw new ClientError('Cannot remove the final active administrator owner', 'last-owner', {\n status: 409,\n });\n }\n if (error instanceof AdminIdempotencyKeyReuseError) {\n throw new ClientError(\n 'Idempotency-Key was already used for a different command',\n 'idempotency-key-reused',\n {status: 409},\n );\n }\n throw error;\n}\n\nconst bootstrapRoute = defineRoute({\n method: 'POST',\n path: '/bootstrap',\n description: 'Claim the first administrator owner role with the deployment bootstrap token.',\n schema: {\n body: bootstrapAdminOwnerBodySchema,\n response: {201: bootstrapAdminOwnerResponseSchema},\n },\n preHandler: createAuthIpRateLimitPreHandler('bootstrap'),\n errorHandler: translateAdministrationError,\n handler: async (request, reply) => {\n const actorId = requireActorId(request);\n const grant = await bootstrapFirstAdminOwner({\n actorId,\n bootstrapToken: request.body.bootstrap_token,\n idempotencyKey: requireIdempotencyKey(request),\n correlationId: request.id,\n });\n reply.code(201);\n return toAdminGrantDto(grant);\n },\n});\n\nconst listRoute = defineRoute({\n method: 'GET',\n path: '/',\n description: 'List local administrator grants.',\n schema: {response: {200: listAdminGrantsResponseSchema}},\n errorHandler: translateAdministrationError,\n handler: async (request) => ({\n grants: (await listAdministratorGrants({actorId: requireActorId(request)})).map(\n toAdminGrantDto,\n ),\n }),\n});\n\nconst grantRoute = defineRoute({\n method: 'POST',\n path: '/',\n description: 'Grant a local administrator role to an active user.',\n schema: {\n body: grantAdminRoleBodySchema,\n response: {201: grantAdminRoleResponseSchema},\n },\n errorHandler: translateAdministrationError,\n handler: async (request, reply) => {\n const actorId = requireActorId(request);\n const grant = await grantAdministratorRole({\n actorId,\n userId: request.body.user_id,\n role: request.body.role,\n reason: request.body.reason,\n idempotencyKey: requireIdempotencyKey(request),\n correlationId: request.id,\n });\n reply.code(201);\n return toAdminGrantDto(grant);\n },\n});\n\nconst revokeRoute = defineRoute({\n method: 'DELETE',\n path: '/:grantId',\n description: 'Revoke a local administrator grant.',\n schema: {\n params: z.object({grantId: z.string().uuid()}),\n body: revokeAdminGrantBodySchema,\n response: {200: revokeAdminGrantResponseSchema},\n },\n errorHandler: translateAdministrationError,\n handler: async (request) => {\n const grant = await revokeAdministratorGrant({\n actorId: requireActorId(request),\n grantId: request.params.grantId,\n reason: request.body.reason,\n idempotencyKey: requireIdempotencyKey(request),\n correlationId: request.id,\n });\n return toAdminGrantDto(grant);\n },\n});\n\nexport const administrationRoutes: RouteGroup = {\n prefix: '/admin/v1/auth/admin-grants',\n auth: AUTH_USER,\n routes: [bootstrapRoute, listRoute, grantRoute, revokeRoute],\n};\n"],"names":["AUTH_USER","bootstrapAdminOwnerBodySchema","bootstrapAdminOwnerResponseSchema","grantAdminRoleBodySchema","grantAdminRoleResponseSchema","listAdminGrantsResponseSchema","revokeAdminGrantBodySchema","revokeAdminGrantResponseSchema","ClientError","defineRoute","z","bootstrapFirstAdminOwner","grantAdministratorRole","listAdministratorGrants","revokeAdministratorGrant","AdminBootstrapClosedError","AdminGrantAlreadyExistsError","AdminGrantNotFoundError","AdminIdempotencyKeyReuseError","AdminRoleRequiredError","InvalidAdminBootstrapTokenError","LastAdminOwnerError","UserNotFoundError","getClientContext","createAuthIpRateLimitPreHandler","idempotencyKeyMaxLength","requireActorId","request","client","status","userId","requireIdempotencyKey","value","headers","key","Array","isArray","trim","length","toAdminGrantDto","grant","id","user_id","role","revoked_at","revokedAt","toISOString","created_at","createdAt","updated_at","updatedAt","translateAdministrationError","error","details","required_role","minimumRole","bootstrapRoute","method","path","description","schema","body","response","preHandler","errorHandler","handler","reply","actorId","bootstrapToken","bootstrap_token","idempotencyKey","correlationId","code","listRoute","grants","map","grantRoute","reason","revokeRoute","params","object","grantId","string","uuid","administrationRoutes","prefix","auth","routes"],"mappings":"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"}
@@ -6,5 +6,6 @@ interface EmailBody {
6
6
  export declare function createAuthRateLimitPreHandler(action: AuthRateLimitAction): (request: FastifyRequest<{
7
7
  Body: EmailBody;
8
8
  }>, reply: FastifyReply) => Promise<void>;
9
+ export declare function createAuthIpRateLimitPreHandler(action: AuthRateLimitAction): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
9
10
  export {};
10
11
  //# sourceMappingURL=rate-limit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,KAAK,mBAAmB,EAMzB,MAAM,qBAAqB,CAAC;AAa7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AA6ED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,IACzD,SAAS,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EACL,KAAK,mBAAmB,EAMzB,MAAM,qBAAqB,CAAC;AAmB7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAgFD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,IACzD,SAAS,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,mBAAmB,IAC3D,SAAS,cAAc,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAS3E"}
@@ -20,18 +20,26 @@ const policies = {
20
20
  limit: 3,
21
21
  windowSeconds: 60 * 60
22
22
  }
23
+ },
24
+ bootstrap: {
25
+ ip: {
26
+ limit: 5,
27
+ windowSeconds: 15 * 60
28
+ }
23
29
  }
24
30
  };
25
31
  function routeName(request) {
26
32
  return request.routeOptions.url ?? request.url.split('?')[0] ?? 'unknown';
27
33
  }
28
34
  async function enforceRateLimit(params) {
35
+ const policy = policies[params.action][params.scope];
36
+ if (!policy) return;
29
37
  try {
30
38
  await checkAuthRateLimit({
31
39
  action: params.action,
32
40
  scope: params.scope,
33
41
  identifier: params.identifier,
34
- ...policies[params.action][params.scope]
42
+ ...policy
35
43
  });
36
44
  } catch (error) {
37
45
  if (error instanceof AuthRateLimitExceededError) {
@@ -97,5 +105,16 @@ export function createAuthRateLimitPreHandler(action) {
97
105
  });
98
106
  };
99
107
  }
108
+ export function createAuthIpRateLimitPreHandler(action) {
109
+ return async (request, reply)=>{
110
+ await enforceRateLimit({
111
+ request,
112
+ reply,
113
+ action,
114
+ scope: 'ip',
115
+ identifier: request.ip
116
+ });
117
+ };
118
+ }
100
119
 
101
120
  //# sourceMappingURL=rate-limit.js.map