@kelpie/server 0.4.1 → 0.5.1
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/dist/app.d.ts.map +1 -1
- package/dist/app.js +27 -0
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/appUrl.d.ts +27 -0
- package/dist/lib/appUrl.d.ts.map +1 -0
- package/dist/lib/appUrl.js +53 -0
- package/dist/lib/appUrl.js.map +1 -0
- package/dist/lib/clientIp.d.ts +24 -0
- package/dist/lib/clientIp.d.ts.map +1 -0
- package/dist/lib/clientIp.js +34 -0
- package/dist/lib/clientIp.js.map +1 -0
- package/dist/lib/config.d.ts +6 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +2 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/egress.d.ts +44 -0
- package/dist/lib/egress.d.ts.map +1 -0
- package/dist/lib/egress.js +111 -0
- package/dist/lib/egress.js.map +1 -0
- package/dist/lib/ids.d.ts +2 -0
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/ids.js +2 -0
- package/dist/lib/ids.js.map +1 -1
- package/dist/lib/rateLimit.d.ts +12 -4
- package/dist/lib/rateLimit.d.ts.map +1 -1
- package/dist/lib/rateLimit.js +6 -0
- package/dist/lib/rateLimit.js.map +1 -1
- package/dist/modules/agent-tasks/dispatch.d.ts +4 -2
- package/dist/modules/agent-tasks/dispatch.d.ts.map +1 -1
- package/dist/modules/agent-tasks/dispatch.js +4 -2
- package/dist/modules/agent-tasks/dispatch.js.map +1 -1
- package/dist/modules/agent-tasks/index.d.ts.map +1 -1
- package/dist/modules/agent-tasks/index.js +3 -1
- package/dist/modules/agent-tasks/index.js.map +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +10 -3
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/repository.d.ts +13 -1
- package/dist/modules/auth/repository.d.ts.map +1 -1
- package/dist/modules/auth/repository.js +32 -1
- package/dist/modules/auth/repository.js.map +1 -1
- package/dist/modules/auth/routes.d.ts.map +1 -1
- package/dist/modules/auth/routes.js +29 -6
- package/dist/modules/auth/routes.js.map +1 -1
- package/dist/modules/auth/schema.d.ts +130 -0
- package/dist/modules/auth/schema.d.ts.map +1 -1
- package/dist/modules/auth/schema.js +16 -0
- package/dist/modules/auth/schema.js.map +1 -1
- package/dist/modules/auth/service.d.ts +7 -1
- package/dist/modules/auth/service.d.ts.map +1 -1
- package/dist/modules/auth/service.js +53 -4
- package/dist/modules/auth/service.js.map +1 -1
- package/dist/modules/deals/repository.d.ts.map +1 -1
- package/dist/modules/deals/repository.js +1 -0
- package/dist/modules/deals/repository.js.map +1 -1
- package/dist/modules/decisions/repository.d.ts.map +1 -1
- package/dist/modules/decisions/repository.js +8 -7
- package/dist/modules/decisions/repository.js.map +1 -1
- package/dist/modules/forms/index.d.ts.map +1 -1
- package/dist/modules/forms/index.js +6 -1
- package/dist/modules/forms/index.js.map +1 -1
- package/dist/modules/forms/publicRoutes.d.ts +3 -0
- package/dist/modules/forms/publicRoutes.d.ts.map +1 -1
- package/dist/modules/forms/publicRoutes.js +10 -4
- package/dist/modules/forms/publicRoutes.js.map +1 -1
- package/dist/modules/forms/submission.d.ts +3 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +6 -0
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/opportunities/repository.d.ts.map +1 -1
- package/dist/modules/opportunities/repository.js +1 -0
- package/dist/modules/opportunities/repository.js.map +1 -1
- package/dist/modules/partnerships/repository.d.ts.map +1 -1
- package/dist/modules/partnerships/repository.js +1 -0
- package/dist/modules/partnerships/repository.js.map +1 -1
- package/dist/modules/raises/repository.d.ts.map +1 -1
- package/dist/modules/raises/repository.js +1 -0
- package/dist/modules/raises/repository.js.map +1 -1
- package/dist/modules/rate-limit/middleware.d.ts +4 -4
- package/dist/modules/rate-limit/middleware.d.ts.map +1 -1
- package/dist/modules/rate-limit/middleware.js +57 -11
- package/dist/modules/rate-limit/middleware.js.map +1 -1
- package/dist/modules/webhooks/delivery.d.ts +5 -2
- package/dist/modules/webhooks/delivery.d.ts.map +1 -1
- package/dist/modules/webhooks/delivery.js +5 -2
- package/dist/modules/webhooks/delivery.js.map +1 -1
- package/dist/modules/webhooks/index.d.ts.map +1 -1
- package/dist/modules/webhooks/index.js +3 -1
- package/dist/modules/webhooks/index.js.map +1 -1
- package/dist/modules/workspace/capabilities.d.ts +10 -1
- package/dist/modules/workspace/capabilities.d.ts.map +1 -1
- package/dist/modules/workspace/capabilities.js +13 -0
- package/dist/modules/workspace/capabilities.js.map +1 -1
- package/dist/modules/workspace/idempotencyMiddleware.d.ts.map +1 -1
- package/dist/modules/workspace/idempotencyMiddleware.js +14 -1
- package/dist/modules/workspace/idempotencyMiddleware.js.map +1 -1
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +5 -1
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/repository.d.ts +1 -1
- package/dist/modules/workspace/repository.d.ts.map +1 -1
- package/dist/modules/workspace/repository.js +15 -9
- package/dist/modules/workspace/repository.js.map +1 -1
- package/dist/modules/workspace/routes.d.ts +0 -4
- package/dist/modules/workspace/routes.d.ts.map +1 -1
- package/dist/modules/workspace/routes.js +2 -7
- package/dist/modules/workspace/routes.js.map +1 -1
- package/dist/modules/workspace/service.d.ts +4 -2
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +42 -17
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/modules/workspace/tools.d.ts.map +1 -1
- package/dist/modules/workspace/tools.js +6 -7
- package/dist/modules/workspace/tools.js.map +1 -1
- package/dist/modules/workspace/workspaceAccessMiddleware.d.ts +8 -0
- package/dist/modules/workspace/workspaceAccessMiddleware.d.ts.map +1 -0
- package/dist/modules/workspace/workspaceAccessMiddleware.js +58 -0
- package/dist/modules/workspace/workspaceAccessMiddleware.js.map +1 -0
- package/dist/operator.d.ts +40 -0
- package/dist/operator.d.ts.map +1 -0
- package/dist/operator.js +50 -0
- package/dist/operator.js.map +1 -0
- package/dist/runtime/module.d.ts +25 -1
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts +16 -1
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +34 -0
- package/dist/runtime/registry.js.map +1 -1
- package/dist/testing/client.d.ts +3 -1
- package/dist/testing/client.d.ts.map +1 -1
- package/dist/testing/client.js +19 -2
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/environment.d.ts +2 -0
- package/dist/testing/environment.d.ts.map +1 -1
- package/dist/testing/environment.js +3 -0
- package/dist/testing/environment.js.map +1 -1
- package/dist/webBundle.d.ts +8 -0
- package/dist/webBundle.d.ts.map +1 -1
- package/dist/webBundle.js +9 -7
- package/dist/webBundle.js.map +1 -1
- package/migrations/0018_hard_mystique.sql +12 -0
- package/migrations/meta/0018_snapshot.json +5113 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +2 -2
- package/src/app.ts +32 -0
- package/src/index.ts +9 -1
- package/src/lib/appUrl.ts +64 -0
- package/src/lib/clientIp.ts +41 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/egress.ts +149 -0
- package/src/lib/ids.ts +2 -0
- package/src/lib/rateLimit.ts +16 -4
- package/src/modules/agent-tasks/dispatch.ts +9 -2
- package/src/modules/agent-tasks/index.ts +3 -1
- package/src/modules/auth/index.ts +12 -5
- package/src/modules/auth/repository.ts +56 -1
- package/src/modules/auth/routes.ts +34 -6
- package/src/modules/auth/schema.ts +17 -0
- package/src/modules/auth/service.ts +78 -5
- package/src/modules/deals/repository.ts +1 -0
- package/src/modules/decisions/repository.ts +8 -7
- package/src/modules/forms/index.ts +6 -1
- package/src/modules/forms/publicRoutes.ts +14 -4
- package/src/modules/forms/submission.ts +10 -0
- package/src/modules/opportunities/repository.ts +1 -0
- package/src/modules/partnerships/repository.ts +1 -0
- package/src/modules/raises/repository.ts +1 -0
- package/src/modules/rate-limit/middleware.ts +74 -11
- package/src/modules/webhooks/delivery.ts +10 -2
- package/src/modules/webhooks/index.ts +3 -1
- package/src/modules/workspace/capabilities.ts +15 -1
- package/src/modules/workspace/idempotencyMiddleware.ts +17 -1
- package/src/modules/workspace/index.ts +6 -1
- package/src/modules/workspace/repository.ts +15 -8
- package/src/modules/workspace/routes.ts +0 -8
- package/src/modules/workspace/service.ts +53 -20
- package/src/modules/workspace/tools.ts +7 -21
- package/src/modules/workspace/workspaceAccessMiddleware.ts +76 -0
- package/src/runtime/module.ts +25 -1
- package/src/runtime/registry.ts +59 -1
- package/src/testing/client.ts +22 -2
- package/src/testing/environment.ts +4 -0
- package/src/webBundle.ts +21 -7
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AppError } from '../../lib/errors.js';
|
|
2
|
+
import { actorWorkspaceId } from '../auth/actor.js';
|
|
3
|
+
import { resolveActorFrom } from '../auth/credentials.js';
|
|
4
|
+
import { WORKSPACE_ACCESS } from './capabilities.js';
|
|
5
|
+
/**
|
|
6
|
+
* `POST /v1/*` and every `/mcp` call carry a fixed workspace for the whole
|
|
7
|
+
* request (`api.md`): a session or key resolves to one `Actor.workspaceId`,
|
|
8
|
+
* and every query it makes is scoped to that workspace server-side. This
|
|
9
|
+
* blocks the request outright when that workspace's `workspace.access`
|
|
10
|
+
* entitlement is not granted — inert in a self-hosted assembly, since
|
|
11
|
+
* nothing registers a `GrantProvider` that answers it there
|
|
12
|
+
* (`capabilities.ts`).
|
|
13
|
+
*
|
|
14
|
+
* Mounted the same way as `createIdempotencyMiddleware`: tolerates the
|
|
15
|
+
* no-credential case, since an unauthenticated `/v1/auth/*` call has
|
|
16
|
+
* nothing to check, and ahead of idempotency in `app.ts` so a blocked
|
|
17
|
+
* request never reserves a key it will not be allowed to spend.
|
|
18
|
+
*
|
|
19
|
+
* Exempts `/v1/auth/*` and `/v1/account*`: they manage the *account*, not
|
|
20
|
+
* the workspace, and a member of a workspace this gate has locked out
|
|
21
|
+
* still needs to sign in, sign out, and see their own account to have any
|
|
22
|
+
* chance of understanding why. There is no `/v1` endpoint to switch a
|
|
23
|
+
* session to a different workspace membership — a session is bound to one
|
|
24
|
+
* at login — so there is nothing further to exempt for that case.
|
|
25
|
+
*/
|
|
26
|
+
function isExempt(path) {
|
|
27
|
+
return path.startsWith('/v1/auth/') || path.startsWith('/v1/account');
|
|
28
|
+
}
|
|
29
|
+
export function createWorkspaceAccessMiddleware(dependencies) {
|
|
30
|
+
return async (context, next) => {
|
|
31
|
+
if (isExempt(context.req.path)) {
|
|
32
|
+
await next();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const actor = await resolveActorFrom(dependencies, context).catch((error) => {
|
|
36
|
+
if (error instanceof AppError && error.code === 'unauthorized') {
|
|
37
|
+
// No credential to check a workspace against. The handler this
|
|
38
|
+
// reaches answers its own 401; there is nothing this gate can add.
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
});
|
|
43
|
+
const workspaceId = actor === undefined ? null : actorWorkspaceId(actor);
|
|
44
|
+
// Between signup and a first workspace there is nothing to gate
|
|
45
|
+
// (`POST /v1/workspaces`, `POST /v1/invites/accept`): the same case
|
|
46
|
+
// `createIdempotencyMiddleware` lets through unscoped.
|
|
47
|
+
if (workspaceId === null) {
|
|
48
|
+
await next();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const entitlement = await dependencies.entitlements.check(workspaceId, WORKSPACE_ACCESS.name);
|
|
52
|
+
if (entitlement.kind === 'flag' && !entitlement.granted) {
|
|
53
|
+
throw new AppError('entitlement_required', 'This workspace does not have access');
|
|
54
|
+
}
|
|
55
|
+
await next();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=workspaceAccessMiddleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceAccessMiddleware.js","sourceRoot":"","sources":["../../../src/modules/workspace/workspaceAccessMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AACvE,CAAC;AAMD,MAAM,UAAU,+BAA+B,CAC7C,YAAmD;IAEnD,OAAO,KAAK,EAAE,OAAgB,EAAE,IAAI,EAAE,EAAE;QACtC,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,EAAE,CAAA;YACZ,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACnF,IAAI,KAAK,YAAY,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBAC/D,+DAA+D;gBAC/D,mEAAmE;gBACnE,OAAO,SAAS,CAAA;YAClB,CAAC;YAED,MAAM,KAAK,CAAA;QACb,CAAC,CAAC,CAAA;QAEF,MAAM,WAAW,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAA;QAExE,gEAAgE;QAChE,oEAAoE;QACpE,uDAAuD;QACvD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,EAAE,CAAA;YACZ,OAAM;QACR,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,KAAK,CAAC,WAAW,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAA;QAE7F,IAAI,WAAW,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACxD,MAAM,IAAI,QAAQ,CAAC,sBAAsB,EAAE,qCAAqC,CAAC,CAAA;QACnF,CAAC;QAED,MAAM,IAAI,EAAE,CAAA;IACd,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { CredentialDependencies } from './modules/auth/credentials.ts';
|
|
3
|
+
import type { ModuleRouter } from './runtime/registry.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The operator surface: deployment-level support tooling, mounted by
|
|
6
|
+
* `createApp` at `OPERATOR_ROUTE_PREFIX` the same way the MCP transport is
|
|
7
|
+
* mounted at `/mcp`.
|
|
8
|
+
*
|
|
9
|
+
* It is not part of `/v1` because operator access is a deployment concern,
|
|
10
|
+
* not a workspace role. A workspace or personal API key is refused outright:
|
|
11
|
+
* a key is bound to one workspace and carries that workspace's authority,
|
|
12
|
+
* which is exactly the credential this surface must not answer to. The only
|
|
13
|
+
* way in is a browser session belonging to an account named in
|
|
14
|
+
* `SUPERUSER_EMAILS`.
|
|
15
|
+
*
|
|
16
|
+
* Modules contribute the routes (`ModuleContext.operatorRoutes`); core ships
|
|
17
|
+
* the guard and the mount and no routes of its own. The guard runs ahead of
|
|
18
|
+
* every contribution, so a contributed handler only ever sees a superuser's
|
|
19
|
+
* request.
|
|
20
|
+
*/
|
|
21
|
+
export declare const OPERATOR_ROUTE_PREFIX = "/operator/api";
|
|
22
|
+
export interface OperatorDependencies {
|
|
23
|
+
readonly credentials: CredentialDependencies;
|
|
24
|
+
/** Lower-cased operator emails from `SUPERUSER_EMAILS`. Empty means nobody. */
|
|
25
|
+
readonly superuserEmails: ReadonlySet<string>;
|
|
26
|
+
readonly routers: readonly ModuleRouter[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Three checks, in order:
|
|
30
|
+
*
|
|
31
|
+
* 1. A credential must be present and valid (401 otherwise).
|
|
32
|
+
* 2. It must be a session, not an API key (403 otherwise).
|
|
33
|
+
* 3. The session's account email must be on the allowlist (403 otherwise).
|
|
34
|
+
*
|
|
35
|
+
* The email is read from the database on every request rather than trusted
|
|
36
|
+
* from anywhere else: a session carries only a user id, and re-reading means
|
|
37
|
+
* an email change takes effect on the next request, not the next sign-in.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createOperatorRouter(dependencies: OperatorDependencies): Hono;
|
|
40
|
+
//# sourceMappingURL=operator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../src/operator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAK3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAE3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,qBAAqB,kBAAkB,CAAA;AAEpD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAA;IAC5C,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;CAC1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAmB7E"}
|
package/dist/operator.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { requireSessionActor } from './lib/actor.js';
|
|
3
|
+
import { AppError } from './lib/errors.js';
|
|
4
|
+
import { resolveActorFrom } from './modules/auth/credentials.js';
|
|
5
|
+
import * as authRepository from './modules/auth/repository.js';
|
|
6
|
+
/**
|
|
7
|
+
* The operator surface: deployment-level support tooling, mounted by
|
|
8
|
+
* `createApp` at `OPERATOR_ROUTE_PREFIX` the same way the MCP transport is
|
|
9
|
+
* mounted at `/mcp`.
|
|
10
|
+
*
|
|
11
|
+
* It is not part of `/v1` because operator access is a deployment concern,
|
|
12
|
+
* not a workspace role. A workspace or personal API key is refused outright:
|
|
13
|
+
* a key is bound to one workspace and carries that workspace's authority,
|
|
14
|
+
* which is exactly the credential this surface must not answer to. The only
|
|
15
|
+
* way in is a browser session belonging to an account named in
|
|
16
|
+
* `SUPERUSER_EMAILS`.
|
|
17
|
+
*
|
|
18
|
+
* Modules contribute the routes (`ModuleContext.operatorRoutes`); core ships
|
|
19
|
+
* the guard and the mount and no routes of its own. The guard runs ahead of
|
|
20
|
+
* every contribution, so a contributed handler only ever sees a superuser's
|
|
21
|
+
* request.
|
|
22
|
+
*/
|
|
23
|
+
export const OPERATOR_ROUTE_PREFIX = '/operator/api';
|
|
24
|
+
/**
|
|
25
|
+
* Three checks, in order:
|
|
26
|
+
*
|
|
27
|
+
* 1. A credential must be present and valid (401 otherwise).
|
|
28
|
+
* 2. It must be a session, not an API key (403 otherwise).
|
|
29
|
+
* 3. The session's account email must be on the allowlist (403 otherwise).
|
|
30
|
+
*
|
|
31
|
+
* The email is read from the database on every request rather than trusted
|
|
32
|
+
* from anywhere else: a session carries only a user id, and re-reading means
|
|
33
|
+
* an email change takes effect on the next request, not the next sign-in.
|
|
34
|
+
*/
|
|
35
|
+
export function createOperatorRouter(dependencies) {
|
|
36
|
+
const router = new Hono();
|
|
37
|
+
router.use('*', async (context, next) => {
|
|
38
|
+
const actor = requireSessionActor(await resolveActorFrom(dependencies.credentials, context));
|
|
39
|
+
const user = await authRepository.findUserById(dependencies.credentials.db, actor.userId);
|
|
40
|
+
if (user === undefined || !dependencies.superuserEmails.has(user.email.toLowerCase())) {
|
|
41
|
+
throw new AppError('forbidden', 'This account is not on the operator allowlist');
|
|
42
|
+
}
|
|
43
|
+
await next();
|
|
44
|
+
});
|
|
45
|
+
for (const contribution of dependencies.routers) {
|
|
46
|
+
router.route('/', contribution.router);
|
|
47
|
+
}
|
|
48
|
+
return router;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=operator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator.js","sourceRoot":"","sources":["../src/operator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,KAAK,cAAc,MAAM,8BAA8B,CAAA;AAG9D;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAA;AASpD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,YAAkC;IACrE,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAA;IAEzB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACtC,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,gBAAgB,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAA;QAC5F,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,YAAY,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QAEzF,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACtF,MAAM,IAAI,QAAQ,CAAC,WAAW,EAAE,+CAA+C,CAAC,CAAA;QAClF,CAAC;QAED,MAAM,IAAI,EAAE,CAAA;IACd,CAAC,CAAC,CAAA;IAEF,KAAK,MAAM,YAAY,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,CAAA;IACxC,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/dist/runtime/module.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Hono } from 'hono';
|
|
1
|
+
import type { Handler, Hono, MiddlewareHandler } from 'hono';
|
|
2
2
|
import type { ZodType } from 'zod';
|
|
3
3
|
import type { Actor } from '../lib/actor.ts';
|
|
4
4
|
import type { Database } from '../lib/database.ts';
|
|
@@ -85,6 +85,30 @@ export interface ModuleContext extends ModuleServices {
|
|
|
85
85
|
* scope every query to that. There is no other way in.
|
|
86
86
|
*/
|
|
87
87
|
publicRoutes(mount: (router: Hono) => void): void;
|
|
88
|
+
/**
|
|
89
|
+
* Declares one route at its real, full path on the app itself, outside
|
|
90
|
+
* `/v1`. Core applies the declaration; no router exists between the module
|
|
91
|
+
* and the app.
|
|
92
|
+
*
|
|
93
|
+
* Nothing is put in front of it: no actor resolution, no workspace, no
|
|
94
|
+
* `module.<id>` capability gate, and workspace settings cannot toggle it.
|
|
95
|
+
* A surface declared this way owns its access rules, normally by pairing
|
|
96
|
+
* these with `appMiddleware`. Paths at or under `/v1`, `/mcp`, or
|
|
97
|
+
* `/healthz` are refused at boot: those are core's surfaces, and `routes`
|
|
98
|
+
* is the only way under `/v1`.
|
|
99
|
+
*/
|
|
100
|
+
appRoute(method: 'GET' | 'POST' | 'PATCH' | 'DELETE', path: string, handler: Handler): void;
|
|
101
|
+
/**
|
|
102
|
+
* Declares middleware for a path pattern (`/operator/api/*`), the other
|
|
103
|
+
* half of `appRoute`.
|
|
104
|
+
*
|
|
105
|
+
* The app applies every declared middleware before any declared route, in
|
|
106
|
+
* module registration order, so a pattern covers matching routes from
|
|
107
|
+
* every module, later-registered ones included. That is what lets one
|
|
108
|
+
* module guard a surface other modules add routes to. Reserved paths are
|
|
109
|
+
* refused as for `appRoute`.
|
|
110
|
+
*/
|
|
111
|
+
appMiddleware(pattern: string, handler: MiddlewareHandler): void;
|
|
88
112
|
schema(tables: Readonly<Record<string, unknown>>, migrationsDir: string): void;
|
|
89
113
|
readonly mcp: McpToolRegistry;
|
|
90
114
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/runtime/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/runtime/module.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import type { Context } from 'hono';
|
|
2
|
+
import type { Context, Handler, MiddlewareHandler } from 'hono';
|
|
3
3
|
import type { Actor } from '../lib/actor.ts';
|
|
4
4
|
import type { Environment } from '../lib/config.ts';
|
|
5
5
|
import type { Logger } from '../lib/logger.ts';
|
|
@@ -15,6 +15,10 @@ export interface ModuleContributions {
|
|
|
15
15
|
readonly routers: readonly ModuleRouter[];
|
|
16
16
|
/** Routers for `/v1/public`: no credentials, CORS open. `architecture.md` boot step 5. */
|
|
17
17
|
readonly publicRouters: readonly ModuleRouter[];
|
|
18
|
+
/** Middleware declared on the app itself. Applied before every `appRoutes` entry. */
|
|
19
|
+
readonly appMiddleware: readonly AppMiddlewareContribution[];
|
|
20
|
+
/** Routes declared at full paths on the app itself, outside `/v1`. */
|
|
21
|
+
readonly appRoutes: readonly AppRouteContribution[];
|
|
18
22
|
readonly schemas: readonly SchemaContribution[];
|
|
19
23
|
readonly mcpTools: readonly McpTool[];
|
|
20
24
|
/** The bus every module subscribed to. Services publish through it after commit. */
|
|
@@ -26,6 +30,17 @@ export interface ModuleRouter {
|
|
|
26
30
|
readonly moduleId: string;
|
|
27
31
|
readonly router: Hono;
|
|
28
32
|
}
|
|
33
|
+
export interface AppRouteContribution {
|
|
34
|
+
readonly moduleId: string;
|
|
35
|
+
readonly method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly handler: Handler;
|
|
38
|
+
}
|
|
39
|
+
export interface AppMiddlewareContribution {
|
|
40
|
+
readonly moduleId: string;
|
|
41
|
+
readonly pattern: string;
|
|
42
|
+
readonly handler: MiddlewareHandler;
|
|
43
|
+
}
|
|
29
44
|
/**
|
|
30
45
|
* What `registerModules` needs.
|
|
31
46
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/runtime/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/runtime/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE/D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAE9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAE5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EAGP,cAAc,EACd,kBAAkB,EACnB,MAAM,aAAa,CAAA;AAIpB;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,EAAE,SAAS,YAAY,EAAE,CAAA;IAC/C,qFAAqF;IACrF,QAAQ,CAAC,aAAa,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC5D,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACnD,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAA;IACrC,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAA;IACzB,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAA;CAC3C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;CACpC;AAwBD;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,4FAA4F;IAC5F,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IACtC,yEAAyE;IACzE,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAA;IACvD,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;IACjC;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAA;IACrE;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAA;CAC3E;AA8HD;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA2EjG"}
|
package/dist/runtime/registry.js
CHANGED
|
@@ -5,6 +5,25 @@ import { createEntitlementRegistry, requireCapability } from './entitlements.js'
|
|
|
5
5
|
import { createEventBus } from './events.js';
|
|
6
6
|
import { createModuleConfigProvider, moduleCapabilityName, validateModuleConfig } from './moduleConfig.js';
|
|
7
7
|
import { ModuleBootError, orderModules } from './order.js';
|
|
8
|
+
/**
|
|
9
|
+
* Paths `appRoute`/`appMiddleware` may not touch. `/v1` because `routes` with
|
|
10
|
+
* its gates is the only way there, the rest because they are core's own
|
|
11
|
+
* surfaces. `/mcp` mirrors `MCP_ROUTE_PREFIX`, written out because `runtime/`
|
|
12
|
+
* must not import a feature module.
|
|
13
|
+
*/
|
|
14
|
+
const RESERVED_APP_PATHS = ['/v1', '/mcp', '/healthz'];
|
|
15
|
+
function assertMountablePath(moduleId, kind, path) {
|
|
16
|
+
if (!path.startsWith('/')) {
|
|
17
|
+
throw new ModuleBootError([`module "${moduleId}" declares ${kind} "${path}", which must start with "/"`]);
|
|
18
|
+
}
|
|
19
|
+
for (const reserved of RESERVED_APP_PATHS) {
|
|
20
|
+
if (path === reserved || path.startsWith(`${reserved}/`)) {
|
|
21
|
+
throw new ModuleBootError([
|
|
22
|
+
`module "${moduleId}" declares ${kind} "${path}" under "${reserved}", which is core's surface`,
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
8
27
|
function createModuleContext(module, accumulator, options, events, entitlements, moduleCatalog) {
|
|
9
28
|
return {
|
|
10
29
|
...options.services,
|
|
@@ -30,6 +49,17 @@ function createModuleContext(module, accumulator, options, events, entitlements,
|
|
|
30
49
|
mount(router);
|
|
31
50
|
accumulator.publicRouters.push({ moduleId: module.id, router });
|
|
32
51
|
},
|
|
52
|
+
// No gate on either, unlike `routes`: a surface declared here owns its
|
|
53
|
+
// own access rules, and a per-module `module.<id>` check would hand
|
|
54
|
+
// workspaces a switch over deployment tooling they do not own.
|
|
55
|
+
appRoute(method, path, handler) {
|
|
56
|
+
assertMountablePath(module.id, 'app route', path);
|
|
57
|
+
accumulator.appRoutes.push({ moduleId: module.id, method, path, handler });
|
|
58
|
+
},
|
|
59
|
+
appMiddleware(pattern, handler) {
|
|
60
|
+
assertMountablePath(module.id, 'app middleware', pattern);
|
|
61
|
+
accumulator.appMiddleware.push({ moduleId: module.id, pattern, handler });
|
|
62
|
+
},
|
|
33
63
|
schema(tables, migrationsDir) {
|
|
34
64
|
accumulator.schemas.push({ moduleId: module.id, tables, migrationsDir });
|
|
35
65
|
},
|
|
@@ -92,6 +122,8 @@ export async function registerModules(options) {
|
|
|
92
122
|
const accumulator = {
|
|
93
123
|
routers: [],
|
|
94
124
|
publicRouters: [],
|
|
125
|
+
appMiddleware: [],
|
|
126
|
+
appRoutes: [],
|
|
95
127
|
schemas: [],
|
|
96
128
|
mcpTools: [],
|
|
97
129
|
};
|
|
@@ -139,6 +171,8 @@ export async function registerModules(options) {
|
|
|
139
171
|
return {
|
|
140
172
|
routers: accumulator.routers,
|
|
141
173
|
publicRouters: accumulator.publicRouters,
|
|
174
|
+
appMiddleware: accumulator.appMiddleware,
|
|
175
|
+
appRoutes: accumulator.appRoutes,
|
|
142
176
|
schemas: accumulator.schemas,
|
|
143
177
|
mcpTools: accumulator.mcpTools,
|
|
144
178
|
events,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/runtime/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEpG,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAEhF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAU5C,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC1G,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/runtime/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/client.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import type { AppBindings } from '../app.ts';
|
|
3
|
+
import type { Database } from '../lib/database.ts';
|
|
3
4
|
/**
|
|
4
5
|
* An HTTP client for integration tests, plus the two setups almost every suite
|
|
5
6
|
* needs first: an account, and an account that owns a workspace.
|
|
@@ -30,5 +31,6 @@ export declare function readRecord(payload: unknown): Record<string, unknown>;
|
|
|
30
31
|
export declare function readList(payload: unknown): Record<string, unknown>[];
|
|
31
32
|
/** Reads `next_cursor`, which is a string or null and never absent. */
|
|
32
33
|
export declare function readCursor(payload: unknown): string | null;
|
|
33
|
-
|
|
34
|
+
/** @param db Verifies a fresh signup directly, so almost every suite can start from a working owner. */
|
|
35
|
+
export declare function createTestClient(app: Hono<AppBindings>, db: Database): TestClient;
|
|
34
36
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGlD;;;;;;GAMG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACrC;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACnF,uDAAuD;IACvD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACtC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;CACzD;AAMD,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMpE;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAMpE;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAM1D;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,QAAQ,GAAG,UAAU,CA+DjF"}
|
package/dist/testing/client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { users } from '../modules/auth/schema.js';
|
|
1
3
|
function isRecord(value) {
|
|
2
4
|
return typeof value === 'object' && value !== null;
|
|
3
5
|
}
|
|
@@ -28,7 +30,8 @@ export function readCursor(payload) {
|
|
|
28
30
|
}
|
|
29
31
|
return payload.next_cursor;
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
/** @param db Verifies a fresh signup directly, so almost every suite can start from a working owner. */
|
|
34
|
+
export function createTestClient(app, db) {
|
|
32
35
|
function send(method, path, options = {}) {
|
|
33
36
|
return Promise.resolve(app.request(path, {
|
|
34
37
|
method,
|
|
@@ -40,13 +43,27 @@ export function createTestClient(app) {
|
|
|
40
43
|
...(options.body === undefined ? {} : { body: JSON.stringify(options.body) }),
|
|
41
44
|
}));
|
|
42
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Signs up and verifies directly against the database.
|
|
48
|
+
*
|
|
49
|
+
* These suites are about the CRM, not verification: going through the real
|
|
50
|
+
* emailed link for every signup would mean threading the email spy through
|
|
51
|
+
* this client too, for a step almost nothing here is testing. `auth.test.ts`
|
|
52
|
+
* and `workspace.test.ts` cover the real link.
|
|
53
|
+
*/
|
|
43
54
|
async function signUp(email) {
|
|
44
55
|
const response = await send('POST', '/v1/auth/signup', {
|
|
45
|
-
body: {
|
|
56
|
+
body: {
|
|
57
|
+
email,
|
|
58
|
+
name: 'Someone',
|
|
59
|
+
password: 'correct horse battery staple',
|
|
60
|
+
},
|
|
46
61
|
});
|
|
47
62
|
if (response.status !== 201) {
|
|
48
63
|
throw new Error(`Signing up ${email} answered ${String(response.status)}`);
|
|
49
64
|
}
|
|
65
|
+
const accountId = readString(readRecord(await response.json()).account, 'id');
|
|
66
|
+
await db.update(users).set({ emailVerifiedAt: new Date() }).where(eq(users.id, accountId));
|
|
50
67
|
// Only the cookie's name=value matters to a later request; the attributes
|
|
51
68
|
// after the first semicolon are for a browser.
|
|
52
69
|
return (response.headers.get('Set-Cookie') ?? '').split(';')[0] ?? '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAKhC,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAA;AA6BjD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAA;AACpD,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,UAAU,CAAC,OAAgB,EAAE,GAAW;IACtD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,QAAQ,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACpE,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,CAAA;AACrB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACvE,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,QAAQ,CAAC,OAAgB;IACvC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC7E,CAAC;IAED,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AACtC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,KAAK,IAAI,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC,EAAE,CAAC;QACpG,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzE,CAAC;IAED,OAAO,OAAO,CAAC,WAAW,CAAA;AAC5B,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,gBAAgB,CAAC,GAAsB,EAAE,EAAY;IACnE,SAAS,IAAI,CAAC,MAAc,EAAE,IAAY,EAAE,OAAO,GAAuB,EAAE;QAC1E,OAAO,OAAO,CAAC,OAAO,CACpB,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE;YAChB,MAAM;YACN,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;gBACnE,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;aACvF;YACD,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;SAC9E,CAAC,CACH,CAAA;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,UAAU,MAAM,CAAC,KAAa;QACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,iBAAiB,EAAE;YACrD,IAAI,EAAE;gBACJ,KAAK;gBACL,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,8BAA8B;aACzC;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,cAAc,KAAK,aAAa,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC5E,CAAC;QAED,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAE7E,MAAM,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,eAAe,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAA;QAE1F,0EAA0E;QAC1E,+CAA+C;QAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACvE,CAAC;IAED,OAAO;QACL,IAAI;QACJ,MAAM;QAEN,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,iBAAiB,EAAE,IAAI,GAAG,MAAM;YAClD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAA;YAClC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE;gBACnD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,qBAAqB,EAAE;gBAC7D,MAAM;aACP,CAAC,CAAA;YAEF,IAAI,OAAO,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,aAAa,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAClF,CAAC;YAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,EAAE,CAAA;QACxE,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -13,5 +13,7 @@ import type { Environment } from '../lib/config.ts';
|
|
|
13
13
|
* secret is ever sealed with it.
|
|
14
14
|
*/
|
|
15
15
|
export declare const TEST_SECRET_ENCRYPTION_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
|
16
|
+
/** The base URL every emailed link is built from in tests. */
|
|
17
|
+
export declare const TEST_APP_BASE_URL = "https://kelpie.test";
|
|
16
18
|
export declare const TEST_ENVIRONMENT: Environment;
|
|
17
19
|
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/testing/environment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD;;;;;;;GAOG;AAEH;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,iDAAiD,CAAA;AAExF,eAAO,MAAM,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/testing/environment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD;;;;;;;GAOG;AAEH;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,iDAAiD,CAAA;AAExF,8DAA8D;AAC9D,eAAO,MAAM,iBAAiB,wBAAwB,CAAA;AAEtD,eAAO,MAAM,gBAAgB,EAAE,WAI9B,CAAA"}
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
* secret is ever sealed with it.
|
|
13
13
|
*/
|
|
14
14
|
export const TEST_SECRET_ENCRYPTION_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
|
|
15
|
+
/** The base URL every emailed link is built from in tests. */
|
|
16
|
+
export const TEST_APP_BASE_URL = 'https://kelpie.test';
|
|
15
17
|
export const TEST_ENVIRONMENT = {
|
|
16
18
|
NODE_ENV: 'test',
|
|
17
19
|
SECRET_ENCRYPTION_KEY: TEST_SECRET_ENCRYPTION_KEY,
|
|
20
|
+
APP_BASE_URL: TEST_APP_BASE_URL,
|
|
18
21
|
};
|
|
19
22
|
//# sourceMappingURL=environment.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/testing/environment.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,8CAA8C,CAAA;AAExF,MAAM,CAAC,MAAM,gBAAgB,GAAgB;IAC3C,QAAQ,EAAE,MAAM;IAChB,qBAAqB,EAAE,0BAA0B;
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/testing/environment.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,8CAA8C,CAAA;AAExF,8DAA8D;AAC9D,MAAM,CAAC,MAAM,iBAAiB,GAAG,qBAAqB,CAAA;AAEtD,MAAM,CAAC,MAAM,gBAAgB,GAAgB;IAC3C,QAAQ,EAAE,MAAM;IAChB,qBAAqB,EAAE,0BAA0B;IACjD,YAAY,EAAE,iBAAiB;CAChC,CAAA"}
|
package/dist/webBundle.d.ts
CHANGED
|
@@ -17,6 +17,14 @@ import type { AppBindings } from './app.ts';
|
|
|
17
17
|
export interface WebBundleOptions {
|
|
18
18
|
/** Directory holding the built `index.html` and its assets. */
|
|
19
19
|
readonly directory: string;
|
|
20
|
+
/**
|
|
21
|
+
* Prefixes the assembly's own modules answer on beyond core's, via
|
|
22
|
+
* `appRoute`/`appMiddleware`. An unmatched GET under one stays a JSON
|
|
23
|
+
* error instead of becoming the app shell, the same rule `/v1` gets. The
|
|
24
|
+
* assembly names them here because only it knows which of its declared
|
|
25
|
+
* paths are API rather than pages.
|
|
26
|
+
*/
|
|
27
|
+
readonly apiPrefixes?: readonly string[];
|
|
20
28
|
}
|
|
21
29
|
/** The bundle directory does not hold a build. Thrown at boot, never per request. */
|
|
22
30
|
export declare class WebBundleError extends Error {
|
package/dist/webBundle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webBundle.d.ts","sourceRoot":"","sources":["../src/webBundle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAqB,MAAM,MAAM,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAG3C;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"webBundle.d.ts","sourceRoot":"","sources":["../src/webBundle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAqB,MAAM,MAAM,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAG3C;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACzC;AAED,qFAAqF;AACrF,qBAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AA2CD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CA0BtF"}
|
package/dist/webBundle.js
CHANGED
|
@@ -10,14 +10,15 @@ export class WebBundleError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* The prefixes `createApp` answers on.
|
|
13
|
+
* The prefixes `createApp` itself answers on.
|
|
14
14
|
*
|
|
15
15
|
* `/v1/public` needs no entry of its own: it sits under `/v1`. `MCP_ROUTE_PREFIX`
|
|
16
16
|
* is imported rather than written out, so moving the endpoint moves this with it.
|
|
17
|
+
* An assembly's own API prefixes arrive through `WebBundleOptions.apiPrefixes`.
|
|
17
18
|
*/
|
|
18
|
-
const
|
|
19
|
-
function
|
|
20
|
-
return
|
|
19
|
+
const CORE_API_PREFIXES = ['/v1', MCP_ROUTE_PREFIX, '/healthz'];
|
|
20
|
+
function isApiRequestFor(prefixes) {
|
|
21
|
+
return (path) => prefixes.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* Restricts a handler to requests for the web app.
|
|
@@ -32,7 +33,7 @@ function isApiRequest(path) {
|
|
|
32
33
|
* not a page request, and only a `GET` or a `HEAD` can sensibly be answered with
|
|
33
34
|
* a document.
|
|
34
35
|
*/
|
|
35
|
-
function webRequestsOnly(handler) {
|
|
36
|
+
function webRequestsOnly(isApiRequest, handler) {
|
|
36
37
|
return async (context, next) => {
|
|
37
38
|
const isDocumentRequest = context.req.method === 'GET' || context.req.method === 'HEAD';
|
|
38
39
|
if (!isDocumentRequest || isApiRequest(context.req.path)) {
|
|
@@ -58,10 +59,11 @@ export function serveWebBundle(app, options) {
|
|
|
58
59
|
if (!existsSync(indexHtml)) {
|
|
59
60
|
throw new WebBundleError(`No index.html in ${directory}. Point WEB_BUNDLE_DIR at a built web bundle, or run the web build first.`);
|
|
60
61
|
}
|
|
62
|
+
const isApiRequest = isApiRequestFor([...CORE_API_PREFIXES, ...(options.apiPrefixes ?? [])]);
|
|
61
63
|
// Two registrations rather than one: `serveStatic` calls `next()` when it
|
|
62
64
|
// finds no file, which is exactly the signal the fallback needs. A request
|
|
63
65
|
// for a real asset is answered by the first and never reaches the second.
|
|
64
|
-
app.use('*', webRequestsOnly(serveStatic({ root: directory })));
|
|
66
|
+
app.use('*', webRequestsOnly(isApiRequest, serveStatic({ root: directory })));
|
|
65
67
|
// The single-page fallback. The app decides what to draw from the address, so
|
|
66
68
|
// a deep link to `/people/per_01J…` has to return the same `index.html` even
|
|
67
69
|
// though no file sits at that path.
|
|
@@ -70,6 +72,6 @@ export function serveWebBundle(app, options) {
|
|
|
70
72
|
// does by default. Narrowing this by `Accept` would turn a stale asset
|
|
71
73
|
// reference into a clean 404, and would also turn `curl /people/per_01J…`
|
|
72
74
|
// into one, so it is left alone.
|
|
73
|
-
app.use('*', webRequestsOnly(serveStatic({ path: indexHtml })));
|
|
75
|
+
app.use('*', webRequestsOnly(isApiRequest, serveStatic({ path: indexHtml })));
|
|
74
76
|
}
|
|
75
77
|
//# sourceMappingURL=webBundle.js.map
|
package/dist/webBundle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webBundle.js","sourceRoot":"","sources":["../src/webBundle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AAI5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"webBundle.js","sourceRoot":"","sources":["../src/webBundle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AAI5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AA8BzD,qFAAqF;AACrF,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,iBAAiB,GAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,UAAU,CAAC,CAAA;AAElF,SAAS,eAAe,CAAC,QAA2B;IAClD,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAA;AAC9F,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,eAAe,CACtB,YAAuC,EACvC,OAA0B;IAE1B,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAA;QAEvF,IAAI,CAAC,iBAAiB,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACzD,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QAED,OAAO,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAC/B,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,GAAsB,EAAE,OAAyB;IAC9E,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IAE/C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,cAAc,CACtB,oBAAoB,SAAS,2EAA2E,CACzG,CAAA;IACH,CAAC;IAED,MAAM,YAAY,GAAG,eAAe,CAAC,CAAC,GAAG,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IAE5F,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAA;IAE7E,8EAA8E;IAC9E,6EAA6E;IAC7E,oCAAoC;IACpC,EAAE;IACF,4EAA4E;IAC5E,uEAAuE;IACvE,0EAA0E;IAC1E,iCAAiC;IACjC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAA;AAC/E,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE "email_verification_tokens" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"user_id" text NOT NULL,
|
|
4
|
+
"token_hash" text NOT NULL,
|
|
5
|
+
"expires_at" timestamp with time zone NOT NULL,
|
|
6
|
+
"used_at" timestamp with time zone,
|
|
7
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
8
|
+
CONSTRAINT "email_verification_tokens_token_hash_unique" UNIQUE("token_hash")
|
|
9
|
+
);
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
ALTER TABLE "users" ADD COLUMN "email_verified_at" timestamp with time zone;--> statement-breakpoint
|
|
12
|
+
ALTER TABLE "email_verification_tokens" ADD CONSTRAINT "email_verification_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|