@segmentstream-cloud/ontology 0.9.0 → 0.11.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/dist/action.d.ts +19 -0
- package/dist/action.js +1 -0
- package/dist/analyze.d.ts +3 -1
- package/dist/analyze.js +97 -2
- package/dist/bin/migrate.js +3 -2
- package/dist/blob.d.ts +39 -0
- package/dist/blob.js +49 -0
- package/dist/diagnostics.d.ts +3 -3
- package/dist/diagnostics.js +1 -0
- package/dist/entity.d.ts +5 -3
- package/dist/index.d.ts +9 -4
- package/dist/index.js +6 -1
- package/dist/manifest.d.ts +71 -7
- package/dist/manifest.js +18 -1
- package/dist/migrate/apply.d.ts +7 -0
- package/dist/migrate/apply.js +18 -1
- package/dist/migrate/index.d.ts +1 -1
- package/dist/migrate/index.js +1 -1
- package/dist/migrate/model.d.ts +15 -2
- package/dist/migrate/plan.d.ts +6 -0
- package/dist/migrate/plan.js +35 -3
- package/dist/migrate/storage.d.ts +14 -0
- package/dist/migrate/storage.js +95 -0
- package/dist/names.d.ts +9 -1
- package/dist/names.js +11 -0
- package/dist/nitro.d.ts +13 -0
- package/dist/nitro.js +18 -0
- package/dist/property.d.ts +10 -1
- package/dist/property.js +12 -1
- package/dist/query.js +6 -1
- package/dist/runtime/generated-actions.d.ts +3 -0
- package/dist/runtime/generated-actions.js +10 -5
- package/dist/runtime/history.d.ts +3 -0
- package/dist/runtime/oauth.d.ts +9 -0
- package/dist/runtime/oauth.js +165 -0
- package/dist/runtime/pipeline.d.ts +5 -1
- package/dist/runtime/pipeline.js +87 -3
- package/dist/runtime/principals.d.ts +5 -0
- package/dist/runtime/principals.js +15 -0
- package/dist/runtime/records.js +9 -0
- package/dist/runtime/service-binding.d.ts +2 -1
- package/dist/runtime/service-binding.js +15 -2
- package/dist/runtime/writes.js +17 -3
- package/dist/service/api.js +19 -10
- package/dist/service/authentication.d.ts +27 -1
- package/dist/service/authentication.js +48 -26
- package/dist/service/config.d.ts +8 -0
- package/dist/service/config.js +18 -0
- package/dist/service/descriptions.d.ts +8 -0
- package/dist/service/descriptions.js +15 -0
- package/dist/service/errors.d.ts +5 -1
- package/dist/service/errors.js +6 -2
- package/dist/service/handlers.d.ts +1 -4
- package/dist/service/handlers.js +59 -203
- package/dist/service/mcp/server.d.ts +24 -0
- package/dist/service/mcp/server.js +370 -0
- package/dist/service/mcp/tools.d.ts +47 -0
- package/dist/service/mcp/tools.js +218 -0
- package/dist/service/oauth/protocol.d.ts +52 -0
- package/dist/service/oauth/protocol.js +110 -0
- package/dist/service/oauth/server.d.ts +27 -0
- package/dist/service/oauth/server.js +576 -0
- package/dist/service/operations.d.ts +114 -0
- package/dist/service/operations.js +218 -0
- package/dist/service/runtime-contract.d.ts +98 -0
- package/dist/service/schemas.d.ts +18 -2
- package/dist/service/schemas.js +18 -2
- package/dist/service/service.d.ts +3 -3
- package/dist/service/service.js +27 -7
- package/dist/storage/api.d.ts +4 -0
- package/dist/storage/api.js +39 -0
- package/dist/storage/authorization.d.ts +8 -0
- package/dist/storage/authorization.js +30 -0
- package/dist/storage/maintenance-auth.d.ts +20 -0
- package/dist/storage/maintenance-auth.js +24 -0
- package/dist/storage/maintenance.d.ts +7 -0
- package/dist/storage/maintenance.js +50 -0
- package/dist/storage/neon.d.ts +3 -0
- package/dist/storage/neon.js +212 -0
- package/dist/storage/operator.d.ts +10 -0
- package/dist/storage/operator.js +30 -0
- package/dist/storage/provider.d.ts +80 -0
- package/dist/storage/provider.js +20 -0
- package/dist/storage/sessions.d.ts +87 -0
- package/dist/storage/sessions.js +341 -0
- package/dist/storage/tables.d.ts +2 -0
- package/dist/storage/tables.js +55 -0
- package/dist/storage/writes.d.ts +7 -0
- package/dist/storage/writes.js +88 -0
- package/package.json +8 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { HttpServerRequest } from "effect/unstable/http";
|
|
3
|
+
import type { Principal } from "../../runtime.js";
|
|
4
|
+
import type { OAuthStore } from "../runtime-contract.js";
|
|
5
|
+
/** Access tokens: `sso_` and 43 characters of base64url. They work only on the MCP server. */
|
|
6
|
+
export declare const ACCESS_TOKEN_PREFIX = "sso_";
|
|
7
|
+
export declare const REFRESH_TOKEN_PREFIX = "ssr_";
|
|
8
|
+
export declare const CODE_PREFIX = "ssc_";
|
|
9
|
+
export declare const CLIENT_ID_PREFIX = "ssa_";
|
|
10
|
+
export declare const CLIENT_SECRET_PREFIX = "sss_";
|
|
11
|
+
export declare const ACCESS_TOKEN_TTL_MS: number;
|
|
12
|
+
export declare const REFRESH_TOKEN_TTL_MS: number;
|
|
13
|
+
export declare const CODE_TTL_MS: number;
|
|
14
|
+
/** How long a member has to sign in and decide. */
|
|
15
|
+
export declare const REQUEST_TTL_MS: number;
|
|
16
|
+
/** Everything the member may do. */
|
|
17
|
+
export declare const FULL_SCOPE = "ontology";
|
|
18
|
+
/** Only the reads of what the member may do. */
|
|
19
|
+
export declare const READ_SCOPE = "ontology:read";
|
|
20
|
+
export declare const SCOPES: readonly ["ontology", "ontology:read"];
|
|
21
|
+
export type Scope = (typeof SCOPES)[number];
|
|
22
|
+
/** The well-known path of the MCP server's protected resource metadata (RFC 9728). */
|
|
23
|
+
export declare const PROTECTED_RESOURCE_PATH = "/.well-known/oauth-protected-resource";
|
|
24
|
+
/** The well-known path of the authorization server metadata (RFC 8414). */
|
|
25
|
+
export declare const AUTHORIZATION_SERVER_PATH = "/.well-known/oauth-authorization-server";
|
|
26
|
+
/** The app's page where a signed-in member approves or denies a request. */
|
|
27
|
+
export declare const CONSENT_PATH = "/oauth/consent";
|
|
28
|
+
/** 32 random bytes, base64url, after a prefix that says what the value is. */
|
|
29
|
+
export declare function newSecret(prefix?: string): string;
|
|
30
|
+
/** SHA-256 in hex: how codes, tokens and client secrets are stored. */
|
|
31
|
+
export declare function secretHash(value: string): string;
|
|
32
|
+
export declare function sameSecret(value: string, hash: string): boolean;
|
|
33
|
+
/** Whether a PKCE verifier answers an S256 challenge (RFC 7636). */
|
|
34
|
+
export declare function verifierMatches(verifier: string, challenge: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The scope a request gets: everything the member may do unless it asks only to read. Scopes this
|
|
37
|
+
* server does not know are ignored, as clients often ask for the ones they always ask for.
|
|
38
|
+
*/
|
|
39
|
+
export declare function grantedScope(requested: string | undefined): Scope;
|
|
40
|
+
/** The member as a grant of this scope acts: with only their reads under `ontology:read`. */
|
|
41
|
+
export declare function scopedPrincipal(principal: Principal, scope: string): Principal;
|
|
42
|
+
export declare function isLoopback(host: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The origin the client reached the app on, as Vercel forwards it: the issuer of the authorization
|
|
45
|
+
* server and the origin of the MCP server's URL. Each domain of the app is its own resource.
|
|
46
|
+
*/
|
|
47
|
+
export declare function publicOrigin(request: HttpServerRequest.HttpServerRequest): string;
|
|
48
|
+
/**
|
|
49
|
+
* The member an access token acts for, as its grant allows: only for the resource it was issued
|
|
50
|
+
* for, while the grant stands and the member may still sign in.
|
|
51
|
+
*/
|
|
52
|
+
export declare function principalOfAccessToken(store: OAuthStore, token: string, resource: string): Effect.Effect<Principal | undefined>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { parsePermission, permissionOf, READ_VERB, WILDCARD } from "../../permissions.js";
|
|
4
|
+
/** Access tokens: `sso_` and 43 characters of base64url. They work only on the MCP server. */
|
|
5
|
+
export const ACCESS_TOKEN_PREFIX = "sso_";
|
|
6
|
+
export const REFRESH_TOKEN_PREFIX = "ssr_";
|
|
7
|
+
export const CODE_PREFIX = "ssc_";
|
|
8
|
+
export const CLIENT_ID_PREFIX = "ssa_";
|
|
9
|
+
export const CLIENT_SECRET_PREFIX = "sss_";
|
|
10
|
+
export const ACCESS_TOKEN_TTL_MS = 60 * 60 * 1000;
|
|
11
|
+
export const REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
12
|
+
export const CODE_TTL_MS = 5 * 60 * 1000;
|
|
13
|
+
/** How long a member has to sign in and decide. */
|
|
14
|
+
export const REQUEST_TTL_MS = 15 * 60 * 1000;
|
|
15
|
+
/** Everything the member may do. */
|
|
16
|
+
export const FULL_SCOPE = "ontology";
|
|
17
|
+
/** Only the reads of what the member may do. */
|
|
18
|
+
export const READ_SCOPE = "ontology:read";
|
|
19
|
+
export const SCOPES = [FULL_SCOPE, READ_SCOPE];
|
|
20
|
+
/** The well-known path of the MCP server's protected resource metadata (RFC 9728). */
|
|
21
|
+
export const PROTECTED_RESOURCE_PATH = "/.well-known/oauth-protected-resource";
|
|
22
|
+
/** The well-known path of the authorization server metadata (RFC 8414). */
|
|
23
|
+
export const AUTHORIZATION_SERVER_PATH = "/.well-known/oauth-authorization-server";
|
|
24
|
+
/** The app's page where a signed-in member approves or denies a request. */
|
|
25
|
+
export const CONSENT_PATH = "/oauth/consent";
|
|
26
|
+
/** 32 random bytes, base64url, after a prefix that says what the value is. */
|
|
27
|
+
export function newSecret(prefix = "") {
|
|
28
|
+
return `${prefix}${randomBytes(32).toString("base64url")}`;
|
|
29
|
+
}
|
|
30
|
+
/** SHA-256 in hex: how codes, tokens and client secrets are stored. */
|
|
31
|
+
export function secretHash(value) {
|
|
32
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
33
|
+
}
|
|
34
|
+
export function sameSecret(value, hash) {
|
|
35
|
+
const left = Buffer.from(secretHash(value), "hex");
|
|
36
|
+
const right = Buffer.from(hash, "hex");
|
|
37
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
38
|
+
}
|
|
39
|
+
/** Whether a PKCE verifier answers an S256 challenge (RFC 7636). */
|
|
40
|
+
export function verifierMatches(verifier, challenge) {
|
|
41
|
+
if (!/^[A-Za-z0-9._~-]{43,128}$/.test(verifier)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const computed = Buffer.from(createHash("sha256").update(verifier, "ascii").digest("base64url"));
|
|
45
|
+
const expected = Buffer.from(challenge);
|
|
46
|
+
return computed.length === expected.length && timingSafeEqual(computed, expected);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The scope a request gets: everything the member may do unless it asks only to read. Scopes this
|
|
50
|
+
* server does not know are ignored, as clients often ask for the ones they always ask for.
|
|
51
|
+
*/
|
|
52
|
+
export function grantedScope(requested) {
|
|
53
|
+
const asked = new Set((requested ?? "").split(" ").filter((scope) => scope !== ""));
|
|
54
|
+
return asked.has(READ_SCOPE) && !asked.has(FULL_SCOPE) ? READ_SCOPE : FULL_SCOPE;
|
|
55
|
+
}
|
|
56
|
+
/** `lead:*` and `lead:read` keep `lead:read`; `*:*` keeps `*:read`; other verbs go. */
|
|
57
|
+
function readPermissions(permissions) {
|
|
58
|
+
const reads = permissions.flatMap((permission) => {
|
|
59
|
+
const parsed = parsePermission(permission);
|
|
60
|
+
return parsed !== undefined && (parsed.verb === READ_VERB || parsed.verb === WILDCARD)
|
|
61
|
+
? [permissionOf(parsed.entity, READ_VERB)]
|
|
62
|
+
: [];
|
|
63
|
+
});
|
|
64
|
+
return [...new Set(reads)];
|
|
65
|
+
}
|
|
66
|
+
/** The member as a grant of this scope acts: with only their reads under `ontology:read`. */
|
|
67
|
+
export function scopedPrincipal(principal, scope) {
|
|
68
|
+
return scope === READ_SCOPE
|
|
69
|
+
? { ...principal, permissions: readPermissions(principal.permissions) }
|
|
70
|
+
: principal;
|
|
71
|
+
}
|
|
72
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
73
|
+
export function isLoopback(host) {
|
|
74
|
+
return LOOPBACK_HOSTS.has(host.replace(/:\d+$/, ""));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The origin the client reached the app on, as Vercel forwards it: the issuer of the authorization
|
|
78
|
+
* server and the origin of the MCP server's URL. Each domain of the app is its own resource.
|
|
79
|
+
*/
|
|
80
|
+
export function publicOrigin(request) {
|
|
81
|
+
const forwardedHost = request.headers["x-forwarded-host"] ?? request.headers.host;
|
|
82
|
+
if (forwardedHost === undefined) {
|
|
83
|
+
// A web Request without a Host header still carries its absolute URL.
|
|
84
|
+
return URL.canParse(request.originalUrl)
|
|
85
|
+
? new URL(request.originalUrl).origin
|
|
86
|
+
: "http://localhost";
|
|
87
|
+
}
|
|
88
|
+
const host = forwardedHost.split(",")[0].trim();
|
|
89
|
+
const forwardedProto = request.headers["x-forwarded-proto"]?.split(",")[0]?.trim();
|
|
90
|
+
const protocol = forwardedProto === "http" || forwardedProto === "https"
|
|
91
|
+
? forwardedProto
|
|
92
|
+
: isLoopback(host)
|
|
93
|
+
? "http"
|
|
94
|
+
: "https";
|
|
95
|
+
return `${protocol}://${host}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The member an access token acts for, as its grant allows: only for the resource it was issued
|
|
99
|
+
* for, while the grant stands and the member may still sign in.
|
|
100
|
+
*/
|
|
101
|
+
export function principalOfAccessToken(store, token, resource) {
|
|
102
|
+
return Effect.gen(function* () {
|
|
103
|
+
const grant = yield* store.findAccess(secretHash(token));
|
|
104
|
+
if (grant === undefined || grant.resource !== resource) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const member = yield* store.memberPrincipal(grant.userId);
|
|
108
|
+
return member === undefined ? undefined : scopedPrincipal(member, grant.scope);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
3
|
+
import { type TokenVerifier } from "../authentication.js";
|
|
4
|
+
import { RequestId } from "../errors.js";
|
|
5
|
+
import { type RateLimit } from "../rate-limit.js";
|
|
6
|
+
import { OntologyBackend } from "../runtime-contract.js";
|
|
7
|
+
/** The authorization server's endpoints, under the ontology service's path. */
|
|
8
|
+
export declare const OAUTH_PATH = "/api/ontology/oauth";
|
|
9
|
+
/** Registrations one client address may make per minute; each one is a row in the database. */
|
|
10
|
+
export declare const REGISTRATION_LIMIT: RateLimit;
|
|
11
|
+
/** Where the MCP server is, on the origin the client reached. */
|
|
12
|
+
export declare function mcpResource(origin: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* The `WWW-Authenticate` challenge of the MCP server: it names the protected resource metadata,
|
|
15
|
+
* which is how a client learns where members authorize it (RFC 9728).
|
|
16
|
+
*/
|
|
17
|
+
export declare function mcpChallenge(origin: string, missing: boolean): string;
|
|
18
|
+
export interface OAuthServerOptions {
|
|
19
|
+
readonly verifyToken: TokenVerifier;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The MCP server's OAuth 2.1 authorization server, on the app's own origin: the protected resource
|
|
23
|
+
* and authorization server metadata, dynamic client registration, the authorization endpoint that
|
|
24
|
+
* hands the member to the app's consent page, the endpoints the consent page decides with, and the
|
|
25
|
+
* token and revocation endpoints. Without the OAuth tables, it serves nothing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function oauthLayer(options: OAuthServerOptions): Layer.Layer<never, never, OntologyBackend | HttpRouter.HttpRouter | HttpRouter.Request<"Requires", RequestId>>;
|