@camstack/server 1.2.592 → 1.2.594
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/addon-root-inventory.js +11 -50
- package/dist/agent/addon-scan.js +6 -44
- package/dist/agent/agent-addon-runtime.js +3 -8
- package/dist/agent/agent-addon-updates.js +12 -52
- package/dist/agent/agent-cap-dispatch-service.js +3 -5
- package/dist/agent/agent-config.js +7 -42
- package/dist/agent/agent-deploy-swap.js +5 -41
- package/dist/agent/agent-group-runner.js +8 -47
- package/dist/agent/agent-http-auth.js +10 -22
- package/dist/agent/agent-http.js +69 -115
- package/dist/agent/agent-hub-addon-update.js +10 -16
- package/dist/agent/agent-hub-registration.js +4 -7
- package/dist/agent/agent-infra-capabilities.js +5 -9
- package/dist/agent/agent-json-file.js +5 -42
- package/dist/agent/agent-mesh-gate.js +5 -8
- package/dist/agent/agent-node-certificates.js +25 -28
- package/dist/agent/agent-pairing-local.js +10 -47
- package/dist/agent/agent-pairing-routes.js +16 -20
- package/dist/agent/agent-pairing-store.js +18 -58
- package/dist/agent/agent-reconnect.js +1 -4
- package/dist/agent/agent-service.js +42 -78
- package/dist/agent/agent-sniffing-listener.js +11 -48
- package/dist/agent/agent-undeploy.js +5 -41
- package/dist/agent/agent-update-routes.js +14 -19
- package/dist/agent/agent-update-service.js +38 -80
- package/dist/agent/apply-model-distribution.js +1 -4
- package/dist/agent/apply-stage-dev-uploads.js +6 -9
- package/dist/agent/derive-hub-url.js +3 -8
- package/dist/agent/fetch-bundle-from-hub.js +6 -9
- package/dist/agent/hubs-found.js +6 -43
- package/dist/agent/infra-boot-guard.js +1 -4
- package/dist/agent/infra-boot-plan.js +2 -6
- package/dist/agent/main.js +158 -186
- package/dist/agent/register-agent-cap-dispatch.js +3 -6
- package/dist/agent/serial-queue.js +2 -7
- package/dist/agent-status-page.js +1 -4
- package/dist/api/addon-upload.js +22 -61
- package/dist/api/agent-addon-delivery.js +6 -11
- package/dist/api/auth-whoami.js +1 -4
- package/dist/api/backup-download-pump.js +5 -9
- package/dist/api/backup-download.js +7 -10
- package/dist/api/core/addon-settings.router.js +16 -19
- package/dist/api/core/agent-pairing.router.js +76 -79
- package/dist/api/core/agents.router.js +21 -24
- package/dist/api/core/auth-admin-tokens.procedures.js +48 -52
- package/dist/api/core/auth-device-cert.procedures.js +62 -67
- package/dist/api/core/auth-limits.js +16 -25
- package/dist/api/core/auth-reauth.procedures.js +32 -37
- package/dist/api/core/auth-revoke-credentials.procedures.js +22 -26
- package/dist/api/core/auth-sessions.procedures.js +52 -56
- package/dist/api/core/auth.router.js +300 -304
- package/dist/api/core/cap-providers.js +74 -121
- package/dist/api/core/capabilities.router.js +34 -37
- package/dist/api/core/capability-preference-writes.js +10 -17
- package/dist/api/core/certificates.router.js +106 -109
- package/dist/api/core/cluster-nodes.router.js +15 -18
- package/dist/api/core/collection-preference.js +2 -6
- package/dist/api/core/event-bus-proxy.router.js +34 -37
- package/dist/api/core/failure-contributions.js +4 -8
- package/dist/api/core/hwaccel.router.js +17 -20
- package/dist/api/core/integration-secrets.js +8 -12
- package/dist/api/core/lifecycle-job-runner.js +13 -49
- package/dist/api/core/live-events.router.js +29 -32
- package/dist/api/core/load-contributions.js +4 -8
- package/dist/api/core/logging-settings.js +30 -43
- package/dist/api/core/logs.router.js +40 -43
- package/dist/api/core/notifications.router.js +15 -18
- package/dist/api/core/public-event-filter.js +4 -8
- package/dist/api/core/repl.router.js +18 -21
- package/dist/api/core/request-census-settings.js +11 -16
- package/dist/api/core/server-management.provider.js +1 -4
- package/dist/api/core/server-name.js +9 -17
- package/dist/api/core/site-location.js +16 -24
- package/dist/api/core/stream-probe.router.js +21 -24
- package/dist/api/core/system-events.router.js +23 -26
- package/dist/api/cors/cors-policy.js +20 -29
- package/dist/api/cors/register-cors-policy.js +9 -14
- package/dist/api/deploy-stage-registry.js +6 -10
- package/dist/api/dev-uploads-bundle.js +12 -50
- package/dist/api/health/adaptive-probe.routes.js +9 -13
- package/dist/api/health/health.routes.js +8 -13
- package/dist/api/hub-cluster-url.js +3 -9
- package/dist/api/model-distributor.js +12 -50
- package/dist/api/oauth2/consent-page.js +10 -13
- package/dist/api/oauth2/oauth2-routes.js +17 -25
- package/dist/api/oauth2/private-host.js +1 -4
- package/dist/api/server-upload-propagate.js +15 -51
- package/dist/api/server-upload.js +13 -50
- package/dist/api/sse-no-compression.js +1 -4
- package/dist/api/static/frame-policy.js +4 -9
- package/dist/api/static/precompressed-asset.js +15 -57
- package/dist/api/static/spa-mount.js +8 -47
- package/dist/api/static/spa-static.js +5 -12
- package/dist/api/tarball-manifest.js +7 -44
- package/dist/api/trpc/cap-mount-helpers.js +7 -12
- package/dist/api/trpc/cap-route-error-formatter.js +10 -13
- package/dist/api/trpc/cap-router-runtime.js +40 -51
- package/dist/api/trpc/client-ip.js +9 -20
- package/dist/api/trpc/core-cap-bridge.js +32 -42
- package/dist/api/trpc/device-collection-dispatch.js +23 -27
- package/dist/api/trpc/device-config-secret-redaction.js +24 -40
- package/dist/api/trpc/generated-cap-mounts.js +219 -221
- package/dist/api/trpc/generated-cap-routers.js +4878 -4726
- package/dist/api/trpc/http-request-census-hook.js +8 -11
- package/dist/api/trpc/http-request-census.js +34 -41
- package/dist/api/trpc/http-request-census.singleton.js +4 -8
- package/dist/api/trpc/localized-schema-paths.js +1 -4
- package/dist/api/trpc/principal-visibility.js +14 -22
- package/dist/api/trpc/remote-addon-update-grant.js +1 -4
- package/dist/api/trpc/scope-access.js +10 -15
- package/dist/api/trpc/server-provided-caps.js +3 -7
- package/dist/api/trpc/share-view-access.js +6 -14
- package/dist/api/trpc/trpc-error-device-tags.js +3 -6
- package/dist/api/trpc/trpc-error-principal.js +2 -6
- package/dist/api/trpc/trpc.context.js +20 -25
- package/dist/api/trpc/trpc.middleware.js +38 -46
- package/dist/api/trpc/trpc.router.js +58 -99
- package/dist/api/trpc/user-error.js +5 -8
- package/dist/api/trpc/ws-request-census.js +17 -24
- package/dist/api/trpc/ws-slow-consumer-guard.js +11 -16
- package/dist/api/upload-auth.js +5 -9
- package/dist/auth/addon-route-jwt-gate.js +4 -7
- package/dist/auth/addon-route-share-gate.js +7 -12
- package/dist/auth/app-confirm-page.js +17 -22
- package/dist/auth/app-confirm-routes.js +26 -31
- package/dist/auth/app-confirmations.js +7 -44
- package/dist/auth/app-page-shell.js +17 -30
- package/dist/auth/app-page-strings.js +11 -17
- package/dist/auth/app-passkey-page.js +16 -21
- package/dist/auth/app-passkey-routes.js +37 -41
- package/dist/auth/app-refused-route.js +13 -18
- package/dist/auth/auth-network-policy.js +11 -15
- package/dist/auth/auth-provider-gate.js +4 -9
- package/dist/auth/auth-rate-limit.js +3 -7
- package/dist/auth/client-address.js +6 -9
- package/dist/auth/device-enroll-proofs.js +6 -43
- package/dist/auth/external-link-intents.js +3 -7
- package/dist/auth/hub-sessions.js +6 -12
- package/dist/auth/native-link-completions.js +7 -11
- package/dist/auth/native-return.js +9 -13
- package/dist/auth/passkey-provider.js +26 -32
- package/dist/auth/second-factors.js +6 -10
- package/dist/auth/session-connections.js +6 -12
- package/dist/auth/session-cookie.js +12 -23
- package/dist/auth/session-lifetimes.js +8 -12
- package/dist/auth/session-mint.js +4 -11
- package/dist/auth/sso-finish-app.js +13 -16
- package/dist/auth/sso-finish-deps.js +21 -27
- package/dist/auth/sso-finish.js +28 -38
- package/dist/auth/sso-routes.js +22 -25
- package/dist/auth/used-bridge-ids.js +1 -5
- package/dist/boot/boot-config.js +16 -55
- package/dist/boot/init-sequence.js +1 -4
- package/dist/boot/integration-id-backfill.js +3 -8
- package/dist/boot/platform-keychain-key.js +5 -42
- package/dist/boot/post-boot.service.js +14 -18
- package/dist/boot/reconcile-lifecycle-jobs.js +3 -6
- package/dist/boot/retired-public-url-env.js +4 -8
- package/dist/bootstrap-packages.js +1 -4
- package/dist/closure-node-modules.js +2 -38
- package/dist/closure-patches/patch-reach.json +6 -2
- package/dist/core/addon/addon-call-gateway.js +1 -5
- package/dist/core/addon/addon-package.service.js +83 -123
- package/dist/core/addon/addon-process-view.js +2 -6
- package/dist/core/addon/addon-realign-plan.js +1 -4
- package/dist/core/addon/addon-registry.service.js +254 -197
- package/dist/core/addon/addon-row-manifest.js +1 -4
- package/dist/core/addon/addon-search.service.js +3 -7
- package/dist/core/addon/addon-settings-provider.js +1 -3
- package/dist/core/addon/addon-uninstall.js +4 -11
- package/dist/core/addon/addon.tokens.js +2 -5
- package/dist/core/addon/device-meta-mirror.js +8 -13
- package/dist/core/addon/forked-custom-actions.js +6 -11
- package/dist/core/addon/integration-visibility.js +3 -6
- package/dist/core/addon/materialize-route-path.js +1 -4
- package/dist/core/addon/package-dir-utils.js +8 -45
- package/dist/core/addon/prune-misplaced-addons.js +5 -43
- package/dist/core/addon/runner-convergence.js +1 -4
- package/dist/core/addon/runner-serialization.js +1 -4
- package/dist/core/addon/runner-spawn-fanout.js +3 -8
- package/dist/core/addon/settings-door-wiring.js +5 -8
- package/dist/core/addon/single-flight-refresh.js +1 -4
- package/dist/core/addon-bridge/addon-bridge.service.js +7 -44
- package/dist/core/addon-pages/addon-pages.service.js +3 -40
- package/dist/core/addon-widgets/addon-widgets.service.js +4 -42
- package/dist/core/agent/agent-addon-backfill.js +9 -17
- package/dist/core/agent/agent-delivery-ledger.js +3 -7
- package/dist/core/agent/agent-liveness-event.js +2 -6
- package/dist/core/agent/agent-pairing-http.js +41 -45
- package/dist/core/agent/agent-registry.service.js +53 -92
- package/dist/core/agent/cluster-node-history-mirror.js +3 -7
- package/dist/core/agent/cluster-node-history-store.js +113 -58
- package/dist/core/agent/hub-agent-pairing.js +1 -5
- package/dist/core/agent/node-certificates-client.js +3 -6
- package/dist/core/agent/node-directory.js +1 -4
- package/dist/core/app-links/well-known-app-links.js +2 -6
- package/dist/core/auth/auth.service.js +3 -8
- package/dist/core/auth/handoff-code.service.js +11 -49
- package/dist/core/auth/record-kinds.js +35 -0
- package/dist/core/auth/share-token.service.js +174 -122
- package/dist/core/capability/capability.service.js +1 -5
- package/dist/core/config/config.schema.js +1 -6
- package/dist/core/config/config.service.js +2 -6
- package/dist/core/events/event-bus.service.js +3 -7
- package/dist/core/feature/feature.service.js +2 -6
- package/dist/core/lifecycle/lifecycle-runner.singleton.js +5 -10
- package/dist/core/lifecycle/lifecycle-state-machine.js +1 -5
- package/dist/core/logging/log-ring-buffer.js +1 -5
- package/dist/core/logging/logging.service.js +5 -9
- package/dist/core/logging/scoped-logger.js +1 -5
- package/dist/core/moleculer/agent-readiness-pull.js +1 -4
- package/dist/core/moleculer/agent-registration-request.js +3 -7
- package/dist/core/moleculer/cap-call-fn.js +39 -5
- package/dist/core/moleculer/cap-route-authority.js +3 -8
- package/dist/core/moleculer/hub-mesh-guard.js +5 -9
- package/dist/core/moleculer/moleculer.service.js +108 -119
- package/dist/core/network/network-quality.service.js +2 -6
- package/dist/core/notification/notification-wrapper.service.js +3 -7
- package/dist/core/notification/toast-wrapper.service.js +3 -7
- package/dist/core/provider/provider.tokens.js +1 -4
- package/dist/core/repl/repl-engine.service.js +4 -8
- package/dist/core/runner-health/runner-boot-check.js +7 -15
- package/dist/core/runner-health/update-preflight.js +9 -14
- package/dist/core/server-update/server-update.service.js +12 -49
- package/dist/core/server-update/system-apply-closure-patches.js +6 -42
- package/dist/core/server-update/system-ensure-prebuilds.js +3 -6
- package/dist/core/server-update/system-exec-npm.js +2 -5
- package/dist/core/storage/fs-storage-backend.js +1 -5
- package/dist/core/storage/storage-location-manager.js +1 -5
- package/dist/core/storage/storage.service.js +2 -6
- package/dist/core/streaming/ffmpeg-stream-info.js +3 -8
- package/dist/core/streaming/stream-probe.service.js +12 -16
- package/dist/core/streaming/ttl-cache.js +1 -5
- package/dist/core/topology/topology-emitter.service.js +19 -23
- package/dist/core/update-availability-emitter.js +3 -7
- package/dist/core/update-availability-store.js +8 -45
- package/dist/core/updates/agent-installed-packages.js +5 -9
- package/dist/core/updates/restart-marker.js +3 -8
- package/dist/core/updates/update-check-scheduler.js +13 -18
- package/dist/core/updates/wrapper-update-checker.js +3 -7
- package/dist/first-boot-addon-plan.js +2 -6
- package/dist/framework-nodepath.js +2 -38
- package/dist/i18n/request-lang.js +7 -11
- package/dist/launcher.js +30 -65
- package/dist/lifecycle-journal-path.js +2 -38
- package/dist/main.js +287 -324
- package/dist/manual-boot.js +120 -157
- package/dist/node-role.js +1 -4
- package/dist/package-inventory.js +7 -48
- package/dist/pending-restore.js +7 -43
- package/dist/single-copy-cleanup-runner.js +9 -45
- package/dist/single-copy-cleanup.js +7 -15
- package/dist/sqlite-restore.js +9 -48
- package/dist/starter.js +4 -39
- package/dist/ui-serving/lazy-ui-serving.js +3 -39
- package/package.json +13 -13
|
@@ -1,33 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
expiresAt: zod_1.z.number().optional(),
|
|
20
|
-
sessionId: zod_1.z.string().optional(),
|
|
21
|
-
sessionExpiresAt: zod_1.z.number().optional(),
|
|
22
|
-
refreshToken: zod_1.z.string().optional(),
|
|
23
|
-
credentialId: zod_1.z.string(),
|
|
1
|
+
import { getUserManagementInternal } from '@camstack/system';
|
|
2
|
+
import { CompleteEnrollmentInputSchema, CompleteEnrollmentResultSchema, DEVICE_CERT_SIGNATURE, DeviceChallengeResultSchema, DeviceCredentialSchema, DeviceEnrollProofSchema, DeviceProofInputSchema, DeviceRefusalSchema, EnrollSelfInputSchema, formatDeviceRevoker, isDefinitiveDeviceRefusal, RenewDeviceCertificateResultSchema, RevokeOwnDeviceCertificateResultSchema, } from '@camstack/types';
|
|
3
|
+
import { TRPCError } from '@trpc/server';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { SessionUserGoneError, } from '../../auth/session-mint.js';
|
|
6
|
+
import { protectedProcedure } from '../trpc/trpc.middleware.js';
|
|
7
|
+
import { assertReauthenticated, ReauthTokenInput } from './auth-reauth.procedures.js';
|
|
8
|
+
import { trpcUserError } from '../trpc/user-error.js';
|
|
9
|
+
const DeviceLoginResultSchema = z.discriminatedUnion('ok', [
|
|
10
|
+
z.object({
|
|
11
|
+
ok: z.literal(true),
|
|
12
|
+
token: z.string(),
|
|
13
|
+
user: z.object({ id: z.string(), username: z.string(), isAdmin: z.boolean() }),
|
|
14
|
+
expiresAt: z.number().optional(),
|
|
15
|
+
sessionId: z.string().optional(),
|
|
16
|
+
sessionExpiresAt: z.number().optional(),
|
|
17
|
+
refreshToken: z.string().optional(),
|
|
18
|
+
credentialId: z.string(),
|
|
24
19
|
/** The certificate's expiry (epoch ms): the app renews when < 30 days remain. */
|
|
25
|
-
certNotAfter:
|
|
20
|
+
certNotAfter: z.number(),
|
|
26
21
|
}),
|
|
27
|
-
|
|
22
|
+
DeviceRefusalSchema,
|
|
28
23
|
]);
|
|
29
24
|
/** Internal manager reason → the wire reason an app switches on. */
|
|
30
|
-
function toWireReason(reason) {
|
|
25
|
+
export function toWireReason(reason) {
|
|
31
26
|
switch (reason) {
|
|
32
27
|
case 'revoked':
|
|
33
28
|
case 'unknown-credential':
|
|
@@ -58,11 +53,11 @@ function toWireReason(reason) {
|
|
|
58
53
|
return 'superseded';
|
|
59
54
|
}
|
|
60
55
|
}
|
|
61
|
-
function refusal(reason, retryAfterSeconds) {
|
|
56
|
+
export function refusal(reason, retryAfterSeconds) {
|
|
62
57
|
return {
|
|
63
58
|
ok: false,
|
|
64
59
|
reason,
|
|
65
|
-
definitive:
|
|
60
|
+
definitive: isDefinitiveDeviceRefusal(reason),
|
|
66
61
|
...(retryAfterSeconds !== undefined ? { retryAfterSeconds } : {}),
|
|
67
62
|
};
|
|
68
63
|
}
|
|
@@ -70,13 +65,13 @@ function fromAddress(req, network) {
|
|
|
70
65
|
const ip = network.clientAddress(req).ip;
|
|
71
66
|
return ip ?? undefined;
|
|
72
67
|
}
|
|
73
|
-
function createDeviceCertProcedures(deps) {
|
|
68
|
+
export function createDeviceCertProcedures(deps) {
|
|
74
69
|
const internal = () => {
|
|
75
70
|
const found = deps.registry === null
|
|
76
71
|
? null
|
|
77
|
-
:
|
|
72
|
+
: getUserManagementInternal(deps.registry.getSingleton('user-management'));
|
|
78
73
|
if (found === null) {
|
|
79
|
-
throw
|
|
74
|
+
throw trpcUserError('PRECONDITION_FAILED', 'device.certificates-unavailable', undefined, 'Device certificates are not available');
|
|
80
75
|
}
|
|
81
76
|
return found;
|
|
82
77
|
};
|
|
@@ -111,13 +106,13 @@ function createDeviceCertProcedures(deps) {
|
|
|
111
106
|
};
|
|
112
107
|
const assertPerson = (user) => {
|
|
113
108
|
if (user.isScoped === true || user.shareView !== undefined || user.adminTokenId !== undefined) {
|
|
114
|
-
throw
|
|
109
|
+
throw trpcUserError('FORBIDDEN', 'auth.session-required', undefined, 'Requires a signed-in user session');
|
|
115
110
|
}
|
|
116
111
|
};
|
|
117
112
|
const selfLimit = (userId, path) => {
|
|
118
113
|
const verdict = deps.selfLimiter.check(`${path}:${userId}`);
|
|
119
114
|
if (!verdict.allowed) {
|
|
120
|
-
throw new
|
|
115
|
+
throw new TRPCError({
|
|
121
116
|
code: 'TOO_MANY_REQUESTS',
|
|
122
117
|
message: `Too many attempts — retry in ${verdict.retryAfterSeconds}s`,
|
|
123
118
|
});
|
|
@@ -126,8 +121,8 @@ function createDeviceCertProcedures(deps) {
|
|
|
126
121
|
return {
|
|
127
122
|
/** PUBLIC — redeem a QR enrollment code with the device's PUBLIC key; answers the certificate. */
|
|
128
123
|
completeEnrollment: deps.publicProcedure
|
|
129
|
-
.input(
|
|
130
|
-
.output(
|
|
124
|
+
.input(CompleteEnrollmentInputSchema)
|
|
125
|
+
.output(CompleteEnrollmentResultSchema)
|
|
131
126
|
.mutation(async ({ input, ctx }) => {
|
|
132
127
|
const slow = limited(deps.credentialLimiter, 'completeEnrollment', ctx.req);
|
|
133
128
|
if (slow !== null)
|
|
@@ -150,8 +145,8 @@ function createDeviceCertProcedures(deps) {
|
|
|
150
145
|
}),
|
|
151
146
|
/** PUBLIC — a single-use nonce (60 s), bound to the hub origin this request is entitled to. */
|
|
152
147
|
getDeviceChallenge: deps.publicProcedure
|
|
153
|
-
.input(
|
|
154
|
-
.output(
|
|
148
|
+
.input(z.void())
|
|
149
|
+
.output(DeviceChallengeResultSchema)
|
|
155
150
|
.mutation(async ({ ctx }) => {
|
|
156
151
|
const slow = limited(deps.ceremonyLimiter, 'getDeviceChallenge', ctx.req);
|
|
157
152
|
if (slow !== null)
|
|
@@ -167,7 +162,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
167
162
|
if (origin === null)
|
|
168
163
|
return refusal('unavailable');
|
|
169
164
|
const { nonce, expiresAt } = internal().deviceCredentials.issueNonce(origin, partition);
|
|
170
|
-
return { ok: true, nonce, origin, expiresAt, signature:
|
|
165
|
+
return { ok: true, nonce, origin, expiresAt, signature: DEVICE_CERT_SIGNATURE };
|
|
171
166
|
}),
|
|
172
167
|
/**
|
|
173
168
|
* PUBLIC — sign in with the device certificate: chain to the device CA, not
|
|
@@ -175,7 +170,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
175
170
|
* Opens the SAME session a password login opens, carrying the credential id.
|
|
176
171
|
*/
|
|
177
172
|
loginWithDeviceCertificate: deps.publicProcedure
|
|
178
|
-
.input(
|
|
173
|
+
.input(DeviceProofInputSchema.extend({ refreshable: z.boolean().optional() }))
|
|
179
174
|
.output(DeviceLoginResultSchema)
|
|
180
175
|
.mutation(async ({ input, ctx }) => {
|
|
181
176
|
const slow = limited(deps.credentialLimiter, 'loginWithDeviceCertificate', ctx.req);
|
|
@@ -220,7 +215,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
220
215
|
};
|
|
221
216
|
}
|
|
222
217
|
catch (error) {
|
|
223
|
-
if (error instanceof
|
|
218
|
+
if (error instanceof SessionUserGoneError) {
|
|
224
219
|
deps.logger?.warn('Device certificate login refused: user-gone', {
|
|
225
220
|
tags: { credentialId: credential.id, userId: credential.userId },
|
|
226
221
|
});
|
|
@@ -231,8 +226,8 @@ function createDeviceCertProcedures(deps) {
|
|
|
231
226
|
}),
|
|
232
227
|
/** PUBLIC — a new leaf for the SAME public key, while the certificate is valid and not revoked. */
|
|
233
228
|
renewDeviceCertificate: deps.publicProcedure
|
|
234
|
-
.input(
|
|
235
|
-
.output(
|
|
229
|
+
.input(DeviceProofInputSchema)
|
|
230
|
+
.output(RenewDeviceCertificateResultSchema)
|
|
236
231
|
.mutation(async ({ input, ctx }) => {
|
|
237
232
|
const slow = limited(deps.credentialLimiter, 'renewDeviceCertificate', ctx.req);
|
|
238
233
|
if (slow !== null)
|
|
@@ -261,8 +256,8 @@ function createDeviceCertProcedures(deps) {
|
|
|
261
256
|
* purpose): works with no session. Ends every session the credential opened.
|
|
262
257
|
*/
|
|
263
258
|
revokeOwnDeviceCertificate: deps.publicProcedure
|
|
264
|
-
.input(
|
|
265
|
-
.output(
|
|
259
|
+
.input(DeviceProofInputSchema)
|
|
260
|
+
.output(RevokeOwnDeviceCertificateResultSchema)
|
|
266
261
|
.mutation(async ({ input, ctx }) => {
|
|
267
262
|
const slow = limited(deps.credentialLimiter, 'revokeOwnDeviceCertificate', ctx.req);
|
|
268
263
|
if (slow !== null)
|
|
@@ -276,7 +271,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
276
271
|
if (!checked.ok)
|
|
277
272
|
return refusal(toWireReason(checked.reason));
|
|
278
273
|
const id = checked.credential.id;
|
|
279
|
-
await internal().deviceCredentials.revoke(id,
|
|
274
|
+
await internal().deviceCredentials.revoke(id, formatDeviceRevoker({ kind: 'device' }));
|
|
280
275
|
const sessionsEnded = await internal().sessions.revokeByCredential(id);
|
|
281
276
|
await audit('device-credential-revoked', checked.credential.userId, ctx.req, {
|
|
282
277
|
credentialId: id,
|
|
@@ -287,9 +282,9 @@ function createDeviceCertProcedures(deps) {
|
|
|
287
282
|
}),
|
|
288
283
|
// ── Signed-in legs ───────────────────────────────────────────────
|
|
289
284
|
/** The caller's own device credentials (live ones, and revoked when asked). */
|
|
290
|
-
listOwnDeviceCredentials:
|
|
291
|
-
.input(
|
|
292
|
-
.output(
|
|
285
|
+
listOwnDeviceCredentials: protectedProcedure
|
|
286
|
+
.input(z.object({ includeRevoked: z.boolean().optional() }).optional())
|
|
287
|
+
.output(z.array(DeviceCredentialSchema))
|
|
293
288
|
.query(({ ctx, input }) => {
|
|
294
289
|
assertPerson(ctx.user);
|
|
295
290
|
const manager = internal().deviceCredentials;
|
|
@@ -317,9 +312,9 @@ function createDeviceCertProcedures(deps) {
|
|
|
317
312
|
}));
|
|
318
313
|
}),
|
|
319
314
|
/** Revoke one of the caller's OWN credentials by id, from a session (e.g. a lost phone). */
|
|
320
|
-
revokeOwnDeviceCredential:
|
|
321
|
-
.input(
|
|
322
|
-
.output(
|
|
315
|
+
revokeOwnDeviceCredential: protectedProcedure
|
|
316
|
+
.input(z.object({ id: z.string().min(1) }))
|
|
317
|
+
.output(z.object({ revoked: z.boolean(), sessionsEnded: z.number().int() }))
|
|
323
318
|
.mutation(async ({ ctx, input }) => {
|
|
324
319
|
assertPerson(ctx.user);
|
|
325
320
|
selfLimit(ctx.user.id, 'revokeOwnDeviceCredential');
|
|
@@ -331,7 +326,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
331
326
|
});
|
|
332
327
|
return { revoked: false, sessionsEnded: 0 };
|
|
333
328
|
}
|
|
334
|
-
const revoked = await internal().deviceCredentials.revoke(input.id,
|
|
329
|
+
const revoked = await internal().deviceCredentials.revoke(input.id, formatDeviceRevoker({ kind: 'user', userId: ctx.user.id }));
|
|
335
330
|
const sessionsEnded = await internal().sessions.revokeByCredential(input.id);
|
|
336
331
|
if (revoked) {
|
|
337
332
|
await audit('device-credential-revoked', ctx.user.id, ctx.req, {
|
|
@@ -343,16 +338,16 @@ function createDeviceCertProcedures(deps) {
|
|
|
343
338
|
return { revoked, sessionsEnded };
|
|
344
339
|
}),
|
|
345
340
|
/** Step-up, password / passkey: turn a fresh `reauthenticate` proof into a single-use device-enroll proof. */
|
|
346
|
-
deviceEnrollProofFromReauth:
|
|
347
|
-
.input(
|
|
348
|
-
.output(
|
|
341
|
+
deviceEnrollProofFromReauth: protectedProcedure
|
|
342
|
+
.input(z.object({ reauthToken: ReauthTokenInput }))
|
|
343
|
+
.output(DeviceEnrollProofSchema)
|
|
349
344
|
.mutation(({ ctx, input }) => {
|
|
350
345
|
assertPerson(ctx.user);
|
|
351
346
|
selfLimit(ctx.user.id, 'deviceEnrollProofFromReauth');
|
|
352
|
-
|
|
347
|
+
assertReauthenticated(deps.auth, ctx.user, input.reauthToken);
|
|
353
348
|
// One reauthentication, one proof (or one step-up handoff code).
|
|
354
349
|
if (!deps.enrollProofs.consumeReauth(input.reauthToken)) {
|
|
355
|
-
throw new
|
|
350
|
+
throw new TRPCError({
|
|
356
351
|
code: 'FORBIDDEN',
|
|
357
352
|
message: 'This re-authentication was already used - confirm it is you again',
|
|
358
353
|
});
|
|
@@ -364,13 +359,13 @@ function createDeviceCertProcedures(deps) {
|
|
|
364
359
|
* `code` is a handoff code minted with purpose `step-up` by a browser sign-in
|
|
365
360
|
* of the SAME user. Opens no session and replaces none.
|
|
366
361
|
*/
|
|
367
|
-
deviceEnrollProofFromHandoff:
|
|
368
|
-
.input(
|
|
369
|
-
code:
|
|
370
|
-
codeVerifier:
|
|
371
|
-
state:
|
|
362
|
+
deviceEnrollProofFromHandoff: protectedProcedure
|
|
363
|
+
.input(z.object({
|
|
364
|
+
code: z.string().min(1).max(256),
|
|
365
|
+
codeVerifier: z.string().min(1).max(256).optional(),
|
|
366
|
+
state: z.string().min(1).max(256).optional(),
|
|
372
367
|
}))
|
|
373
|
-
.output(
|
|
368
|
+
.output(DeviceEnrollProofSchema)
|
|
374
369
|
.mutation(({ ctx, input }) => {
|
|
375
370
|
assertPerson(ctx.user);
|
|
376
371
|
selfLimit(ctx.user.id, 'deviceEnrollProofFromHandoff');
|
|
@@ -386,7 +381,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
386
381
|
reason: reason ?? (grant?.purpose !== 'step-up' ? 'wrong-purpose' : 'other-user'),
|
|
387
382
|
},
|
|
388
383
|
});
|
|
389
|
-
throw new
|
|
384
|
+
throw new TRPCError({
|
|
390
385
|
code: 'UNAUTHORIZED',
|
|
391
386
|
message: 'Invalid, expired or mismatched step-up code — sign in again',
|
|
392
387
|
});
|
|
@@ -399,9 +394,9 @@ function createDeviceCertProcedures(deps) {
|
|
|
399
394
|
* a merely refreshed session can never mint a permanent credential.
|
|
400
395
|
* Hub-wide `deviceCertificates.selfEnrollment = disabled` refuses.
|
|
401
396
|
*/
|
|
402
|
-
enrollSelf:
|
|
403
|
-
.input(
|
|
404
|
-
.output(
|
|
397
|
+
enrollSelf: protectedProcedure
|
|
398
|
+
.input(EnrollSelfInputSchema)
|
|
399
|
+
.output(CompleteEnrollmentResultSchema)
|
|
405
400
|
.mutation(async ({ ctx, input }) => {
|
|
406
401
|
assertPerson(ctx.user);
|
|
407
402
|
selfLimit(ctx.user.id, 'enrollSelf');
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PROCESS_AUTH_LIMITERS = void 0;
|
|
4
|
-
exports.createAuthLimiters = createAuthLimiters;
|
|
5
|
-
exports.tooMany = tooMany;
|
|
6
|
-
exports.limitByClient = limitByClient;
|
|
7
|
-
exports.assertAccountNotLocked = assertAccountNotLocked;
|
|
8
|
-
exports.recordAccountFailure = recordAccountFailure;
|
|
9
|
-
exports.limitByUser = limitByUser;
|
|
10
1
|
/**
|
|
11
2
|
* Rate limits of the public auth surface, in one place.
|
|
12
3
|
*
|
|
@@ -29,29 +20,29 @@ exports.limitByUser = limitByUser;
|
|
|
29
20
|
*
|
|
30
21
|
* Mesh-internal calls (no originating request) are never limited.
|
|
31
22
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function createAuthLimiters(now) {
|
|
23
|
+
import { TRPCError } from '@trpc/server';
|
|
24
|
+
import { DEFAULT_AUTH_NETWORK_POLICY, } from '../../auth/auth-network-policy.js';
|
|
25
|
+
import { createRateLimiter } from '../../auth/auth-rate-limit.js';
|
|
26
|
+
export function createAuthLimiters(now) {
|
|
36
27
|
const clock = now !== undefined ? { now } : {};
|
|
37
28
|
return {
|
|
38
|
-
credential:
|
|
39
|
-
ceremony:
|
|
40
|
-
accountFailures:
|
|
41
|
-
link:
|
|
42
|
-
reauth:
|
|
29
|
+
credential: createRateLimiter({ windowMs: 60_000, max: 10, ...clock }),
|
|
30
|
+
ceremony: createRateLimiter({ windowMs: 60_000, max: 30, ...clock }),
|
|
31
|
+
accountFailures: createRateLimiter({ windowMs: 15 * 60_000, max: 20, ...clock }),
|
|
32
|
+
link: createRateLimiter({ windowMs: 60_000, max: 5, ...clock }),
|
|
33
|
+
reauth: createRateLimiter({ windowMs: 60_000, max: 10, ...clock }),
|
|
43
34
|
};
|
|
44
35
|
}
|
|
45
36
|
/** The process-wide limiters: router rebuilds must not reset an attacker's budget. */
|
|
46
|
-
|
|
47
|
-
function tooMany(retryAfterSeconds) {
|
|
48
|
-
return new
|
|
37
|
+
export const PROCESS_AUTH_LIMITERS = createAuthLimiters();
|
|
38
|
+
export function tooMany(retryAfterSeconds) {
|
|
39
|
+
return new TRPCError({
|
|
49
40
|
code: 'TOO_MANY_REQUESTS',
|
|
50
41
|
message: `Too many attempts — retry in ${retryAfterSeconds}s`,
|
|
51
42
|
});
|
|
52
43
|
}
|
|
53
44
|
/** Count one attempt from the request's client address; throws when over. */
|
|
54
|
-
function limitByClient(limiter, bucket, req, network =
|
|
45
|
+
export function limitByClient(limiter, bucket, req, network = DEFAULT_AUTH_NETWORK_POLICY) {
|
|
55
46
|
const ip = network.clientAddress(req).ip;
|
|
56
47
|
if (ip === null)
|
|
57
48
|
return;
|
|
@@ -60,17 +51,17 @@ function limitByClient(limiter, bucket, req, network = auth_network_policy_js_1.
|
|
|
60
51
|
throw tooMany(verdict.retryAfterSeconds);
|
|
61
52
|
}
|
|
62
53
|
/** Refuse before checking a secret when `account` already failed too often. */
|
|
63
|
-
function assertAccountNotLocked(limiters, account) {
|
|
54
|
+
export function assertAccountNotLocked(limiters, account) {
|
|
64
55
|
const verdict = limiters.accountFailures.peek(`account:${account.toLowerCase()}`);
|
|
65
56
|
if (!verdict.allowed)
|
|
66
57
|
throw tooMany(verdict.retryAfterSeconds);
|
|
67
58
|
}
|
|
68
59
|
/** Count one failed secret against `account`. */
|
|
69
|
-
function recordAccountFailure(limiters, account) {
|
|
60
|
+
export function recordAccountFailure(limiters, account) {
|
|
70
61
|
limiters.accountFailures.check(`account:${account.toLowerCase()}`);
|
|
71
62
|
}
|
|
72
63
|
/** Count one call of an authenticated leg against the user; throws when over. */
|
|
73
|
-
function limitByUser(limiter, bucket, userId) {
|
|
64
|
+
export function limitByUser(limiter, bucket, userId) {
|
|
74
65
|
const verdict = limiter.check(`${bucket}:${userId}`);
|
|
75
66
|
if (!verdict.allowed)
|
|
76
67
|
throw tooMany(verdict.retryAfterSeconds);
|
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const zod_1 = require("zod");
|
|
8
|
-
const passkey_provider_js_1 = require("../../auth/passkey-provider.js");
|
|
9
|
-
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
10
|
-
const auth_limits_js_1 = require("./auth-limits.js");
|
|
11
|
-
const user_error_js_1 = require("../trpc/user-error.js");
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { OptionsJsonSchema, resolvePasskeyProvider } from '../../auth/passkey-provider.js';
|
|
4
|
+
import { protectedProcedure } from '../trpc/trpc.middleware.js';
|
|
5
|
+
import { assertAccountNotLocked, limitByUser, recordAccountFailure, } from './auth-limits.js';
|
|
6
|
+
import { trpcUserError } from '../trpc/user-error.js';
|
|
12
7
|
/** The input field a re-auth-gated procedure takes. */
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
export const ReauthTokenInput = z.string().min(1).max(4096);
|
|
9
|
+
export const REAUTH_REQUIRED_MESSAGE = 'Confirm it is you first: re-enter your password or use a passkey (update the app if it cannot ask).';
|
|
15
10
|
/**
|
|
16
11
|
* Throw unless `reauthToken` is a live proof by THIS user, made in THIS
|
|
17
12
|
* session. A missing token is the same refusal: the action needs one.
|
|
18
13
|
*/
|
|
19
|
-
function assertReauthenticated(auth, user, reauthToken) {
|
|
14
|
+
export function assertReauthenticated(auth, user, reauthToken) {
|
|
20
15
|
const claims = reauthToken === undefined ? null : auth.verifyReauthToken(reauthToken);
|
|
21
16
|
const valid = claims !== null &&
|
|
22
17
|
claims.userId === user.id &&
|
|
23
18
|
(user.sessionId === undefined || claims.sessionId === user.sessionId);
|
|
24
19
|
if (!valid)
|
|
25
|
-
throw new
|
|
20
|
+
throw new TRPCError({ code: 'FORBIDDEN', message: REAUTH_REQUIRED_MESSAGE });
|
|
26
21
|
}
|
|
27
22
|
function isPasswordChecker(value) {
|
|
28
23
|
return (value !== null &&
|
|
@@ -32,19 +27,19 @@ function isPasswordChecker(value) {
|
|
|
32
27
|
/** Real user sessions only: a scoped or share token cannot prove a password. */
|
|
33
28
|
function assertPerson(user) {
|
|
34
29
|
if (user.isScoped === true || user.shareView !== undefined || user.adminTokenId !== undefined) {
|
|
35
|
-
throw
|
|
30
|
+
throw trpcUserError('FORBIDDEN', 'auth.session-required', undefined, 'Requires a signed-in user session');
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
|
-
function createReauthProcedures(deps) {
|
|
33
|
+
export function createReauthProcedures(deps) {
|
|
39
34
|
return {
|
|
40
35
|
/** Passkey re-authentication, step 1: assertion options for the caller's own credentials. */
|
|
41
|
-
reauthBeginPasskey:
|
|
42
|
-
.input(
|
|
43
|
-
.output(
|
|
36
|
+
reauthBeginPasskey: protectedProcedure
|
|
37
|
+
.input(z.void())
|
|
38
|
+
.output(OptionsJsonSchema)
|
|
44
39
|
.mutation(async ({ ctx }) => {
|
|
45
40
|
assertPerson(ctx.user);
|
|
46
|
-
|
|
47
|
-
const provider =
|
|
41
|
+
limitByUser(deps.limiters.reauth, 'auth.reauthBeginPasskey', ctx.user.id);
|
|
42
|
+
const provider = resolvePasskeyProvider(deps.registry, deps.moleculer);
|
|
48
43
|
if (!provider)
|
|
49
44
|
throw new Error('Passkey authentication is not available');
|
|
50
45
|
return provider.beginAuthentication({ userId: ctx.user.id });
|
|
@@ -54,20 +49,20 @@ function createReauthProcedures(deps) {
|
|
|
54
49
|
* `reauthBeginPasskey`. Answers a 5-minute proof for the re-auth-gated
|
|
55
50
|
* actions, valid only for this user in this session.
|
|
56
51
|
*/
|
|
57
|
-
reauthenticate:
|
|
58
|
-
.input(
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
reauthenticate: protectedProcedure
|
|
53
|
+
.input(z.union([
|
|
54
|
+
z.object({ password: z.string().min(1).max(1024) }),
|
|
55
|
+
z.object({ passkeyResponse: z.record(z.string(), z.unknown()) }),
|
|
61
56
|
]))
|
|
62
|
-
.output(
|
|
63
|
-
reauthToken:
|
|
64
|
-
expiresAt:
|
|
65
|
-
method:
|
|
57
|
+
.output(z.object({
|
|
58
|
+
reauthToken: z.string(),
|
|
59
|
+
expiresAt: z.number(),
|
|
60
|
+
method: z.enum(['password', 'passkey']),
|
|
66
61
|
}))
|
|
67
62
|
.mutation(async ({ ctx, input }) => {
|
|
68
63
|
assertPerson(ctx.user);
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
limitByUser(deps.limiters.reauth, 'auth.reauthenticate', ctx.user.id);
|
|
65
|
+
assertAccountNotLocked(deps.limiters, ctx.user.username);
|
|
71
66
|
let method;
|
|
72
67
|
if ('password' in input) {
|
|
73
68
|
const userMgmt = deps.registry?.getSingleton('user-management');
|
|
@@ -79,13 +74,13 @@ function createReauthProcedures(deps) {
|
|
|
79
74
|
password: input.password,
|
|
80
75
|
});
|
|
81
76
|
if (record === null || record.id !== ctx.user.id) {
|
|
82
|
-
|
|
83
|
-
throw
|
|
77
|
+
recordAccountFailure(deps.limiters, ctx.user.username);
|
|
78
|
+
throw trpcUserError('UNAUTHORIZED', 'auth.password-incorrect', undefined, 'Password is incorrect');
|
|
84
79
|
}
|
|
85
80
|
method = 'password';
|
|
86
81
|
}
|
|
87
82
|
else {
|
|
88
|
-
const provider =
|
|
83
|
+
const provider = resolvePasskeyProvider(deps.registry, deps.moleculer);
|
|
89
84
|
if (!provider)
|
|
90
85
|
throw new Error('Passkey authentication is not available');
|
|
91
86
|
const result = await provider.finishAuthentication({
|
|
@@ -93,8 +88,8 @@ function createReauthProcedures(deps) {
|
|
|
93
88
|
response: input.passkeyResponse,
|
|
94
89
|
});
|
|
95
90
|
if (!result.verified) {
|
|
96
|
-
|
|
97
|
-
throw
|
|
91
|
+
recordAccountFailure(deps.limiters, ctx.user.username);
|
|
92
|
+
throw trpcUserError('UNAUTHORIZED', 'auth.passkey-verification-failed', undefined, 'Passkey verification failed');
|
|
98
93
|
}
|
|
99
94
|
method = 'passkey';
|
|
100
95
|
}
|
|
@@ -1,36 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const server_1 = require("@trpc/server");
|
|
6
|
-
const zod_1 = require("zod");
|
|
7
|
-
const hub_sessions_js_1 = require("../../auth/hub-sessions.js");
|
|
8
|
-
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { userManagementInternal } from '../../auth/hub-sessions.js';
|
|
4
|
+
import { adminProcedure } from '../trpc/trpc.middleware.js';
|
|
9
5
|
/** Upper bound of one bulk call — a page of a table, several times over. */
|
|
10
|
-
|
|
6
|
+
export const REVOKE_CREDENTIALS_MAX = 500;
|
|
11
7
|
/** What a bulk revoke can target. Each has a per-row revoke on the server. */
|
|
12
|
-
|
|
8
|
+
export const RevocableCredentialKindSchema = z.enum([
|
|
13
9
|
'share-link',
|
|
14
10
|
'admin-token',
|
|
15
11
|
'login-session',
|
|
16
12
|
'scoped-token',
|
|
17
13
|
'oauth-session',
|
|
18
14
|
]);
|
|
19
|
-
|
|
20
|
-
const RevokeCredentialsInputSchema =
|
|
15
|
+
export const RevokeSkipReasonSchema = z.enum(['current-session', 'not-active']);
|
|
16
|
+
const RevokeCredentialsInputSchema = z
|
|
21
17
|
.object({
|
|
22
|
-
kind:
|
|
23
|
-
ids:
|
|
18
|
+
kind: RevocableCredentialKindSchema,
|
|
19
|
+
ids: z.array(z.string().min(1)).min(1).max(REVOKE_CREDENTIALS_MAX),
|
|
24
20
|
/** The count the operator confirmed. Must equal `ids.length`. */
|
|
25
|
-
confirmCount:
|
|
21
|
+
confirmCount: z.number().int().min(1),
|
|
26
22
|
})
|
|
27
23
|
.refine((input) => new Set(input.ids).size === input.ids.length, {
|
|
28
24
|
message: 'Each id may be listed once',
|
|
29
25
|
path: ['ids'],
|
|
30
26
|
});
|
|
31
|
-
const RevokeCredentialsOutputSchema =
|
|
32
|
-
revoked:
|
|
33
|
-
skipped:
|
|
27
|
+
const RevokeCredentialsOutputSchema = z.object({
|
|
28
|
+
revoked: z.array(z.string()),
|
|
29
|
+
skipped: z.array(z.object({ id: z.string(), reason: RevokeSkipReasonSchema })),
|
|
34
30
|
});
|
|
35
31
|
/** Keep the audit row bounded; the ids beyond this are counted, not listed. */
|
|
36
32
|
const AUDIT_IDS_MAX_CHARS = 4000;
|
|
@@ -39,16 +35,16 @@ function assertSignedInAdminSession(user) {
|
|
|
39
35
|
user.shareView !== undefined ||
|
|
40
36
|
user.credential !== undefined ||
|
|
41
37
|
user.adminTokenId !== undefined) {
|
|
42
|
-
throw new
|
|
38
|
+
throw new TRPCError({
|
|
43
39
|
code: 'FORBIDDEN',
|
|
44
40
|
message: 'Revoking credentials in bulk needs a signed-in admin session',
|
|
45
41
|
});
|
|
46
42
|
}
|
|
47
43
|
}
|
|
48
44
|
function requireInternal(registry) {
|
|
49
|
-
const internal = registry === null ? null :
|
|
45
|
+
const internal = registry === null ? null : userManagementInternal(registry);
|
|
50
46
|
if (internal === null) {
|
|
51
|
-
throw new
|
|
47
|
+
throw new TRPCError({
|
|
52
48
|
code: 'PRECONDITION_FAILED',
|
|
53
49
|
message: 'User management is not available',
|
|
54
50
|
});
|
|
@@ -60,7 +56,7 @@ function revokerFor(kind, caller, deps) {
|
|
|
60
56
|
if (kind === 'share-link') {
|
|
61
57
|
const service = deps.shareTokens;
|
|
62
58
|
if (service === null) {
|
|
63
|
-
throw new
|
|
59
|
+
throw new TRPCError({ code: 'PRECONDITION_FAILED', message: 'Share links are not available' });
|
|
64
60
|
}
|
|
65
61
|
return async (id) => (await service.revoke({ id, callerUserId: caller.id, callerIsAdmin: true }))
|
|
66
62
|
? null
|
|
@@ -88,16 +84,16 @@ function auditIds(ids) {
|
|
|
88
84
|
const joined = ids.join(',');
|
|
89
85
|
return joined.length <= AUDIT_IDS_MAX_CHARS ? joined : `${joined.slice(0, AUDIT_IDS_MAX_CHARS)}…`;
|
|
90
86
|
}
|
|
91
|
-
function createRevokeCredentialsProcedures(deps) {
|
|
87
|
+
export function createRevokeCredentialsProcedures(deps) {
|
|
92
88
|
return {
|
|
93
89
|
/** ADMIN — revoke exactly `ids` of one kind; see the module header. */
|
|
94
|
-
revokeCredentials:
|
|
90
|
+
revokeCredentials: adminProcedure
|
|
95
91
|
.input(RevokeCredentialsInputSchema)
|
|
96
92
|
.output(RevokeCredentialsOutputSchema)
|
|
97
93
|
.mutation(async ({ ctx, input }) => {
|
|
98
94
|
assertSignedInAdminSession(ctx.user);
|
|
99
95
|
if (input.confirmCount !== input.ids.length) {
|
|
100
|
-
throw new
|
|
96
|
+
throw new TRPCError({
|
|
101
97
|
code: 'BAD_REQUEST',
|
|
102
98
|
message: `Confirmed ${input.confirmCount} but ${input.ids.length} were listed — nothing was revoked`,
|
|
103
99
|
});
|
|
@@ -114,7 +110,7 @@ function createRevokeCredentialsProcedures(deps) {
|
|
|
114
110
|
else
|
|
115
111
|
skipped.push({ id, reason });
|
|
116
112
|
}
|
|
117
|
-
const internal = deps.registry === null ? null :
|
|
113
|
+
const internal = deps.registry === null ? null : userManagementInternal(deps.registry);
|
|
118
114
|
if (internal !== null) {
|
|
119
115
|
const ip = deps.network.clientAddress(ctx.req).ip;
|
|
120
116
|
await internal.audit.record({
|