@k2b/cloud 0.48.0 → 0.50.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/package.json +3 -3
- package/src/_internal/app-access-resources.ts +2 -1
- package/src/_internal/app-capabilities.ts +73 -0
- package/src/_internal/app-event-catalog.ts +1 -1
- package/src/_internal/app-queries.ts +1 -1
- package/src/_internal/application-package.ts +2 -1
- package/src/_internal/capabilities.ts +17 -2
- package/src/_internal/define-app.ts +2 -0
- package/src/ai/app-skill-store.ts +2 -1
- package/src/ai/app-skills.ts +2 -1
- package/src/ai/approvals.ts +2 -1
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/credentials.ts +2 -1
- package/src/ai/dictation-runtime.ts +1 -1
- package/src/ai/dictations.ts +1 -1
- package/src/ai/files-store.ts +2 -1
- package/src/ai/inference-calls.ts +2 -1
- package/src/ai/map-store.ts +1 -1
- package/src/ai/map-tool.ts +1 -1
- package/src/ai/memories.ts +1 -1
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +1 -1
- package/src/ai/memory-workflow-evidence.ts +1 -1
- package/src/ai/message-queue.ts +2 -1
- package/src/ai/migrate.ts +1 -1
- package/src/ai/model-access-migrate.ts +1 -1
- package/src/ai/model-access.ts +1 -1
- package/src/ai/model-catalog.ts +1 -1
- package/src/ai/model-delete.ts +1 -1
- package/src/ai/model-usage.ts +2 -1
- package/src/ai/open-questions.ts +1 -1
- package/src/ai/openai-api-slots.ts +2 -1
- package/src/ai/prefs.ts +2 -1
- package/src/ai/projects.ts +2 -1
- package/src/ai/provider-connections.ts +1 -1
- package/src/ai/quota-limits.ts +2 -1
- package/src/ai/quota-provider.ts +2 -1
- package/src/ai/quota-report.ts +2 -1
- package/src/ai/quotas-migrate.ts +2 -1
- package/src/ai/quotas.ts +2 -1
- package/src/ai/request-headers.ts +2 -1
- package/src/ai/review-content.ts +2 -1
- package/src/ai/skill-search.ts +2 -1
- package/src/ai/skills.ts +2 -1
- package/src/ai/store.ts +2 -1
- package/src/ai/structured-runs.ts +2 -1
- package/src/ai/tool-audit.ts +2 -1
- package/src/ai/turn-timing.ts +1 -1
- package/src/ai/usage.ts +2 -1
- package/src/ai/web-provenance.ts +2 -1
- package/src/api/admin-ai-quotas.ts +1 -1
- package/src/api/admin-ai.ts +1 -1
- package/src/api/admin-app-keys.ts +47 -3
- package/src/api/admin-core-settings.ts +1 -1
- package/src/api/admin-outgoing-mail.ts +1 -1
- package/src/api/app-bootstrap.ts +23 -1
- package/src/api/app-events.ts +1 -1
- package/src/api/auth.ts +1 -1
- package/src/api/authenticate-app-request.ts +129 -0
- package/src/api/capabilities.ts +79 -5
- package/src/api/index.ts +2 -0
- package/src/api/me.ts +1 -1
- package/src/api/search.bench.ts +1 -1
- package/src/app-pwa/service.ts +2 -1
- package/src/app-pwa/transaction.ts +4 -2
- package/src/capabilities/claims.ts +2 -1
- package/src/capabilities/executions.ts +8 -4
- package/src/capabilities/geo-client.ts +1 -1
- package/src/capabilities/migrate.ts +3 -1
- package/src/capabilities/resource-readers.ts +2 -2
- package/src/capabilities/server-invoke.ts +45 -18
- package/src/capabilities/server.ts +2 -1
- package/src/cli/admin/app-keys.ts +25 -3
- package/src/config/env.ts +4 -4
- package/src/contracts/capabilities.ts +15 -2
- package/src/contracts/outgoing-mail.ts +1 -1
- package/src/events/app-event-delivery.ts +1 -1
- package/src/events/live-delivery.ts +2 -1
- package/src/runtime/bootstrap-protocol.ts +11 -4
- package/src/runtime/clients.ts +6 -2
- package/src/server/index.ts +1 -0
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access.ts +2 -1
- package/src/services/account-category-policy.ts +1 -1
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/account-lifecycle/index.ts +1 -1
- package/src/services/account-lifecycle/ipa-backfill.ts +2 -1
- package/src/services/accounts/app.ts +1 -1
- package/src/services/accounts/avatar.ts +1 -1
- package/src/services/accounts/base-group.ts +1 -1
- package/src/services/accounts/directory-shares.ts +1 -1
- package/src/services/accounts/duplicate-emails.ts +1 -1
- package/src/services/accounts/entities.ts +2 -1
- package/src/services/accounts/group-sql.ts +2 -1
- package/src/services/accounts/groups.ts +1 -1
- package/src/services/accounts/identities.ts +13 -11
- package/src/services/accounts/ipa-data.ts +1 -1
- package/src/services/accounts/local-groups.ts +2 -1
- package/src/services/accounts/machine-group-management.ts +2 -1
- package/src/services/accounts/posix.ts +13 -12
- package/src/services/accounts/request-policy.ts +2 -1
- package/src/services/accounts/switching.ts +1 -1
- package/src/services/accounts/users.ts +2 -1
- package/src/services/announcements/index.ts +1 -1
- package/src/services/app-access-management.ts +2 -1
- package/src/services/app-access.ts +2 -1
- package/src/services/app-admin-facts.ts +11 -3
- package/src/services/app-approval.ts +34 -30
- package/src/services/app-bootstrap.ts +104 -21
- package/src/services/app-details.ts +3 -2
- package/src/services/app-events/accept.ts +5 -5
- package/src/services/app-events/admin.ts +15 -15
- package/src/services/app-events/declarations.ts +6 -3
- package/src/services/app-events/deliver.ts +6 -6
- package/src/services/app-events.ts +3 -2
- package/src/services/app-keys.ts +42 -11
- package/src/services/approvals/content.ts +1 -1
- package/src/services/approvals/index.ts +2 -1
- package/src/services/approvals/list.ts +1 -1
- package/src/services/approvals/migrate.ts +1 -1
- package/src/services/audit/index.ts +2 -1
- package/src/services/auth-flows/ipa.ts +1 -1
- package/src/services/auth-flows/magic-link.ts +1 -1
- package/src/services/auth-flows/password-reset.ts +1 -1
- package/src/services/auth-flows/proxy-return.ts +1 -1
- package/src/services/geo/admin.ts +13 -12
- package/src/services/geo/identity.ts +46 -0
- package/src/services/geo/ledger.ts +118 -106
- package/src/services/geo/migrate.ts +2 -1
- package/src/services/geo/route-chain.ts +3 -3
- package/src/services/geo/service.ts +4 -24
- package/src/services/geoip/index.ts +5 -5
- package/src/services/geoip/store.ts +24 -21
- package/src/services/help/index.ts +2 -1
- package/src/services/help/store.ts +2 -1
- package/src/services/identity/app-request.ts +44 -0
- package/src/services/identity/constants.ts +1 -0
- package/src/services/identity/index.ts +1 -0
- package/src/services/identity/invocation-actor.ts +2 -1
- package/src/services/identity/invocation-token.ts +112 -0
- package/src/services/identity/key-ring.ts +2 -2
- package/src/services/identity/resource-readers.ts +1 -1
- package/src/services/identity/routes.ts +11 -3
- package/src/services/identity/runtime-config.ts +14 -3
- package/src/services/identity/workload-auth.ts +2 -0
- package/src/services/identity/workload-runtime.ts +1 -1
- package/src/services/ipa/groups.ts +1 -1
- package/src/services/ipa/profile.ts +1 -1
- package/src/services/ipa/search.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/ipa/users.ts +1 -1
- package/src/services/known-browsers.ts +2 -1
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +6 -3
- package/src/services/logging/trace.ts +1 -1
- package/src/services/logging/web-vitals.ts +1 -1
- package/src/services/mandates/index.ts +2 -1
- package/src/services/notifications/batches.ts +1 -1
- package/src/services/notifications/browser.ts +1 -1
- package/src/services/notifications/catalog.ts +1 -1
- package/src/services/notifications/dispatcher.ts +1 -1
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/observability.ts +2 -1
- package/src/services/notifications/platform.ts +1 -1
- package/src/services/notifications/quiet.ts +1 -1
- package/src/services/notifications/user.ts +1 -1
- package/src/services/oauth-tokens.ts +1 -1
- package/src/services/outbox.ts +15 -14
- package/src/services/outgoing-mail/admin.ts +1 -1
- package/src/services/outgoing-mail/dispatcher.ts +1 -1
- package/src/services/outgoing-mail/drain.ts +2 -1
- package/src/services/outgoing-mail/enqueue.ts +2 -1
- package/src/services/outgoing-mail/messages.ts +2 -1
- package/src/services/outgoing-mail/retention.ts +1 -1
- package/src/services/outgoing-mail/send.ts +1 -1
- package/src/services/outgoing-mail/store.ts +2 -1
- package/src/services/panel-context-consent.ts +32 -28
- package/src/services/providers/local/auth.ts +1 -1
- package/src/services/providers/local/users.ts +1 -1
- package/src/services/pwa-devices.ts +2 -1
- package/src/services/rail-preferences.ts +16 -12
- package/src/services/rail-shortcuts.ts +12 -10
- package/src/services/request-cache-redis.ts +3 -3
- package/src/services/service-account-credentials.ts +2 -1
- package/src/services/service-accounts.ts +2 -1
- package/src/services/session/index.ts +2 -1
- package/src/services/session/user.ts +2 -1
- package/src/services/settings/index.ts +1 -1
- package/src/services/settings/store.ts +1 -1
- package/src/services/settings/versions.ts +2 -1
- package/src/services/weather/forecast.ts +2 -1
- package/src/services/weather/locations.ts +1 -1
- package/src/services/weather/migrate.ts +2 -1
- package/src/services/weather/short-id.ts +2 -1
- package/src/services/webauthn.ts +1 -1
- package/src/shared-text/documents.ts +2 -2
- package/src/ssr/compact-shell.browser-harness.ts +2 -0
- package/src/workflows/ai/store.ts +2 -1
- package/src/workflows/store/budget.ts +2 -1
- package/src/workflows/store/definitions.ts +2 -1
- package/src/workflows/store/events.ts +2 -1
- package/src/workflows/store/observability.ts +2 -1
- package/src/workflows/store/retention.ts +2 -1
- package/src/workflows/store/runs.ts +2 -1
- package/src/workflows/store/transaction.ts +2 -1
- package/src/workflows/store/worker.ts +2 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getAppWorkloadCredential } from "../../_internal/workload-credential";
|
|
2
|
+
import { parseAppKey } from "../../app-key";
|
|
3
|
+
import { env } from "../../config/env";
|
|
4
|
+
import { CapabilityAppIdSchema } from "../../contracts/capabilities";
|
|
5
|
+
import { signAppProof } from "../../runtime/bootstrap-protocol";
|
|
6
|
+
|
|
7
|
+
/** Send only to Core. Call for every transmission, including safe caller-owned retries. */
|
|
8
|
+
export const sendAppRequest = async (
|
|
9
|
+
request: Request,
|
|
10
|
+
options: {
|
|
11
|
+
appId: string;
|
|
12
|
+
transport?: (request: Request) => Promise<Response>;
|
|
13
|
+
/** Release N legacy authority; evaluated only when no app key is configured. */
|
|
14
|
+
bearer?: () => string | undefined;
|
|
15
|
+
},
|
|
16
|
+
): Promise<Response> => {
|
|
17
|
+
const appId = CapabilityAppIdSchema.parse(options.appId);
|
|
18
|
+
const headers = new Headers(request.headers);
|
|
19
|
+
const declaredApp = headers.get("X-Cloud-App-Id");
|
|
20
|
+
if (declaredApp !== null && declaredApp !== appId) throw new Error("App identity header does not match the calling application");
|
|
21
|
+
headers.set("X-Cloud-App-Id", appId);
|
|
22
|
+
const keyValue = env.CLOUD_APP_KEY;
|
|
23
|
+
if (keyValue) {
|
|
24
|
+
const key = parseAppKey(keyValue);
|
|
25
|
+
if (key.appId !== options.appId) throw new Error("App-key appId does not match the calling application");
|
|
26
|
+
const url = new URL(request.url);
|
|
27
|
+
const proof = await signAppProof(key, {
|
|
28
|
+
method: request.method,
|
|
29
|
+
path: url.pathname + url.search,
|
|
30
|
+
body: new Uint8Array(await request.clone().arrayBuffer()),
|
|
31
|
+
});
|
|
32
|
+
headers.set("authorization", `Bearer ${proof}`);
|
|
33
|
+
} else if (!headers.has("authorization")) {
|
|
34
|
+
const token = (options.bearer ?? getAppWorkloadCredential)();
|
|
35
|
+
if (!token) throw new Error("App workload authorization is unavailable");
|
|
36
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
37
|
+
}
|
|
38
|
+
// A proof is bound to this target; redirects must never carry it elsewhere.
|
|
39
|
+
return (options.transport ?? fetch)(new Request(request, { headers, redirect: "error" }));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Legacy workload tokens and the OAuth secret never contain JWT separators. */
|
|
43
|
+
export const isAppRequestProof = (authorization: string | undefined): boolean =>
|
|
44
|
+
authorization?.startsWith("Bearer ") === true && authorization.slice(7).includes(".");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const CLOUD_SESSION_JWKS_PATH = "/.well-known/cloud-session-jwks.json";
|
|
2
2
|
export const CLOUD_INVOCATION_JWKS_PATH = "/.well-known/cloud-invocation-jwks.json";
|
|
3
3
|
export const CLOUD_OAUTH_JWKS_PATH = "/.well-known/jwks.json";
|
|
4
|
+
export const CLOUD_INTERNAL_OAUTH_JWKS_PATH = "/api/_internal/identity/v1/oauth/jwks";
|
|
4
5
|
export const CLOUD_SESSION_TOKEN_TYPE = "cloud-session+jwt";
|
|
5
6
|
export const CLOUD_SESSION_AUDIENCE = "cloud";
|
|
6
7
|
export const CLOUD_INVOCATION_TOKEN_TYPE = "cloud-invocation+jwt";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { sql } from "
|
|
1
|
+
import { sql } from "../../runtime/clients";
|
|
2
|
+
|
|
2
3
|
import type { RequestActor } from "../../server/middleware/auth";
|
|
3
4
|
import type { AccessSubject } from "../../server/services/access";
|
|
4
5
|
import { isAccountCategoryAllowed } from "../account-category-policy";
|
|
@@ -507,6 +507,26 @@ const AppQueryClaimsSchema = z
|
|
|
507
507
|
.strict()
|
|
508
508
|
.refine((claims) => claims.nbf === claims.iat && claims.exp - claims.iat === CLOUD_INVOCATION_TOKEN_TTL_SECONDS);
|
|
509
509
|
|
|
510
|
+
const AppCapabilityClaimsSchema = z
|
|
511
|
+
.object({
|
|
512
|
+
iss: z.string().url(),
|
|
513
|
+
aud: z.string().regex(/^app:[a-z][a-z0-9-]{0,79}$/),
|
|
514
|
+
token_use: z.literal("app_capability"),
|
|
515
|
+
sub: AppIdSchema,
|
|
516
|
+
key_id: z.uuid(),
|
|
517
|
+
permission: z.string().min(1).max(100),
|
|
518
|
+
op: OperationSchema.refine((value) => value.startsWith("capability.query:") || value.startsWith("capability.action:")),
|
|
519
|
+
schema_hash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
520
|
+
input_hash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
521
|
+
request_id: BoundedIdentifierSchema,
|
|
522
|
+
jti: z.uuid(),
|
|
523
|
+
iat: z.number().int(),
|
|
524
|
+
nbf: z.number().int(),
|
|
525
|
+
exp: z.number().int(),
|
|
526
|
+
})
|
|
527
|
+
.strict()
|
|
528
|
+
.refine((claims) => claims.nbf === claims.iat && claims.exp - claims.iat === CLOUD_INVOCATION_TOKEN_TTL_SECONDS);
|
|
529
|
+
|
|
510
530
|
export const signAppQueryInvocation = async (input: {
|
|
511
531
|
workload: import("./workload-auth").AuthenticatedWorkload;
|
|
512
532
|
targetAppId: string;
|
|
@@ -595,3 +615,95 @@ export const verifyAppQueryInvocation = async (
|
|
|
595
615
|
throw error;
|
|
596
616
|
}
|
|
597
617
|
};
|
|
618
|
+
|
|
619
|
+
export const signAppCapabilityInvocation = async (input: {
|
|
620
|
+
app: { appId: string; keyId: string };
|
|
621
|
+
kind: "query" | "action";
|
|
622
|
+
permission: string;
|
|
623
|
+
targetAppId: string;
|
|
624
|
+
capabilityId: string;
|
|
625
|
+
schemaHash: string;
|
|
626
|
+
input: unknown;
|
|
627
|
+
requestId: string;
|
|
628
|
+
signer: PreparedIdentitySigner & { issuer: string };
|
|
629
|
+
}) => {
|
|
630
|
+
const now = Math.floor(Date.now() / 1000);
|
|
631
|
+
if (input.signer.signUntil.getTime() < (now + CLOUD_INVOCATION_TOKEN_TTL_SECONDS) * 1000)
|
|
632
|
+
throw new Error("Signing key expires before the app capability invocation");
|
|
633
|
+
const claims = AppCapabilityClaimsSchema.parse({
|
|
634
|
+
iss: input.signer.issuer,
|
|
635
|
+
aud: `app:${input.targetAppId}`,
|
|
636
|
+
token_use: "app_capability",
|
|
637
|
+
sub: input.app.appId,
|
|
638
|
+
key_id: input.app.keyId,
|
|
639
|
+
permission: input.permission,
|
|
640
|
+
op: `capability.${input.kind}:${input.capabilityId}`,
|
|
641
|
+
schema_hash: input.schemaHash,
|
|
642
|
+
input_hash: appEventPayloadHash(input.input),
|
|
643
|
+
request_id: input.requestId,
|
|
644
|
+
jti: crypto.randomUUID(),
|
|
645
|
+
iat: now,
|
|
646
|
+
nbf: now,
|
|
647
|
+
exp: now + CLOUD_INVOCATION_TOKEN_TTL_SECONDS,
|
|
648
|
+
});
|
|
649
|
+
const token = await new SignJWT(claims)
|
|
650
|
+
.setProtectedHeader({ alg: CLOUD_IDENTITY_ALGORITHM, typ: CLOUD_INVOCATION_TOKEN_TYPE, kid: input.signer.kid })
|
|
651
|
+
.sign(input.signer.key);
|
|
652
|
+
if (Buffer.byteLength(token) > IDENTITY_MAX_COMPACT_TOKEN_BYTES) throw new Error("App capability token exceeds its byte budget");
|
|
653
|
+
return { token, claims };
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
export const verifyAppCapabilityInvocation = async (
|
|
657
|
+
token: string,
|
|
658
|
+
expected: { appId: string; kind: "query" | "action"; capabilityId: string; schemaHash: string; input: unknown; permission: string },
|
|
659
|
+
options: { issuer?: string; key?: JWTVerifyGetKey; now?: Date } = {},
|
|
660
|
+
) => {
|
|
661
|
+
if (Buffer.byteLength(token) > IDENTITY_MAX_COMPACT_TOKEN_BYTES) return null;
|
|
662
|
+
try {
|
|
663
|
+
assertProtectedHeader(decodeProtectedHeader(token));
|
|
664
|
+
} catch {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
// Configuration and key-fetch failures must remain retryable, rather than look like invalid tokens.
|
|
668
|
+
const runtime = options.issuer && options.key ? null : await getIdentityRuntimeConfig();
|
|
669
|
+
try {
|
|
670
|
+
const issuer = options.issuer ?? runtime!.issuer;
|
|
671
|
+
const state = options.key ? null : remoteSet(runtime!.invocationJwksUrl);
|
|
672
|
+
const key = options.key ?? state!.key;
|
|
673
|
+
const verified = await verifyWithKeyRefresh(
|
|
674
|
+
(candidate) =>
|
|
675
|
+
jwtVerify(token, candidate, {
|
|
676
|
+
algorithms: [CLOUD_IDENTITY_ALGORITHM],
|
|
677
|
+
issuer,
|
|
678
|
+
audience: `app:${expected.appId}`,
|
|
679
|
+
typ: CLOUD_INVOCATION_TOKEN_TYPE,
|
|
680
|
+
clockTolerance: INVOCATION_CLOCK_TOLERANCE_SECONDS,
|
|
681
|
+
currentDate: options.now,
|
|
682
|
+
}),
|
|
683
|
+
key,
|
|
684
|
+
state ? { url: runtime!.invocationJwksUrl, state } : null,
|
|
685
|
+
);
|
|
686
|
+
const claims = AppCapabilityClaimsSchema.parse(verified.payload);
|
|
687
|
+
if (
|
|
688
|
+
claims.permission !== expected.permission ||
|
|
689
|
+
claims.op !== `capability.${expected.kind}:${expected.capabilityId}` ||
|
|
690
|
+
claims.schema_hash !== expected.schemaHash ||
|
|
691
|
+
claims.input_hash !== appEventPayloadHash(expected.input)
|
|
692
|
+
)
|
|
693
|
+
return null;
|
|
694
|
+
return claims;
|
|
695
|
+
} catch (error) {
|
|
696
|
+
if (
|
|
697
|
+
error instanceof z.ZodError ||
|
|
698
|
+
error instanceof errors.JWTExpired ||
|
|
699
|
+
error instanceof errors.JWTClaimValidationFailed ||
|
|
700
|
+
error instanceof errors.JWTInvalid ||
|
|
701
|
+
error instanceof errors.JWSInvalid ||
|
|
702
|
+
error instanceof errors.JWSSignatureVerificationFailed ||
|
|
703
|
+
error instanceof errors.JWKSNoMatchingKey ||
|
|
704
|
+
error instanceof errors.JOSENotSupported
|
|
705
|
+
)
|
|
706
|
+
return null;
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { crypto } from "@k2b/stdlib";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import { CompactSign, compactVerify, exportJWK, generateKeyPair, importJWK, type JWK } from "jose";
|
|
3
|
+
import { sql } from "../../runtime/clients";
|
|
4
4
|
import { logger } from "../logging";
|
|
5
5
|
import {
|
|
6
6
|
CLOUD_IDENTITY_ALGORITHM,
|
|
@@ -449,7 +449,7 @@ export const initializeIdentityAuthority = async (): Promise<void> => {
|
|
|
449
449
|
if (keys.previous) await rewrapIdentitySigningKeys();
|
|
450
450
|
};
|
|
451
451
|
|
|
452
|
-
export const listIdentityJwks = async (purpose:
|
|
452
|
+
export const listIdentityJwks = async (purpose: SigningKeyPurpose): Promise<{ keys: JWK[]; etag: string }> => {
|
|
453
453
|
const rows = await sql<Array<{ public_jwk: JWK | string }>>`
|
|
454
454
|
SELECT public_jwk
|
|
455
455
|
FROM auth.signing_keys
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { type ResourcePrincipal, ResourcePrincipalSchema } from "../../contracts/capabilities";
|
|
4
3
|
import type { AccessSubject } from "../../contracts/shared";
|
|
4
|
+
import { sql } from "../../runtime/clients";
|
|
5
5
|
import { decodeAccountCategoryEnabled } from "../account-category-policy";
|
|
6
6
|
import { toPgUuidArray } from "../postgres";
|
|
7
7
|
import type { ServiceAccount } from "../service-accounts";
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { type Context, Hono } from "hono";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CLOUD_INTERNAL_OAUTH_JWKS_PATH,
|
|
4
|
+
CLOUD_INVOCATION_JWKS_PATH,
|
|
5
|
+
CLOUD_SESSION_JWKS_PATH,
|
|
6
|
+
IDENTITY_JWKS_MAX_AGE_SECONDS,
|
|
7
|
+
} from "./constants";
|
|
3
8
|
import { listIdentityJwks } from "./key-ring";
|
|
4
9
|
|
|
5
10
|
// If-None-Match uses weak comparison, including lists and the wildcard.
|
|
6
11
|
const matchesEtag = (header: string | undefined, etag: string): boolean =>
|
|
7
12
|
header?.trim() === "*" || (header?.match(/(?:W\/)?"[^"]*"/g) ?? []).some((tag) => tag.replace(/^W\//, "") === etag.replace(/^W\//, ""));
|
|
8
13
|
|
|
9
|
-
const serveJwks = (purpose: "session" | "invocation") => async (c: Context) => {
|
|
14
|
+
const serveJwks = (purpose: "session" | "invocation" | "oauth") => async (c: Context) => {
|
|
10
15
|
const jwks = await listIdentityJwks(purpose);
|
|
11
16
|
if (matchesEtag(c.req.header("if-none-match"), jwks.etag)) {
|
|
12
17
|
c.header("ETag", jwks.etag);
|
|
@@ -19,4 +24,7 @@ const serveJwks = (purpose: "session" | "invocation") => async (c: Context) => {
|
|
|
19
24
|
};
|
|
20
25
|
|
|
21
26
|
export const createIdentityPublicRoutes = (): Hono =>
|
|
22
|
-
new Hono()
|
|
27
|
+
new Hono()
|
|
28
|
+
.get(CLOUD_SESSION_JWKS_PATH, serveJwks("session"))
|
|
29
|
+
.get(CLOUD_INVOCATION_JWKS_PATH, serveJwks("invocation"))
|
|
30
|
+
.get(CLOUD_INTERNAL_OAUTH_JWKS_PATH, serveJwks("oauth"));
|
|
@@ -4,7 +4,13 @@ import { publicCloudOrigin } from "../../shared/app-url";
|
|
|
4
4
|
import { get as getSetting } from "../settings";
|
|
5
5
|
import { decryptValue } from "../settings/crypto";
|
|
6
6
|
import { SETTINGS_MAP, validateSettingValue } from "../settings/defaults";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CLOUD_INTERNAL_OAUTH_JWKS_PATH,
|
|
9
|
+
CLOUD_INVOCATION_JWKS_PATH,
|
|
10
|
+
CLOUD_OAUTH_JWKS_PATH,
|
|
11
|
+
CLOUD_SESSION_JWKS_PATH,
|
|
12
|
+
IDENTITY_REFRESH_TIMEOUT_MS,
|
|
13
|
+
} from "./constants";
|
|
8
14
|
|
|
9
15
|
const RUNTIME_CONFIG_TTL_MS = 60_000;
|
|
10
16
|
|
|
@@ -75,8 +81,13 @@ export const resolveInvocationJwksUrl = (
|
|
|
75
81
|
transportOrigin = env.CLOUD_IDENTITY_JWKS_ORIGIN ?? env.CLOUD_CORE_INTERNAL_ORIGIN,
|
|
76
82
|
): URL => resolveCoreJwksUrl(issuer, CLOUD_INVOCATION_JWKS_PATH, transportOrigin);
|
|
77
83
|
|
|
78
|
-
export const resolveOAuthJwksUrl = (
|
|
79
|
-
|
|
84
|
+
export const resolveOAuthJwksUrl = (
|
|
85
|
+
issuer: string,
|
|
86
|
+
transportOrigin = env.CLOUD_OAUTH_JWKS_ORIGIN,
|
|
87
|
+
coreOrigin = env.CLOUD_CORE_INTERNAL_ORIGIN,
|
|
88
|
+
): URL => {
|
|
89
|
+
if (!transportOrigin)
|
|
90
|
+
return coreOrigin ? resolveCoreJwksUrl(issuer, CLOUD_INTERNAL_OAUTH_JWKS_PATH, coreOrigin) : new URL(CLOUD_OAUTH_JWKS_PATH, issuer);
|
|
80
91
|
|
|
81
92
|
const url = new URL(CLOUD_OAUTH_JWKS_PATH, transportOrigin);
|
|
82
93
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
@@ -9,6 +9,8 @@ export type AuthenticatedWorkload = {
|
|
|
9
9
|
serviceAccountId: string;
|
|
10
10
|
credentialId: string;
|
|
11
11
|
scope: WorkloadScope;
|
|
12
|
+
/** Present only for a verified signed app request, never a legacy token. */
|
|
13
|
+
keyId?: string;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
type WorkloadCredentialAuthenticator = Pick<typeof serviceAccountCredentials, "isApiToken" | "authenticateApiToken">;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** Core-only workload issuer. This module never reads identity signing keys, OAuth secrets, or sessions. */
|
|
2
2
|
import type { NatsConnection } from "@nats-io/transport-node";
|
|
3
|
-
import { sql } from "bun";
|
|
4
3
|
import { getProcessNats } from "../../_internal/process-sync";
|
|
5
4
|
import { APP_REGISTRY_TTL_MS, appInstancePrefix, appInstanceRegistry } from "../../_internal/registry";
|
|
6
5
|
import {
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
type WorkloadResponse,
|
|
13
12
|
workloadSubject,
|
|
14
13
|
} from "../../_internal/workload-protocol";
|
|
14
|
+
import { sql } from "../../runtime/clients";
|
|
15
15
|
import { connections } from "../../runtime/connections";
|
|
16
16
|
import { audit } from "../audit";
|
|
17
17
|
import { logger } from "../logging";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import type { BaseGroup, GroupMember, MutationResult } from "../../contracts/shared";
|
|
2
|
+
import { sql } from "../../runtime/clients";
|
|
3
3
|
import { freeipa } from "../../server/services";
|
|
4
4
|
import { toPgUuidArray } from "../postgres";
|
|
5
5
|
import { ensureFreeIpaMutationAvailable, getIpaUrl } from "./guard";
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Local group mutations keep that projection as source of truth; the local
|
|
6
6
|
* display mirror is only a bootstrap fallback before the first full sync.
|
|
7
7
|
*/
|
|
8
|
+
import { sql } from "../../runtime/clients";
|
|
8
9
|
|
|
9
|
-
import { sql } from "bun";
|
|
10
10
|
import { calculateIpaProfileFromGroupNames } from "../account-model";
|
|
11
11
|
import { getFreeIpaConfig } from "../freeipa-config";
|
|
12
12
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import type { BaseGroup, BaseUser, UserProfile, UserProvider } from "../../contracts/shared";
|
|
2
|
+
import { sql } from "../../runtime/clients";
|
|
3
3
|
import { freeipa } from "../../server/services";
|
|
4
4
|
import { buildRoles } from "../account-model";
|
|
5
5
|
import { getFreeIpaConfig } from "../freeipa-config";
|
package/src/services/ipa/sync.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import { coreUserEventPublisher } from "../../events/core-user-created";
|
|
2
|
+
import { sql } from "../../runtime/clients";
|
|
3
3
|
import { freeipa } from "../../server/services";
|
|
4
4
|
import { writeDeletedAccountAudit } from "../account-lifecycle/audit";
|
|
5
5
|
import { calculateIpaProfileFromGroupNames, parseIpaAccountTransitionPolicy, parseIpaMatchMode } from "../account-model";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { password } from "@k2b/stdlib";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import type { MutationResult, UserProfile } from "../../contracts/shared";
|
|
4
3
|
import { coreUserEventPublisher } from "../../events/core-user-created";
|
|
4
|
+
import { sql } from "../../runtime/clients";
|
|
5
5
|
import { freeipa } from "../../server/services";
|
|
6
6
|
import { writeDeletedAccountAudit } from "../account-lifecycle/audit";
|
|
7
7
|
import { resolveProviderProfile } from "../accounts/base-user";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SQL } from "bun";
|
|
2
2
|
import type { Context } from "hono";
|
|
3
3
|
import { getCookie, setCookie } from "hono/cookie";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { env } from "../config/env";
|
|
6
6
|
import { APP_LOGIN_CONTEXT_BROWSERS, APP_LOGIN_CONTEXT_OSES } from "../contracts/app-approval";
|
|
7
|
+
import { sql } from "../runtime/clients";
|
|
7
8
|
import { type BrowserCode, type OsCode, truncateUtf8 } from "../server/request-context";
|
|
8
9
|
import { logger } from "./logging";
|
|
9
10
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import type { Context } from "hono";
|
|
4
3
|
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
import { sql } from "../runtime/clients";
|
|
5
5
|
import { isAccountCategoryAllowed } from "./account-category-policy";
|
|
6
6
|
import { isAccountExpired } from "./account-model";
|
|
7
7
|
import { verifySessionToken } from "./identity";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import type { PaginationParams } from "../../contracts/shared";
|
|
2
|
+
import { sql } from "../../runtime/clients";
|
|
3
3
|
import { escapeLikePattern, parsePgJsonRecord, toPgTextArray } from "../postgres";
|
|
4
4
|
import { registerSettings } from "../settings/defaults";
|
|
5
5
|
import { redactLinkTokens, redactMetadata } from "./redaction";
|
|
@@ -126,7 +126,8 @@ function write(params: WriteParams): void {
|
|
|
126
126
|
const consoleFn = params.level === "error" ? console.error : params.level === "warn" ? console.warn : console.log;
|
|
127
127
|
consoleFn(prefix, message, ...(safeMetadata ? [safeMetadata] : []));
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
const persist = async () => {
|
|
130
|
+
await sql`
|
|
130
131
|
INSERT INTO logging.entries (level, source, message, metadata)
|
|
131
132
|
VALUES (
|
|
132
133
|
${params.level},
|
|
@@ -134,7 +135,9 @@ function write(params: WriteParams): void {
|
|
|
134
135
|
${message},
|
|
135
136
|
(${safeMetadata ? JSON.stringify(safeMetadata) : null}::text)::jsonb
|
|
136
137
|
)
|
|
137
|
-
|
|
138
|
+
`;
|
|
139
|
+
};
|
|
140
|
+
void persist().catch((err: Error) => console.error("[logging] DB write failed:", err.message));
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import type { SyncEvent } from "@k2b/sync";
|
|
3
|
-
import { sql } from "bun";
|
|
4
3
|
import { getProcessApplicationId } from "../../_internal/process-identity";
|
|
5
4
|
import type { PaginationParams } from "../../contracts/shared";
|
|
5
|
+
import { sql } from "../../runtime/clients";
|
|
6
6
|
import { escapeLikePattern, parsePgJsonRecord, toPgTextArray } from "../postgres";
|
|
7
7
|
import { isSensitiveMetadataKey, REDACTED, redactLinkTokens, redactMetadata } from "./redaction";
|
|
8
8
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { err, fail, ok, type PageParams, type Paginated, paginate, type Result } from "@k2b/stdlib";
|
|
2
|
-
import {
|
|
2
|
+
import type { SQL } from "bun";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import type { CapabilityActionPolicy } from "../../contracts/capabilities";
|
|
5
5
|
import { CapabilityAppIdSchema } from "../../contracts/capabilities";
|
|
6
|
+
import { sql } from "../../runtime/clients";
|
|
6
7
|
import { isAccountCategoryAllowed } from "../account-category-policy";
|
|
7
8
|
import { audit } from "../audit";
|
|
8
9
|
import { requireRegisteredAppAccess, resolveIssuanceAuthority } from "../identity/issue-invocation";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { err, fail, ok, type Result } from "@k2b/stdlib";
|
|
3
3
|
import type { Worker } from "@k2b/sync";
|
|
4
|
-
import { sql } from "bun";
|
|
5
4
|
import { lazySync } from "../../_internal/process-sync";
|
|
6
5
|
import { type MailMessage, MailMessageSchema, type MailStatus, MailStatusSchema } from "../../contracts/outgoing-mail";
|
|
6
|
+
import { sql } from "../../runtime/clients";
|
|
7
7
|
import { markdown } from "../../shared/markdown";
|
|
8
8
|
import { type AuditActor, audit } from "../audit";
|
|
9
9
|
import { logger, trace } from "../logging";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import webpush from "web-push";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { notificationGroupTag } from "../../contracts/notification-group";
|
|
6
5
|
import { isSafeNotificationTargetHref } from "../../contracts/notification-types";
|
|
7
6
|
import { type BrowserPushSubscription, BrowserPushSubscriptionSchema } from "../../contracts/user-notifications";
|
|
7
|
+
import { sql } from "../../runtime/clients";
|
|
8
8
|
import { decryptSecret, encryptSecret } from "../secrets";
|
|
9
9
|
import { coreSettings } from "../settings/api";
|
|
10
10
|
import { SettingUnreadableError } from "../settings/store";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import type { ZodType } from "zod";
|
|
3
2
|
import type { BoundNotificationDefinition, NotificationRecipientKind, NotificationSendInput } from "../../contracts/notification-types";
|
|
4
3
|
import type { PaginationParams } from "../../contracts/shared";
|
|
4
|
+
import { sql } from "../../runtime/clients";
|
|
5
5
|
import { logger } from "../logging";
|
|
6
6
|
import { escapeLikePattern } from "../postgres";
|
|
7
7
|
import { notificationMailOutcome, sendNotificationMail } from "./email-mail";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SQL } from "bun";
|
|
2
2
|
import type { MailStatus } from "../../contracts/outgoing-mail";
|
|
3
3
|
import type { NotificationDeliveryStatus } from "../../contracts/user-notifications";
|
|
4
|
+
import { sql } from "../../runtime/clients";
|
|
4
5
|
import { escapeLikePattern, toPgTextArray } from "../postgres";
|
|
5
6
|
|
|
6
7
|
const DELIVERY_STATUSES = new Set<NotificationDeliveryStatus>(["deferred", "pending", "sending", "delivered", "suppressed", "failed"]);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import type { output, ZodType } from "zod";
|
|
3
2
|
import { isNotificationGroup, notificationGroupTag } from "../../contracts/notification-group";
|
|
4
3
|
import type {
|
|
@@ -10,6 +9,7 @@ import type {
|
|
|
10
9
|
NotificationSendInput,
|
|
11
10
|
} from "../../contracts/notification-types";
|
|
12
11
|
import { validateNotificationTargetHref } from "../../contracts/notification-types";
|
|
12
|
+
import { sql } from "../../runtime/clients";
|
|
13
13
|
import { normalizeLocale } from "../../shared/locale";
|
|
14
14
|
import { logger } from "../logging";
|
|
15
15
|
import { encryptSecret } from "../secrets";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { dates } from "@k2b/stdlib";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import type { MutationResult } from "../../contracts/shared";
|
|
4
3
|
import {
|
|
5
4
|
NOTIFICATION_DO_NOT_DISTURB_MAX_DAYS,
|
|
@@ -9,6 +8,7 @@ import {
|
|
|
9
8
|
type NotificationQuietState,
|
|
10
9
|
type UpdateNotificationQuietSettings,
|
|
11
10
|
} from "../../contracts/user-notifications";
|
|
11
|
+
import { sql } from "../../runtime/clients";
|
|
12
12
|
import { normalizeTimeZone } from "../../shared/time";
|
|
13
13
|
|
|
14
14
|
const DAY_MS = 24 * 60 * 60_000;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import {
|
|
3
2
|
isSafeNotificationTargetHref,
|
|
4
3
|
type NotificationDefinitionPresentationCatalog,
|
|
@@ -11,6 +10,7 @@ import type {
|
|
|
11
10
|
UserNotificationPreference,
|
|
12
11
|
UserNotificationPreferencesResponse,
|
|
13
12
|
} from "../../contracts/user-notifications";
|
|
13
|
+
import { sql } from "../../runtime/clients";
|
|
14
14
|
import { normalizeLocale } from "../../shared/locale";
|
|
15
15
|
import { toPgTextArray } from "../postgres";
|
|
16
16
|
import { listNotificationChannels } from "./channels";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import { createRemoteJWKSet, decodeProtectedHeader, errors, type JWTPayload, type JWTVerifyGetKey, jwtVerify } from "jose";
|
|
3
2
|
import type { User } from "../contracts/shared";
|
|
3
|
+
import { sql } from "../runtime/clients";
|
|
4
4
|
import { isAccountCategoryAllowed } from "./account-category-policy";
|
|
5
5
|
import { isAccountExpired } from "./account-model";
|
|
6
6
|
import { IDENTITY_JWKS_MAX_AGE_SECONDS } from "./identity/constants";
|