@isahaq/kosh-server 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/api/client.d.ts +18 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +380 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/idempotency.d.ts +23 -0
- package/dist/api/idempotency.d.ts.map +1 -0
- package/dist/api/idempotency.js +52 -0
- package/dist/api/idempotency.js.map +1 -0
- package/dist/api/metadata.d.ts +65 -0
- package/dist/api/metadata.d.ts.map +1 -0
- package/dist/api/metadata.js +88 -0
- package/dist/api/metadata.js.map +1 -0
- package/dist/api/navigation.d.ts +46 -0
- package/dist/api/navigation.d.ts.map +1 -0
- package/dist/api/navigation.js +131 -0
- package/dist/api/navigation.js.map +1 -0
- package/dist/api/openapi.d.ts +22 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +349 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/api/rest.d.ts +28 -0
- package/dist/api/rest.d.ts.map +1 -0
- package/dist/api/rest.js +435 -0
- package/dist/api/rest.js.map +1 -0
- package/dist/api/views.d.ts +67 -0
- package/dist/api/views.d.ts.map +1 -0
- package/dist/api/views.js +307 -0
- package/dist/api/views.js.map +1 -0
- package/dist/audit.d.ts +21 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +26 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth/admins.d.ts +26 -0
- package/dist/auth/admins.d.ts.map +1 -0
- package/dist/auth/admins.js +70 -0
- package/dist/auth/admins.js.map +1 -0
- package/dist/auth/base.d.ts +12 -0
- package/dist/auth/base.d.ts.map +1 -0
- package/dist/auth/base.js +192 -0
- package/dist/auth/base.js.map +1 -0
- package/dist/auth/crypto.d.ts +20 -0
- package/dist/auth/crypto.d.ts.map +1 -0
- package/dist/auth/crypto.js +100 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/install.d.ts +24 -0
- package/dist/auth/install.d.ts.map +1 -0
- package/dist/auth/install.js +18 -0
- package/dist/auth/install.js.map +1 -0
- package/dist/auth/routes.d.ts +20 -0
- package/dist/auth/routes.d.ts.map +1 -0
- package/dist/auth/routes.js +56 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/service.d.ts +108 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +477 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +78 -0
- package/dist/errors.js.map +1 -0
- package/dist/identity.d.ts +49 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +58 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/orm.d.ts +14 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/orm.js +12 -0
- package/dist/orm.js.map +1 -0
- package/dist/registry.d.ts +19 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +26 -0
- package/dist/registry.js.map +1 -0
- package/dist/server.d.ts +93 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +223 -0
- package/dist/server.js.map +1 -0
- package/dist/telemetry.d.ts +23 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +38 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +63 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who a request acts as (RFC 0002 §6.2). An {@link Authenticator} turns a request into an identity (sessions and 2FA
|
|
3
|
+
* come with the auth step); headers may then narrow it: `X-Company` (current company), `X-Companies` (allowed
|
|
4
|
+
* companies, a subset), `Accept-Language` and `X-Timezone`. Without an identity a route answers 401.
|
|
5
|
+
*/
|
|
6
|
+
import type { FastifyRequest } from 'fastify';
|
|
7
|
+
/** The verified identity of a request. */
|
|
8
|
+
export interface RequestIdentity {
|
|
9
|
+
readonly user: number;
|
|
10
|
+
/** Default current company. */
|
|
11
|
+
readonly company: number;
|
|
12
|
+
/** Companies the user may work in. */
|
|
13
|
+
readonly companies: readonly number[];
|
|
14
|
+
/** Shutro group codes for these companies. */
|
|
15
|
+
readonly groups: readonly string[];
|
|
16
|
+
/** Special permissions of the user (Shutro `grants`), e.g. `account.backdate`. */
|
|
17
|
+
readonly grants?: readonly string[];
|
|
18
|
+
readonly lang?: string;
|
|
19
|
+
readonly tz?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The single explicit bypass (CLAUDE.md "superuser mode", RFC 0003 §5.2): the user holds an administrator role
|
|
23
|
+
* (Shutro `env.administrator`, acting as themselves) or the code runs in an audited superuser scope (`env.su`).
|
|
24
|
+
* Permission checks pass; integrity (constraints, company consistency, state guards) still applies.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isSuperuser(env: {
|
|
27
|
+
readonly administrator: boolean;
|
|
28
|
+
readonly su: boolean;
|
|
29
|
+
}): boolean;
|
|
30
|
+
/** Resolves a request's identity, or `null` when it is not authenticated. */
|
|
31
|
+
export type Authenticator = (request: FastifyRequest) => Promise<RequestIdentity | null>;
|
|
32
|
+
/** The default: nobody is authenticated (secure by default until an auth method is configured). */
|
|
33
|
+
export declare const denyAll: Authenticator;
|
|
34
|
+
/** The first `ll-CC` of `Accept-Language`, as a Shutro language code (`bn-BD` → `bn_BD`). */
|
|
35
|
+
export declare function languageOf(accept: string | undefined): string | undefined;
|
|
36
|
+
/** What a request runs as in Shutro (`shutro.run` options). */
|
|
37
|
+
export interface RequestRunOptions {
|
|
38
|
+
readonly user: number;
|
|
39
|
+
readonly company: number;
|
|
40
|
+
readonly companies: readonly number[];
|
|
41
|
+
readonly groups: readonly string[];
|
|
42
|
+
readonly grants?: readonly string[];
|
|
43
|
+
readonly lang?: string;
|
|
44
|
+
readonly tz?: string;
|
|
45
|
+
readonly readOnly: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** The Shutro run options of a request: the identity narrowed by its headers. @throws {HttpProblem} */
|
|
48
|
+
export declare function runOptionsOf(request: FastifyRequest, identity: RequestIdentity): RequestRunOptions;
|
|
49
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE;IAC/B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAEV;AAED,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;AAEzF,mGAAmG;AACnG,eAAO,MAAM,OAAO,EAAE,aAAgC,CAAC;AAuBvD,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,uGAAuG;AACvG,wBAAgB,YAAY,CAC1B,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,eAAe,GACxB,iBAAiB,CAkCnB"}
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { HttpProblem } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* The single explicit bypass (CLAUDE.md "superuser mode", RFC 0003 §5.2): the user holds an administrator role
|
|
4
|
+
* (Shutro `env.administrator`, acting as themselves) or the code runs in an audited superuser scope (`env.su`).
|
|
5
|
+
* Permission checks pass; integrity (constraints, company consistency, state guards) still applies.
|
|
6
|
+
*/
|
|
7
|
+
export function isSuperuser(env) {
|
|
8
|
+
return env.administrator || env.su;
|
|
9
|
+
}
|
|
10
|
+
/** The default: nobody is authenticated (secure by default until an auth method is configured). */
|
|
11
|
+
export const denyAll = async () => null;
|
|
12
|
+
const LANG = /^([a-z]{2,3})[-_]([A-Za-z]{2})$/;
|
|
13
|
+
function header(request, name) {
|
|
14
|
+
const value = request.headers[name];
|
|
15
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
16
|
+
}
|
|
17
|
+
function ids(text, what) {
|
|
18
|
+
return text.split(',').map((part) => {
|
|
19
|
+
const n = Number(part.trim());
|
|
20
|
+
if (!Number.isSafeInteger(n) || n <= 0)
|
|
21
|
+
throw new HttpProblem(400, 'invalid-header', 'Invalid header', `${what} must be company ids, got ${JSON.stringify(text)}.`);
|
|
22
|
+
return n;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** The first `ll-CC` of `Accept-Language`, as a Shutro language code (`bn-BD` → `bn_BD`). */
|
|
26
|
+
export function languageOf(accept) {
|
|
27
|
+
for (const part of (accept ?? '').split(',')) {
|
|
28
|
+
const m = LANG.exec(part.split(';')[0]?.trim() ?? '');
|
|
29
|
+
if (m !== null)
|
|
30
|
+
return `${m[1]}_${m[2]?.toUpperCase()}`;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/** The Shutro run options of a request: the identity narrowed by its headers. @throws {HttpProblem} */
|
|
35
|
+
export function runOptionsOf(request, identity) {
|
|
36
|
+
const companiesHeader = header(request, 'x-companies');
|
|
37
|
+
const companyHeader = header(request, 'x-company');
|
|
38
|
+
const companies = companiesHeader === undefined ? [...identity.companies] : ids(companiesHeader, 'X-Companies');
|
|
39
|
+
const outside = companies.filter((c) => !identity.companies.includes(c));
|
|
40
|
+
if (outside.length > 0)
|
|
41
|
+
throw new HttpProblem(403, 'company-not-allowed', 'Company not allowed', `Company ${outside.join(', ')} is not allowed for this user.`);
|
|
42
|
+
const company = companyHeader === undefined ? identity.company : ids(companyHeader, 'X-Company')[0];
|
|
43
|
+
if (!companies.includes(company))
|
|
44
|
+
throw new HttpProblem(403, 'company-not-allowed', 'Company not allowed', `Company ${company} is not among the selected companies.`);
|
|
45
|
+
const lang = languageOf(header(request, 'accept-language')) ?? identity.lang;
|
|
46
|
+
const tz = header(request, 'x-timezone') ?? identity.tz;
|
|
47
|
+
return {
|
|
48
|
+
user: identity.user,
|
|
49
|
+
company,
|
|
50
|
+
companies,
|
|
51
|
+
groups: [...identity.groups],
|
|
52
|
+
...(identity.grants === undefined ? {} : { grants: [...identity.grants] }),
|
|
53
|
+
...(lang === undefined ? {} : { lang }),
|
|
54
|
+
...(tz === undefined ? {} : { tz }),
|
|
55
|
+
readOnly: request.method === 'GET' || request.method === 'HEAD',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiB1C;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,GAG3B;IACC,OAAO,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,EAAE,CAAC;AACrC,CAAC;AAKD,mGAAmG;AACnG,MAAM,CAAC,MAAM,OAAO,GAAkB,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC;AAEvD,MAAM,IAAI,GAAG,iCAAiC,CAAC;AAE/C,SAAS,MAAM,CAAC,OAAuB,EAAE,IAAY;IACnD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACrF,CAAC;AAED,SAAS,GAAG,CAAC,IAAY,EAAE,IAAY;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAClC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACpC,MAAM,IAAI,WAAW,CACnB,GAAG,EACH,gBAAgB,EAChB,gBAAgB,EAChB,GAAG,IAAI,6BAA6B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAC5D,CAAC;QACJ,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,UAAU,CAAC,MAA0B;IACnD,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC;IAC1D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAcD,uGAAuG;AACvG,MAAM,UAAU,YAAY,CAC1B,OAAuB,EACvB,QAAyB;IAEzB,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACvD,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACnD,MAAM,SAAS,GACb,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IAChG,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QACpB,MAAM,IAAI,WAAW,CACnB,GAAG,EACH,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAC9D,CAAC;IACJ,MAAM,OAAO,GACX,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAE,GAAG,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,CAAY,CAAC;IAClG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC9B,MAAM,IAAI,WAAW,CACnB,GAAG,EACH,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,OAAO,uCAAuC,CAC1D,CAAC;IACJ,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC;IAC7E,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,IAAI,QAAQ,CAAC,EAAE,CAAC;IACxD,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,OAAO;QACP,SAAS;QACT,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC;QAC5B,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1E,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACvC,GAAG,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;QACnC,QAAQ,EAAE,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;KAChE,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* `@isahaq/kosh-server`: the Fastify server, module loading, the per-request Shutro environment, health checks, rate
|
|
4
|
+
* limiting, logging and telemetry (RFC 0002). Shutro is reached only through `./orm.ts` (`@isahaq/kosh-server/orm`).
|
|
5
|
+
*/
|
|
6
|
+
export { generateClient, typeOf } from './api/client.js';
|
|
7
|
+
export { IDEMPOTENCY_TABLE, idempotencyTableStatements, type Outcome } from './api/idempotency.js';
|
|
8
|
+
export { type ApiField, type ApiModel, apiModels, defaultFields, publicApiModels, schemaName, } from './api/metadata.js';
|
|
9
|
+
export { type MenuNode, moduleVisible, Navigation } from './api/navigation.js';
|
|
10
|
+
export { fieldSchema, openApiDocument, openApiOf } from './api/openapi.js';
|
|
11
|
+
export { registerRestApi } from './api/rest.js';
|
|
12
|
+
export { type ServedView, type ViewField, ViewRegistry } from './api/views.js';
|
|
13
|
+
export { type AuditInput, audit } from './audit.js';
|
|
14
|
+
export { administrators, isAdministratorUser, keepingAdministrator, LAST_ADMINISTRATOR, } from './auth/admins.js';
|
|
15
|
+
export { base, baseShutro } from './auth/base.js';
|
|
16
|
+
export { digest, hashPassword, newTotpSecret, otpauthUri, randomToken, totp, verifyPassword, verifyTotp, } from './auth/crypto.js';
|
|
17
|
+
export { type InstallBaseOptions, installBase } from './auth/install.js';
|
|
18
|
+
export { registerAuthRoutes } from './auth/routes.js';
|
|
19
|
+
export { type AuthOptions, AuthService, cookieOf, SESSION_COOKIE, type SessionInfo, timeOf, type UserInfo, } from './auth/service.js';
|
|
20
|
+
export { HttpProblem, type Problem, problemOf, ServerConfigError } from './errors.js';
|
|
21
|
+
export { type Authenticator, denyAll, isSuperuser, languageOf, type RequestIdentity, type RequestRunOptions, runOptionsOf, } from './identity.js';
|
|
22
|
+
export { orm, pg } from './orm.js';
|
|
23
|
+
export { buildKoshRegistry, KOSH_VERSION, type KoshRegistry } from './registry.js';
|
|
24
|
+
export { createServer, type KoshServer, type ModuleContext, type ServerOptions, startServer, } from './server.js';
|
|
25
|
+
export { startTelemetry, type Telemetry, tracer } from './telemetry.js';
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACnG,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,SAAS,EACT,aAAa,EACb,eAAe,EACf,UAAU,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EACL,MAAM,EACN,YAAY,EACZ,aAAa,EACb,UAAU,EACV,WAAW,EACX,IAAI,EACJ,cAAc,EACd,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EACL,KAAK,WAAW,EAChB,WAAW,EACX,QAAQ,EACR,cAAc,EACd,KAAK,WAAW,EAChB,MAAM,EACN,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,KAAK,aAAa,EAClB,OAAO,EACP,WAAW,EACX,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EACL,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* `@isahaq/kosh-server`: the Fastify server, module loading, the per-request Shutro environment, health checks, rate
|
|
4
|
+
* limiting, logging and telemetry (RFC 0002). Shutro is reached only through `./orm.ts` (`@isahaq/kosh-server/orm`).
|
|
5
|
+
*/
|
|
6
|
+
export { generateClient, typeOf } from './api/client.js';
|
|
7
|
+
export { IDEMPOTENCY_TABLE, idempotencyTableStatements } from './api/idempotency.js';
|
|
8
|
+
export { apiModels, defaultFields, publicApiModels, schemaName, } from './api/metadata.js';
|
|
9
|
+
export { moduleVisible, Navigation } from './api/navigation.js';
|
|
10
|
+
export { fieldSchema, openApiDocument, openApiOf } from './api/openapi.js';
|
|
11
|
+
export { registerRestApi } from './api/rest.js';
|
|
12
|
+
export { ViewRegistry } from './api/views.js';
|
|
13
|
+
export { audit } from './audit.js';
|
|
14
|
+
export { administrators, isAdministratorUser, keepingAdministrator, LAST_ADMINISTRATOR, } from './auth/admins.js';
|
|
15
|
+
export { base, baseShutro } from './auth/base.js';
|
|
16
|
+
export { digest, hashPassword, newTotpSecret, otpauthUri, randomToken, totp, verifyPassword, verifyTotp, } from './auth/crypto.js';
|
|
17
|
+
export { installBase } from './auth/install.js';
|
|
18
|
+
export { registerAuthRoutes } from './auth/routes.js';
|
|
19
|
+
export { AuthService, cookieOf, SESSION_COOKIE, timeOf, } from './auth/service.js';
|
|
20
|
+
export { HttpProblem, problemOf, ServerConfigError } from './errors.js';
|
|
21
|
+
export { denyAll, isSuperuser, languageOf, runOptionsOf, } from './identity.js';
|
|
22
|
+
export { orm, pg } from './orm.js';
|
|
23
|
+
export { buildKoshRegistry, KOSH_VERSION } from './registry.js';
|
|
24
|
+
export { createServer, startServer, } from './server.js';
|
|
25
|
+
export { startTelemetry, tracer } from './telemetry.js';
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAgB,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAGL,SAAS,EACT,aAAa,EACb,eAAe,EACf,UAAU,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAiB,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAmC,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAmB,KAAK,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EACL,MAAM,EACN,YAAY,EACZ,aAAa,EACb,UAAU,EACV,WAAW,EACX,IAAI,EACJ,cAAc,EACd,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAA2B,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAEL,WAAW,EACX,QAAQ,EACR,cAAc,EAEd,MAAM,GAEP,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAgB,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAEL,OAAO,EACP,WAAW,EACX,UAAU,EAGV,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAqB,MAAM,eAAe,CAAC;AACnF,OAAO,EACL,YAAY,EAIZ,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAkB,MAAM,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/orm.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE place Kosh imports Shutro (CLAUDE.md, Shutro integration; RFC 0002 §6.1). Every other package imports the
|
|
3
|
+
* ORM from here (`@isahaq/kosh-server/orm`), so the dependency can be renamed or swapped in one file; `pnpm lint` fails if
|
|
4
|
+
* another file imports `shutro` directly.
|
|
5
|
+
*/
|
|
6
|
+
import * as core from 'shutro';
|
|
7
|
+
import * as driverPg from 'shutro/driver-pg';
|
|
8
|
+
/** The Shutro ORM API as Kosh uses it. */
|
|
9
|
+
export declare const orm: typeof core;
|
|
10
|
+
/** Shutro's PostgreSQL driver (pools, transactions); separate because some error names overlap the core's. */
|
|
11
|
+
export declare const pg: typeof driverPg;
|
|
12
|
+
export type * from 'shutro';
|
|
13
|
+
export type { Driver, DriverOptions } from 'shutro/driver-pg';
|
|
14
|
+
//# sourceMappingURL=orm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orm.d.ts","sourceRoot":"","sources":["../src/orm.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC/B,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,0CAA0C;AAC1C,eAAO,MAAM,GAAG,EAAE,OAAO,IAAW,CAAC;AACrC,8GAA8G;AAC9G,eAAO,MAAM,EAAE,EAAE,OAAO,QAAmB,CAAC;AAE5C,mBAAmB,QAAQ,CAAC;AAC5B,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/orm.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE place Kosh imports Shutro (CLAUDE.md, Shutro integration; RFC 0002 §6.1). Every other package imports the
|
|
3
|
+
* ORM from here (`@isahaq/kosh-server/orm`), so the dependency can be renamed or swapped in one file; `pnpm lint` fails if
|
|
4
|
+
* another file imports `shutro` directly.
|
|
5
|
+
*/
|
|
6
|
+
import * as core from 'shutro';
|
|
7
|
+
import * as driverPg from 'shutro/driver-pg';
|
|
8
|
+
/** The Shutro ORM API as Kosh uses it. */
|
|
9
|
+
export const orm = core;
|
|
10
|
+
/** Shutro's PostgreSQL driver (pools, transactions); separate because some error names overlap the core's. */
|
|
11
|
+
export const pg = driverPg;
|
|
12
|
+
//# sourceMappingURL=orm.js.map
|
package/dist/orm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orm.js","sourceRoot":"","sources":["../src/orm.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC/B,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,0CAA0C;AAC1C,MAAM,CAAC,MAAM,GAAG,GAAgB,IAAI,CAAC;AACrC,8GAA8G;AAC9G,MAAM,CAAC,MAAM,EAAE,GAAoB,QAAQ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From Kosh modules to a Shutro registry (RFC 0001 §5, RFC 0002 §6.1): the module loader's checks, then the Shutro
|
|
3
|
+
* registry of the wrapped Shutro modules, then the checks that need both: every manifest wraps a real Shutro module,
|
|
4
|
+
* and every Shutro module in the registry has a Kosh manifest (so none escapes declaring access levels).
|
|
5
|
+
*/
|
|
6
|
+
import { type KoshModule, type LoadedModules } from '@isahaq/kosh-modules-sdk';
|
|
7
|
+
import { type Registry, type RegistryOptions } from './orm.js';
|
|
8
|
+
/** The framework version this server implements (checked against each module's `kosh` range). */
|
|
9
|
+
export declare const KOSH_VERSION = "0.1.0";
|
|
10
|
+
/** The loaded modules and their Shutro registry. */
|
|
11
|
+
export interface KoshRegistry {
|
|
12
|
+
readonly loaded: LoadedModules;
|
|
13
|
+
readonly registry: Registry;
|
|
14
|
+
}
|
|
15
|
+
/** Loads `modules` (and their dependencies) and builds the Shutro registry. @throws {ServerConfigError} */
|
|
16
|
+
export declare function buildKoshRegistry(modules: readonly KoshModule[], options?: {
|
|
17
|
+
readonly security?: RegistryOptions<[]>['security'];
|
|
18
|
+
}): KoshRegistry;
|
|
19
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAe,MAAM,0BAA0B,CAAC;AAE5F,OAAO,EAAO,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAEpE,iGAAiG;AACjG,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,oDAAoD;AACpD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED,2GAA2G;AAC3G,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,OAAO,GAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,CAAA;CAAO,GACpE,YAAY,CAmBd"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From Kosh modules to a Shutro registry (RFC 0001 §5, RFC 0002 §6.1): the module loader's checks, then the Shutro
|
|
3
|
+
* registry of the wrapped Shutro modules, then the checks that need both: every manifest wraps a real Shutro module,
|
|
4
|
+
* and every Shutro module in the registry has a Kosh manifest (so none escapes declaring access levels).
|
|
5
|
+
*/
|
|
6
|
+
import { loadModules } from '@isahaq/kosh-modules-sdk';
|
|
7
|
+
import { ServerConfigError } from './errors.js';
|
|
8
|
+
import { orm } from './orm.js';
|
|
9
|
+
/** The framework version this server implements (checked against each module's `kosh` range). */
|
|
10
|
+
export const KOSH_VERSION = '0.1.0';
|
|
11
|
+
/** Loads `modules` (and their dependencies) and builds the Shutro registry. @throws {ServerConfigError} */
|
|
12
|
+
export function buildKoshRegistry(modules, options = {}) {
|
|
13
|
+
const loaded = loadModules(modules, { koshVersion: KOSH_VERSION });
|
|
14
|
+
for (const m of loaded.modules)
|
|
15
|
+
if (!orm.isModule(m.shutro))
|
|
16
|
+
throw new ServerConfigError(`Module '${m.name}' does not wrap a Shutro module.`, 'Pass the value returned by defineModule() from @isahaq/kosh-server/orm as `shutro`.');
|
|
17
|
+
const registry = orm.buildRegistry({
|
|
18
|
+
modules: loaded.modules.map((m) => m.shutro),
|
|
19
|
+
...(options.security === undefined ? {} : { security: options.security }),
|
|
20
|
+
});
|
|
21
|
+
for (const name of registry.loadOrder)
|
|
22
|
+
if (!loaded.byName.has(name))
|
|
23
|
+
throw new ServerConfigError(`The Shutro module '${name}' has no Kosh manifest.`, 'Wrap it with defineModule() from @isahaq/kosh-modules-sdk (its access levels are required) and depend on it.');
|
|
24
|
+
return { loaded, registry };
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAuC,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,GAAG,EAAuC,MAAM,UAAU,CAAC;AAEpE,iGAAiG;AACjG,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AAQpC,2GAA2G;AAC3G,MAAM,UAAU,iBAAiB,CAC/B,OAA8B,EAC9B,OAAO,GAA4D,EAAE;IAErE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,CAAC;IACnE,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO;QAC5B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;YACzB,MAAM,IAAI,iBAAiB,CACzB,WAAW,CAAC,CAAC,IAAI,kCAAkC,EACnD,qFAAqF,CACtF,CAAC;IACN,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC;QACjC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAe,CAAC;QACrD,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;KAC1E,CAAwB,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS;QACnC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAC1B,MAAM,IAAI,iBAAiB,CACzB,sBAAsB,IAAI,yBAAyB,EACnD,8GAA8G,CAC/G,CAAC;IACN,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { KoshModule } from '@isahaq/kosh-modules-sdk';
|
|
2
|
+
import type { Span } from '@opentelemetry/api';
|
|
3
|
+
import { type FastifyInstance, type FastifyServerOptions } from 'fastify';
|
|
4
|
+
import { type AuthOptions, AuthService } from './auth/service.js';
|
|
5
|
+
import { type Authenticator, type RequestIdentity } from './identity.js';
|
|
6
|
+
import { type Driver, type Env, type Shutro } from './orm.js';
|
|
7
|
+
import { type KoshRegistry } from './registry.js';
|
|
8
|
+
declare module 'fastify' {
|
|
9
|
+
interface FastifyRequest {
|
|
10
|
+
/** The verified identity, or `null` on public routes. */
|
|
11
|
+
identity: RequestIdentity | null;
|
|
12
|
+
/**
|
|
13
|
+
* Runs `fn` in this request's Shutro transaction (read-only for GET / HEAD, unless `write`: for reads that open
|
|
14
|
+
* audited system scopes). @throws {HttpProblem} 401 without identity
|
|
15
|
+
*/
|
|
16
|
+
env<T>(fn: (env: Env) => Promise<T>, options?: {
|
|
17
|
+
readonly write?: boolean;
|
|
18
|
+
}): Promise<T>;
|
|
19
|
+
/** @internal */
|
|
20
|
+
span: Span | undefined;
|
|
21
|
+
}
|
|
22
|
+
interface FastifyContextConfig {
|
|
23
|
+
/** No authentication, no rate limit (health checks). */
|
|
24
|
+
public?: boolean;
|
|
25
|
+
/** `false`: the route checks the session itself (sign-in, 2FA); still rate limited. */
|
|
26
|
+
auth?: boolean;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Options of {@link createServer}. */
|
|
30
|
+
export interface ServerOptions {
|
|
31
|
+
readonly databaseUrl: string;
|
|
32
|
+
readonly modules: readonly KoshModule[];
|
|
33
|
+
/** Valkey for rate limiting (and later caches, pub/sub); without it, limits are per process. */
|
|
34
|
+
readonly valkeyUrl?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Who is calling. Default: Kosh sessions (cookie or Bearer, 2FA) when the `base` module is installed, otherwise
|
|
37
|
+
* nobody is authenticated.
|
|
38
|
+
*/
|
|
39
|
+
readonly authenticate?: Authenticator;
|
|
40
|
+
/** Session settings of the built-in authentication. */
|
|
41
|
+
readonly auth?: AuthOptions & {
|
|
42
|
+
/** Sign-in attempts per 15 minutes per IP and login (default 10; 0 turns the limit off). */
|
|
43
|
+
readonly loginLimit?: number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Shutro's identity loader checks every request's user (active, companies, groups) against `base`'s data. Default:
|
|
47
|
+
* on with the built-in authentication; custom authenticators opt in.
|
|
48
|
+
*/
|
|
49
|
+
readonly verifyIdentities?: boolean;
|
|
50
|
+
/** Requests per window per user (or IP when anonymous). Default 600 per minute. */
|
|
51
|
+
readonly rateLimit?: {
|
|
52
|
+
readonly max: number;
|
|
53
|
+
readonly timeWindow: string | number;
|
|
54
|
+
};
|
|
55
|
+
readonly logger?: FastifyServerOptions['logger'];
|
|
56
|
+
/** Maximum database connections (default 10). */
|
|
57
|
+
readonly poolSize?: number;
|
|
58
|
+
/** Passed to the registry: `{ enforce: false }` only for tests of non-security features. */
|
|
59
|
+
readonly security?: {
|
|
60
|
+
readonly enforce?: boolean | 'warn';
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** What a module's `routes` hook receives (`defineModule({ routes })`). */
|
|
64
|
+
export interface ModuleContext {
|
|
65
|
+
readonly app: FastifyInstance;
|
|
66
|
+
readonly shutro: Shutro;
|
|
67
|
+
readonly driver: Driver;
|
|
68
|
+
readonly registry: KoshRegistry;
|
|
69
|
+
readonly auth: AuthService | undefined;
|
|
70
|
+
}
|
|
71
|
+
/** A created server. */
|
|
72
|
+
export interface KoshServer {
|
|
73
|
+
readonly app: FastifyInstance;
|
|
74
|
+
readonly shutro: Shutro;
|
|
75
|
+
readonly driver: Driver;
|
|
76
|
+
readonly registry: KoshRegistry;
|
|
77
|
+
/** The built-in authentication (when `base` is installed). */
|
|
78
|
+
readonly auth: AuthService | undefined;
|
|
79
|
+
/** Stops accepting requests, lets in-flight ones finish, then closes the pool and Valkey. Safe to call twice. */
|
|
80
|
+
close(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
/** Creates the server (not listening yet). @throws {ServerConfigError} for modules that cannot load together */
|
|
83
|
+
export declare function createServer(options: ServerOptions): Promise<KoshServer>;
|
|
84
|
+
/**
|
|
85
|
+
* Listens and shuts down gracefully on SIGTERM / SIGINT (in-flight requests finish, then connections close).
|
|
86
|
+
*/
|
|
87
|
+
export declare function startServer(server: KoshServer, options?: {
|
|
88
|
+
readonly port?: number;
|
|
89
|
+
readonly host?: string;
|
|
90
|
+
readonly onShutdown?: () => Promise<void>;
|
|
91
|
+
}): Promise<string>;
|
|
92
|
+
export type { FastifyInstance };
|
|
93
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Kosh server (RFC 0002, CLAUDE.md robustness): Fastify with Pino logs (request ids, redacted credentials),
|
|
3
|
+
* authentication through an {@link Authenticator}, one Shutro transaction per request (`request.env`), RFC 9457
|
|
4
|
+
* errors, rate limiting on Valkey, OpenTelemetry spans, health checks, and graceful shutdown.
|
|
5
|
+
*/
|
|
6
|
+
import rateLimit from '@fastify/rate-limit';
|
|
7
|
+
import Fastify, {} from 'fastify';
|
|
8
|
+
import { Valkey } from 'iovalkey';
|
|
9
|
+
import { idempotencyTableStatements } from './api/idempotency.js';
|
|
10
|
+
import { publicApiModels } from './api/metadata.js';
|
|
11
|
+
import { Navigation } from './api/navigation.js';
|
|
12
|
+
import { openApiDocument } from './api/openapi.js';
|
|
13
|
+
import { registerRestApi } from './api/rest.js';
|
|
14
|
+
import { ViewRegistry } from './api/views.js';
|
|
15
|
+
import { registerAuthRoutes } from './auth/routes.js';
|
|
16
|
+
import { AuthService } from './auth/service.js';
|
|
17
|
+
import { HttpProblem, problemOf } from './errors.js';
|
|
18
|
+
import { denyAll, runOptionsOf } from './identity.js';
|
|
19
|
+
import { orm, pg } from './orm.js';
|
|
20
|
+
import { buildKoshRegistry, KOSH_VERSION } from './registry.js';
|
|
21
|
+
import { endSpan, tracer } from './telemetry.js';
|
|
22
|
+
const REDACT = [
|
|
23
|
+
'req.headers.authorization',
|
|
24
|
+
'req.headers.cookie',
|
|
25
|
+
'req.headers["x-api-key"]',
|
|
26
|
+
'res.headers["set-cookie"]',
|
|
27
|
+
];
|
|
28
|
+
/** Creates the server (not listening yet). @throws {ServerConfigError} for modules that cannot load together */
|
|
29
|
+
export async function createServer(options) {
|
|
30
|
+
const registry = buildKoshRegistry(options.modules, {
|
|
31
|
+
...(options.security === undefined ? {} : { security: options.security }),
|
|
32
|
+
});
|
|
33
|
+
const driver = pg.createDriver({
|
|
34
|
+
url: options.databaseUrl,
|
|
35
|
+
max: options.poolSize ?? 10,
|
|
36
|
+
parseNumeric: orm.toDecimal,
|
|
37
|
+
applicationName: 'kosh',
|
|
38
|
+
});
|
|
39
|
+
const hasBase = registry.registry.models.has('auth.session');
|
|
40
|
+
let shutro;
|
|
41
|
+
const auth = hasBase ? new AuthService(() => shutro, options.auth) : undefined;
|
|
42
|
+
const verify = auth !== undefined && (options.verifyIdentities ?? options.authenticate === undefined);
|
|
43
|
+
shutro = orm.createShutro({
|
|
44
|
+
driver,
|
|
45
|
+
registry: registry.registry,
|
|
46
|
+
runtimeSecurity: true, // roles, access, rules and field security edited by administrators (RFC 0003)
|
|
47
|
+
...(verify ? { identity: (uid) => auth.verify(uid) } : {}),
|
|
48
|
+
});
|
|
49
|
+
const authenticate = options.authenticate ??
|
|
50
|
+
(auth === undefined ? denyAll : (request) => auth.authenticate(request));
|
|
51
|
+
const valkey = options.valkeyUrl === undefined
|
|
52
|
+
? undefined
|
|
53
|
+
: new Valkey(options.valkeyUrl, { lazyConnect: true, maxRetriesPerRequest: 2 });
|
|
54
|
+
if (valkey !== undefined)
|
|
55
|
+
await valkey.connect();
|
|
56
|
+
const app = Fastify({
|
|
57
|
+
logger: options.logger ?? { level: process.env['LOG_LEVEL'] ?? 'info', redact: REDACT },
|
|
58
|
+
genReqId: (req) => {
|
|
59
|
+
const given = req.headers['x-request-id'];
|
|
60
|
+
return typeof given === 'string' && /^[\w.-]{1,128}$/.test(given)
|
|
61
|
+
? given
|
|
62
|
+
: crypto.randomUUID();
|
|
63
|
+
},
|
|
64
|
+
requestIdHeader: false,
|
|
65
|
+
return503OnClosing: true,
|
|
66
|
+
});
|
|
67
|
+
app.decorateRequest('identity', null);
|
|
68
|
+
app.decorateRequest('span', undefined);
|
|
69
|
+
// A generic method: Fastify's decorator typing cannot express it; the augmentation above types callers.
|
|
70
|
+
app.decorateRequest('env', function env(fn, options = {}) {
|
|
71
|
+
const request = this;
|
|
72
|
+
if (request.identity === null)
|
|
73
|
+
throw new HttpProblem(401, 'unauthenticated', 'Not authenticated', 'This route needs an authenticated user.', 'Sign in first.');
|
|
74
|
+
const runOptions = {
|
|
75
|
+
...runOptionsOf(request, request.identity),
|
|
76
|
+
...(options.write === true ? { readOnly: false } : {}),
|
|
77
|
+
};
|
|
78
|
+
return tracer.startActiveSpan('shutro.run', async (span) => {
|
|
79
|
+
try {
|
|
80
|
+
return await shutro.run(runOptions, fn);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
span.end();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
app.addHook('onRequest', async (request, reply) => {
|
|
88
|
+
reply.header('x-request-id', request.id);
|
|
89
|
+
request.span = tracer.startSpan(`HTTP ${request.method}`, {
|
|
90
|
+
attributes: {
|
|
91
|
+
'http.request.method': request.method,
|
|
92
|
+
'url.path': request.url.split('?')[0] ?? '',
|
|
93
|
+
'kosh.request_id': request.id,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
// Authentication runs before rate limiting, so limits are per user when there is one.
|
|
98
|
+
app.addHook('onRequest', async (request) => {
|
|
99
|
+
const config = request.routeOptions.config;
|
|
100
|
+
if (config.public === true || config.auth === false)
|
|
101
|
+
return;
|
|
102
|
+
request.identity = await authenticate(request);
|
|
103
|
+
});
|
|
104
|
+
if (options.rateLimit?.max !== 0)
|
|
105
|
+
await app.register(rateLimit, {
|
|
106
|
+
global: true,
|
|
107
|
+
max: options.rateLimit?.max ?? 600,
|
|
108
|
+
timeWindow: options.rateLimit?.timeWindow ?? '1 minute',
|
|
109
|
+
...(valkey === undefined ? {} : { redis: valkey, nameSpace: 'kosh:ratelimit:' }),
|
|
110
|
+
keyGenerator: (request) => request.identity === null ? `ip:${request.ip}` : `user:${request.identity.user}`,
|
|
111
|
+
allowList: (request) => request.routeOptions.config.public === true,
|
|
112
|
+
errorResponseBuilder: (_request, context) => new HttpProblem(429, 'rate-limited', 'Too many requests', `Limit of ${context.max} requests per ${context.after} reached.`, `Retry after ${context.after}.`),
|
|
113
|
+
});
|
|
114
|
+
// While shutting down, responses ask the client to close its connection, so keep-alive sockets that were busy
|
|
115
|
+
// when the shutdown began do not hold the server open afterwards.
|
|
116
|
+
let shuttingDown = false;
|
|
117
|
+
app.addHook('onSend', async (_request, reply) => {
|
|
118
|
+
if (shuttingDown)
|
|
119
|
+
reply.header('connection', 'close');
|
|
120
|
+
});
|
|
121
|
+
app.addHook('onResponse', async (request, reply) => {
|
|
122
|
+
if (request.span !== undefined) {
|
|
123
|
+
request.span.setAttribute('http.route', request.routeOptions.url ?? '');
|
|
124
|
+
endSpan(request.span, reply.statusCode);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
app.setErrorHandler(async (error, request, reply) => {
|
|
128
|
+
const problem = problemOf(error);
|
|
129
|
+
if (problem.status >= 500)
|
|
130
|
+
request.log.error({ err: error }, 'request failed');
|
|
131
|
+
else
|
|
132
|
+
request.log.info({ problem: problem.type, status: problem.status }, 'request refused');
|
|
133
|
+
request.span?.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
134
|
+
await reply.status(problem.status).type('application/problem+json').send(problem);
|
|
135
|
+
});
|
|
136
|
+
app.setNotFoundHandler(async (request, reply) => {
|
|
137
|
+
await reply
|
|
138
|
+
.status(404)
|
|
139
|
+
.type('application/problem+json')
|
|
140
|
+
.send({
|
|
141
|
+
type: 'https://kosh.dev/problems/not-found',
|
|
142
|
+
title: 'Not found',
|
|
143
|
+
status: 404,
|
|
144
|
+
detail: `No route ${request.method} ${request.url.split('?')[0] ?? ''}.`,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
if (auth !== undefined)
|
|
148
|
+
registerAuthRoutes(app, auth, { loginLimit: options.auth?.loginLimit ?? 10 });
|
|
149
|
+
// The generated REST API and its OpenAPI document (RFC 0002 §6.3–6.4).
|
|
150
|
+
for (const statement of idempotencyTableStatements())
|
|
151
|
+
await driver.query(statement);
|
|
152
|
+
const models = publicApiModels(registry);
|
|
153
|
+
const views = new ViewRegistry(registry.loaded.modules, models); // validated at startup
|
|
154
|
+
let openapi;
|
|
155
|
+
registerRestApi(app, {
|
|
156
|
+
models,
|
|
157
|
+
driver,
|
|
158
|
+
openapi: () => {
|
|
159
|
+
openapi ??= openApiDocument(models.values(), { title: 'Kosh API', version: KOSH_VERSION });
|
|
160
|
+
return openapi;
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
new Navigation(registry.loaded).register(app);
|
|
164
|
+
views.register(app);
|
|
165
|
+
// Module routes (admin APIs, custom endpoints), in dependency order.
|
|
166
|
+
const context = { app, shutro, driver, registry, auth };
|
|
167
|
+
for (const m of registry.loaded.modules)
|
|
168
|
+
if (m.routes !== undefined)
|
|
169
|
+
await m.routes(context);
|
|
170
|
+
// Health checks: public, not rate limited. Liveness: the process answers. Readiness: dependencies answer.
|
|
171
|
+
app.get('/health/live', { config: { public: true } }, async () => ({ status: 'ok' }));
|
|
172
|
+
app.get('/health/ready', { config: { public: true } }, async (_request, reply) => {
|
|
173
|
+
const checks = {};
|
|
174
|
+
try {
|
|
175
|
+
await driver.query({ text: 'SELECT 1', params: [] });
|
|
176
|
+
checks['database'] = 'ok';
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
checks['database'] = 'down';
|
|
180
|
+
}
|
|
181
|
+
if (valkey !== undefined)
|
|
182
|
+
checks['valkey'] = (await valkey.ping().catch(() => 'down')) === 'PONG' ? 'ok' : 'down';
|
|
183
|
+
const ready = Object.values(checks).every((c) => c === 'ok');
|
|
184
|
+
await reply.status(ready ? 200 : 503).send({ status: ready ? 'ok' : 'unavailable', checks });
|
|
185
|
+
});
|
|
186
|
+
let closing;
|
|
187
|
+
return {
|
|
188
|
+
app,
|
|
189
|
+
shutro,
|
|
190
|
+
driver,
|
|
191
|
+
registry,
|
|
192
|
+
auth,
|
|
193
|
+
close() {
|
|
194
|
+
closing ??= (async () => {
|
|
195
|
+
shuttingDown = true;
|
|
196
|
+
await app.close(); // stops accepting; waits for in-flight requests
|
|
197
|
+
await driver.end({ timeoutSeconds: 10 });
|
|
198
|
+
await valkey?.quit().catch(() => undefined);
|
|
199
|
+
})();
|
|
200
|
+
return closing;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Listens and shuts down gracefully on SIGTERM / SIGINT (in-flight requests finish, then connections close).
|
|
206
|
+
*/
|
|
207
|
+
export async function startServer(server, options = {}) {
|
|
208
|
+
const address = await server.app.listen({
|
|
209
|
+
port: options.port ?? 3000,
|
|
210
|
+
host: options.host ?? '0.0.0.0',
|
|
211
|
+
});
|
|
212
|
+
const stop = (signal) => {
|
|
213
|
+
server.app.log.info({ signal }, 'shutting down');
|
|
214
|
+
void server
|
|
215
|
+
.close()
|
|
216
|
+
.then(() => options.onShutdown?.())
|
|
217
|
+
.finally(() => process.exit(0));
|
|
218
|
+
};
|
|
219
|
+
process.once('SIGTERM', () => stop('SIGTERM'));
|
|
220
|
+
process.once('SIGINT', () => stop('SIGINT'));
|
|
221
|
+
return address;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|