@camstack/server 1.2.580 → 1.2.582
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/api/core/auth-device-cert.procedures.js +3 -5
- package/dist/api/core/auth-reauth.procedures.js +4 -3
- package/dist/api/core/auth-sessions.procedures.js +2 -1
- package/dist/api/core/auth.router.js +23 -30
- package/dist/api/core/live-events.router.js +5 -4
- package/dist/api/trpc/cap-route-error-formatter.js +24 -0
- package/dist/api/trpc/generated-cap-mounts.js +17 -1
- package/dist/api/trpc/generated-cap-routers.js +1689 -1640
- package/dist/api/trpc/localized-schema-paths.js +26 -0
- package/dist/api/trpc/trpc.context.js +3 -0
- package/dist/api/trpc/trpc.middleware.js +21 -3
- package/dist/api/trpc/user-error.js +17 -0
- package/dist/auth/app-confirmations.js +2 -1
- package/dist/auth/device-enroll-proofs.js +2 -1
- package/dist/auth/sso-finish-deps.js +4 -3
- package/dist/core/addon/addon-package.service.js +36 -35
- package/dist/core/addon/addon-registry.service.js +20 -0
- package/dist/core/auth/handoff-code.service.js +2 -1
- package/dist/core/auth/share-token.service.js +7 -7
- package/dist/i18n/request-lang.js +27 -0
- package/package.json +12 -12
|
@@ -10,6 +10,7 @@ const zod_1 = require("zod");
|
|
|
10
10
|
const session_mint_js_1 = require("../../auth/session-mint.js");
|
|
11
11
|
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
12
12
|
const auth_reauth_procedures_js_1 = require("./auth-reauth.procedures.js");
|
|
13
|
+
const user_error_js_1 = require("../trpc/user-error.js");
|
|
13
14
|
const DeviceLoginResultSchema = zod_1.z.discriminatedUnion('ok', [
|
|
14
15
|
zod_1.z.object({
|
|
15
16
|
ok: zod_1.z.literal(true),
|
|
@@ -75,10 +76,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
75
76
|
? null
|
|
76
77
|
: (0, system_1.getUserManagementInternal)(deps.registry.getSingleton('user-management'));
|
|
77
78
|
if (found === null) {
|
|
78
|
-
throw
|
|
79
|
-
code: 'PRECONDITION_FAILED',
|
|
80
|
-
message: 'Device certificates are not available',
|
|
81
|
-
});
|
|
79
|
+
throw (0, user_error_js_1.trpcUserError)('PRECONDITION_FAILED', 'device.certificates-unavailable', undefined, 'Device certificates are not available');
|
|
82
80
|
}
|
|
83
81
|
return found;
|
|
84
82
|
};
|
|
@@ -113,7 +111,7 @@ function createDeviceCertProcedures(deps) {
|
|
|
113
111
|
};
|
|
114
112
|
const assertPerson = (user) => {
|
|
115
113
|
if (user.isScoped === true || user.shareView !== undefined || user.adminTokenId !== undefined) {
|
|
116
|
-
throw
|
|
114
|
+
throw (0, user_error_js_1.trpcUserError)('FORBIDDEN', 'auth.session-required', undefined, 'Requires a signed-in user session');
|
|
117
115
|
}
|
|
118
116
|
};
|
|
119
117
|
const selfLimit = (userId, path) => {
|
|
@@ -8,6 +8,7 @@ const zod_1 = require("zod");
|
|
|
8
8
|
const passkey_provider_js_1 = require("../../auth/passkey-provider.js");
|
|
9
9
|
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
10
10
|
const auth_limits_js_1 = require("./auth-limits.js");
|
|
11
|
+
const user_error_js_1 = require("../trpc/user-error.js");
|
|
11
12
|
/** The input field a re-auth-gated procedure takes. */
|
|
12
13
|
exports.ReauthTokenInput = zod_1.z.string().min(1).max(4096);
|
|
13
14
|
exports.REAUTH_REQUIRED_MESSAGE = 'Confirm it is you first: re-enter your password or use a passkey (update the app if it cannot ask).';
|
|
@@ -31,7 +32,7 @@ function isPasswordChecker(value) {
|
|
|
31
32
|
/** Real user sessions only: a scoped or share token cannot prove a password. */
|
|
32
33
|
function assertPerson(user) {
|
|
33
34
|
if (user.isScoped === true || user.shareView !== undefined || user.adminTokenId !== undefined) {
|
|
34
|
-
throw
|
|
35
|
+
throw (0, user_error_js_1.trpcUserError)('FORBIDDEN', 'auth.session-required', undefined, 'Requires a signed-in user session');
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
function createReauthProcedures(deps) {
|
|
@@ -79,7 +80,7 @@ function createReauthProcedures(deps) {
|
|
|
79
80
|
});
|
|
80
81
|
if (record === null || record.id !== ctx.user.id) {
|
|
81
82
|
(0, auth_limits_js_1.recordAccountFailure)(deps.limiters, ctx.user.username);
|
|
82
|
-
throw
|
|
83
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.password-incorrect', undefined, 'Password is incorrect');
|
|
83
84
|
}
|
|
84
85
|
method = 'password';
|
|
85
86
|
}
|
|
@@ -93,7 +94,7 @@ function createReauthProcedures(deps) {
|
|
|
93
94
|
});
|
|
94
95
|
if (!result.verified) {
|
|
95
96
|
(0, auth_limits_js_1.recordAccountFailure)(deps.limiters, ctx.user.username);
|
|
96
|
-
throw
|
|
97
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.passkey-verification-failed', undefined, 'Passkey verification failed');
|
|
97
98
|
}
|
|
98
99
|
method = 'passkey';
|
|
99
100
|
}
|
|
@@ -8,6 +8,7 @@ const types_1 = require("@camstack/types");
|
|
|
8
8
|
const hub_sessions_js_1 = require("../../auth/hub-sessions.js");
|
|
9
9
|
const session_mint_js_1 = require("../../auth/session-mint.js");
|
|
10
10
|
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
11
|
+
const user_error_js_1 = require("../trpc/user-error.js");
|
|
11
12
|
/** One login session as the UI lists it. */
|
|
12
13
|
exports.LoginSessionSchema = zod_1.z.object({
|
|
13
14
|
id: zod_1.z.string(),
|
|
@@ -51,7 +52,7 @@ function requireSessions(registry) {
|
|
|
51
52
|
/** Real user sessions only — a scoped or share token has no login session to manage. */
|
|
52
53
|
function assertLoginSession(user) {
|
|
53
54
|
if (user.isScoped === true || user.shareView !== undefined || user.adminTokenId !== undefined) {
|
|
54
|
-
throw
|
|
55
|
+
throw (0, user_error_js_1.trpcUserError)('FORBIDDEN', 'auth.session-required', undefined, 'Requires a signed-in user session');
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
function createSessionProcedures(deps) {
|
|
@@ -42,6 +42,8 @@ const auth_reauth_procedures_js_1 = require("./auth-reauth.procedures.js");
|
|
|
42
42
|
const auth_device_cert_procedures_js_1 = require("./auth-device-cert.procedures.js");
|
|
43
43
|
const auth_sessions_procedures_js_1 = require("./auth-sessions.procedures.js");
|
|
44
44
|
const server_name_js_1 = require("./server-name.js");
|
|
45
|
+
const types_2 = require("@camstack/types");
|
|
46
|
+
const user_error_js_1 = require("../trpc/user-error.js");
|
|
45
47
|
/**
|
|
46
48
|
* A public procedure limited per (procedure, REAL client address) — see
|
|
47
49
|
* `auth-limits.ts` for the tiers and why the address is the forwarded client.
|
|
@@ -351,7 +353,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
351
353
|
}
|
|
352
354
|
catch (error) {
|
|
353
355
|
if (error instanceof session_mint_js_1.SessionUserGoneError) {
|
|
354
|
-
throw
|
|
356
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.user-gone', undefined, 'User no longer exists');
|
|
355
357
|
}
|
|
356
358
|
throw error;
|
|
357
359
|
}
|
|
@@ -582,7 +584,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
582
584
|
});
|
|
583
585
|
if (!user) {
|
|
584
586
|
(0, auth_limits_js_1.recordAccountFailure)(limiters, input.username);
|
|
585
|
-
throw
|
|
587
|
+
throw (0, types_2.userError)('auth.invalid-credentials', undefined, 'Invalid credentials');
|
|
586
588
|
}
|
|
587
589
|
// ── Second-factor gate ───────────────────────────────────────
|
|
588
590
|
// After credentials validate, collect every ACTIVE second
|
|
@@ -644,7 +646,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
644
646
|
: { valid: false };
|
|
645
647
|
if (!verify.valid) {
|
|
646
648
|
(0, auth_limits_js_1.recordAccountFailure)(limiters, claims.username);
|
|
647
|
-
throw
|
|
649
|
+
throw (0, types_2.userError)('auth.invalid-totp', undefined, 'Invalid TOTP code');
|
|
648
650
|
}
|
|
649
651
|
return mintSessionForUserId(claims.userId, issueFor(ctx.req, claims.method ?? 'password', input.refreshable));
|
|
650
652
|
}),
|
|
@@ -661,7 +663,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
661
663
|
.mutation(async ({ input }) => {
|
|
662
664
|
const claims = auth.verifyTotpChallengeToken(input.challengeToken);
|
|
663
665
|
if (!claims) {
|
|
664
|
-
throw
|
|
666
|
+
throw (0, types_2.userError)('auth.login-challenge-expired', undefined, 'Invalid or expired login challenge — please re-enter your password');
|
|
665
667
|
}
|
|
666
668
|
const provider = (0, passkey_provider_js_1.resolvePasskeyProvider)(registry, moleculer);
|
|
667
669
|
if (!provider)
|
|
@@ -685,7 +687,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
685
687
|
.mutation(async ({ input, ctx }) => {
|
|
686
688
|
const claims = auth.verifyTotpChallengeToken(input.challengeToken);
|
|
687
689
|
if (!claims) {
|
|
688
|
-
throw
|
|
690
|
+
throw (0, types_2.userError)('auth.login-challenge-expired', undefined, 'Invalid or expired login challenge — please re-enter your password');
|
|
689
691
|
}
|
|
690
692
|
const provider = (0, passkey_provider_js_1.resolvePasskeyProvider)(registry, moleculer);
|
|
691
693
|
if (!provider)
|
|
@@ -697,7 +699,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
697
699
|
});
|
|
698
700
|
if (!result.verified) {
|
|
699
701
|
(0, auth_limits_js_1.recordAccountFailure)(limiters, claims.username);
|
|
700
|
-
throw
|
|
702
|
+
throw (0, types_2.userError)('auth.passkey-verification-failed', undefined, 'Passkey verification failed');
|
|
701
703
|
}
|
|
702
704
|
return mintSessionForUserId(claims.userId, issueFor(ctx.req, claims.method ?? 'password', input.refreshable));
|
|
703
705
|
}),
|
|
@@ -747,7 +749,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
747
749
|
response: input.response,
|
|
748
750
|
});
|
|
749
751
|
if (!result.verified || !result.userId) {
|
|
750
|
-
throw
|
|
752
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.passkey-verification-failed', undefined, 'Passkey verification failed');
|
|
751
753
|
}
|
|
752
754
|
const secondFactors = await factorsOwedHere(result.userId, 'passkey', ctx.req);
|
|
753
755
|
if (secondFactors.length > 0) {
|
|
@@ -803,14 +805,14 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
803
805
|
}
|
|
804
806
|
const cookieToken = (0, session_cookie_js_1.readSessionCookieFromHeader)(readRequestHeader(ctx.req, 'cookie') ?? undefined);
|
|
805
807
|
if (!cookieToken) {
|
|
806
|
-
throw
|
|
808
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.no-session-cookie', undefined, 'No session cookie');
|
|
807
809
|
}
|
|
808
810
|
let payload;
|
|
809
811
|
try {
|
|
810
812
|
payload = auth.verifyToken(cookieToken);
|
|
811
813
|
}
|
|
812
814
|
catch {
|
|
813
|
-
throw
|
|
815
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.invalid-session-cookie', undefined, 'Invalid session cookie');
|
|
814
816
|
}
|
|
815
817
|
// Reject non-session JWTs that verify under the same secret:
|
|
816
818
|
// challenge/bridge tokens carry a `kind` discriminator, and a
|
|
@@ -819,7 +821,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
819
821
|
if (kind !== undefined ||
|
|
820
822
|
typeof payload.isAdmin !== 'boolean' ||
|
|
821
823
|
typeof payload.userId !== 'string') {
|
|
822
|
-
throw
|
|
824
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.invalid-session-cookie', undefined, 'Invalid session cookie');
|
|
823
825
|
}
|
|
824
826
|
// A session of its own for the client that asked (the hub-served
|
|
825
827
|
// viewer): signing out of one never signs the other out.
|
|
@@ -912,10 +914,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
912
914
|
});
|
|
913
915
|
}
|
|
914
916
|
if (!grant || grant.purpose === 'step-up') {
|
|
915
|
-
throw
|
|
916
|
-
code: 'UNAUTHORIZED',
|
|
917
|
-
message: 'Invalid or expired handoff code',
|
|
918
|
-
});
|
|
917
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.handoff-code-invalid', undefined, 'Invalid or expired handoff code');
|
|
919
918
|
}
|
|
920
919
|
// A `renew` code continues THE SESSION IT WAS MINTED FROM. One without a session would
|
|
921
920
|
// open a brand-new session with no PKCE behind it: refused.
|
|
@@ -923,10 +922,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
923
922
|
options.logger?.warn('Handoff redeem refused: a renew code with no session behind it', {
|
|
924
923
|
tags: { userId: grant.userId },
|
|
925
924
|
});
|
|
926
|
-
throw
|
|
927
|
-
code: 'UNAUTHORIZED',
|
|
928
|
-
message: 'Invalid or expired handoff code',
|
|
929
|
-
});
|
|
925
|
+
throw (0, user_error_js_1.trpcUserError)('UNAUTHORIZED', 'auth.handoff-code-invalid', undefined, 'Invalid or expired handoff code');
|
|
930
926
|
}
|
|
931
927
|
if (grant.purpose === 'renew' && grant.sessionId !== undefined) {
|
|
932
928
|
// A legacy client renewing: the SAME session, its expiry slides. A
|
|
@@ -1015,7 +1011,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1015
1011
|
if (!userMgmt)
|
|
1016
1012
|
throw new Error('user-management capability not available');
|
|
1017
1013
|
if (!ctx.user)
|
|
1018
|
-
throw
|
|
1014
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1019
1015
|
// Re-validate the current password against the live store to
|
|
1020
1016
|
// confirm session identity → password ownership match. Stops a
|
|
1021
1017
|
// stolen session-token from rotating credentials silently.
|
|
@@ -1045,7 +1041,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1045
1041
|
if (!userMgmt)
|
|
1046
1042
|
throw new Error('user-management capability not available');
|
|
1047
1043
|
if (!ctx.user)
|
|
1048
|
-
throw
|
|
1044
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1049
1045
|
return userMgmt.setupTotp({ userId: ctx.user.id });
|
|
1050
1046
|
}),
|
|
1051
1047
|
confirmOwnTotp: trpc_middleware_js_1.protectedProcedure
|
|
@@ -1056,7 +1052,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1056
1052
|
if (!userMgmt)
|
|
1057
1053
|
throw new Error('user-management capability not available');
|
|
1058
1054
|
if (!ctx.user)
|
|
1059
|
-
throw
|
|
1055
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1060
1056
|
return userMgmt.confirmTotp({ userId: ctx.user.id, code: input.code });
|
|
1061
1057
|
}),
|
|
1062
1058
|
disableOwnTotp: trpc_middleware_js_1.protectedProcedure
|
|
@@ -1069,7 +1065,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1069
1065
|
if (!userMgmt)
|
|
1070
1066
|
throw new Error('user-management capability not available');
|
|
1071
1067
|
if (!ctx.user)
|
|
1072
|
-
throw
|
|
1068
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1073
1069
|
return userMgmt.disableTotp({ userId: ctx.user.id });
|
|
1074
1070
|
}),
|
|
1075
1071
|
getOwnTotpStatus: trpc_middleware_js_1.protectedProcedure
|
|
@@ -1094,7 +1090,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1094
1090
|
.output(passkey_provider_js_1.OptionsJsonSchema)
|
|
1095
1091
|
.mutation(async ({ ctx }) => {
|
|
1096
1092
|
if (!ctx.user)
|
|
1097
|
-
throw
|
|
1093
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1098
1094
|
const provider = (0, passkey_provider_js_1.resolvePasskeyProvider)(registry, moleculer);
|
|
1099
1095
|
if (!provider)
|
|
1100
1096
|
throw new Error('Passkey enrollment is not available');
|
|
@@ -1105,7 +1101,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1105
1101
|
.output(passkey_provider_js_1.FinishRegistrationSchema)
|
|
1106
1102
|
.mutation(async ({ input, ctx }) => {
|
|
1107
1103
|
if (!ctx.user)
|
|
1108
|
-
throw
|
|
1104
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1109
1105
|
const provider = (0, passkey_provider_js_1.resolvePasskeyProvider)(registry, moleculer);
|
|
1110
1106
|
if (!provider)
|
|
1111
1107
|
throw new Error('Passkey enrollment is not available');
|
|
@@ -1137,7 +1133,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1137
1133
|
.output(zod_1.z.object({ success: zod_1.z.literal(true) }))
|
|
1138
1134
|
.mutation(async ({ input, ctx }) => {
|
|
1139
1135
|
if (!ctx.user)
|
|
1140
|
-
throw
|
|
1136
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1141
1137
|
(0, auth_reauth_procedures_js_1.assertReauthenticated)(auth, ctx.user, input.reauthToken);
|
|
1142
1138
|
const provider = (0, passkey_provider_js_1.resolvePasskeyProvider)(registry, moleculer);
|
|
1143
1139
|
if (!provider)
|
|
@@ -1173,7 +1169,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1173
1169
|
.output(passkey_provider_js_1.SetSecondFactorPreferenceSchema)
|
|
1174
1170
|
.mutation(async ({ input, ctx }) => {
|
|
1175
1171
|
if (!ctx.user)
|
|
1176
|
-
throw
|
|
1172
|
+
throw (0, types_2.userError)('auth.not-authenticated', undefined, 'Not authenticated');
|
|
1177
1173
|
const provider = (0, passkey_provider_js_1.resolvePasskeyProvider)(registry, moleculer);
|
|
1178
1174
|
if (!provider)
|
|
1179
1175
|
throw new Error('Passkey management is not available');
|
|
@@ -1243,10 +1239,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1243
1239
|
callerIsAdmin: ctx.user.isAdmin,
|
|
1244
1240
|
});
|
|
1245
1241
|
if (record.expiresAt === null) {
|
|
1246
|
-
throw
|
|
1247
|
-
code: 'BAD_REQUEST',
|
|
1248
|
-
message: 'A never-expiring share token cannot be extended',
|
|
1249
|
-
});
|
|
1242
|
+
throw (0, user_error_js_1.trpcUserError)('BAD_REQUEST', 'auth.share-never-expiring-extend', undefined, 'A never-expiring share token cannot be extended');
|
|
1250
1243
|
}
|
|
1251
1244
|
return { id: record.id, expiresAt: record.expiresAt };
|
|
1252
1245
|
}
|
|
@@ -12,6 +12,7 @@ const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
|
12
12
|
const share_view_access_js_1 = require("../trpc/share-view-access.js");
|
|
13
13
|
const principal_visibility_js_1 = require("../trpc/principal-visibility.js");
|
|
14
14
|
const public_event_filter_js_1 = require("./public-event-filter.js");
|
|
15
|
+
const types_1 = require("@camstack/types");
|
|
15
16
|
// The pushed wire shape is `SystemEvent` from @camstack/types — the exact
|
|
16
17
|
// type `EventBusService.subscribe` hands its handlers. It MUST be a type
|
|
17
18
|
// exported from @camstack/types (not a local interface): the AppRouter
|
|
@@ -82,22 +83,22 @@ function createLiveEventsRouter(eb, ar) {
|
|
|
82
83
|
const deviceRegistry = ar.getDeviceRegistry();
|
|
83
84
|
const device = deviceRegistry.getById(input.deviceId);
|
|
84
85
|
if (!device)
|
|
85
|
-
throw
|
|
86
|
+
throw (0, types_1.userError)('device.not-found', { id: input.deviceId }, `Device id=${input.deviceId} not found`);
|
|
86
87
|
// Permission check — new IDevice has no providerId; admin bypass only
|
|
87
88
|
if (!ctx.user.isAdmin) {
|
|
88
89
|
const ad = ctx.user.permissions?.allowedDevices;
|
|
89
90
|
if (!ad)
|
|
90
|
-
throw
|
|
91
|
+
throw (0, types_1.userError)('auth.access-denied', undefined, 'Access denied');
|
|
91
92
|
// Find which addon owns this device to determine the provider key
|
|
92
93
|
const ownerAddonId = deviceRegistry.getAddonId(input.deviceId);
|
|
93
94
|
if (!ownerAddonId)
|
|
94
|
-
throw
|
|
95
|
+
throw (0, types_1.userError)('auth.access-denied', undefined, 'Access denied');
|
|
95
96
|
const pd = ad[ownerAddonId];
|
|
96
97
|
// Permission lists are still keyed by stableId — that's the
|
|
97
98
|
// external admin-facing identifier. Resolve the device's
|
|
98
99
|
// stableId for the membership check.
|
|
99
100
|
if (!pd || (pd !== '*' && !pd.includes(device.stableId))) {
|
|
100
|
-
throw
|
|
101
|
+
throw (0, types_1.userError)('auth.access-denied', undefined, 'Access denied');
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
return eb.subscribe({ source: { type: 'device', id: input.deviceId } }, (evt) => {
|
|
@@ -17,6 +17,7 @@ exports.formatTrpcError = formatTrpcError;
|
|
|
17
17
|
* The formatter is EXPORTED for unit testing (no side-effects, pure function).
|
|
18
18
|
*/
|
|
19
19
|
const system_1 = require("@camstack/system");
|
|
20
|
+
const types_1 = require("@camstack/types");
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Type guards
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
@@ -110,6 +111,29 @@ function extractCapRouteError(err) {
|
|
|
110
111
|
* Returns the shape unchanged for all other errors.
|
|
111
112
|
*/
|
|
112
113
|
function formatTrpcError(opts) {
|
|
114
|
+
return withErrorI18n(opts, withCapRouteData(opts));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A coded error (`userError(...)`, D863) gets its response `message` in the
|
|
118
|
+
* request language when the `server` catalog has the code; the code and params
|
|
119
|
+
* stay in `data.i18n` whatever the language. A legacy error (no code), an
|
|
120
|
+
* unknown code and English all pass through with the original message.
|
|
121
|
+
*/
|
|
122
|
+
function withErrorI18n(opts, shape) {
|
|
123
|
+
const coded = (0, types_1.readErrorI18n)(opts.error);
|
|
124
|
+
if (coded === null)
|
|
125
|
+
return shape;
|
|
126
|
+
const lang = opts.ctx?.lang ?? types_1.DEFAULT_CATALOG_LANG;
|
|
127
|
+
const text = lang === types_1.DEFAULT_CATALOG_LANG
|
|
128
|
+
? undefined
|
|
129
|
+
: (0, system_1.getCatalogRegistry)().lookup(lang, system_1.SERVER_NAMESPACE, coded.code);
|
|
130
|
+
return {
|
|
131
|
+
...shape,
|
|
132
|
+
...(text !== undefined ? { message: (0, types_1.interpolate)(text, coded.params) } : {}),
|
|
133
|
+
data: { ...shape.data, i18n: coded },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function withCapRouteData(opts) {
|
|
113
137
|
const { error, shape } = opts;
|
|
114
138
|
// extractCapRouteError already walks the full .cause chain, so a single call
|
|
115
139
|
// starting from `error` covers both `error instanceof CapRouteError` and
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// AUTO-GENERATED by scripts/generate-cap-mounts.ts — DO NOT EDIT
|
|
3
3
|
// Re-run: npx tsx scripts/generate-cap-mounts.ts
|
|
4
4
|
//
|
|
5
|
-
// Mounted:
|
|
5
|
+
// Mounted: 171 Skipped (legacy): 3
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.LEGACY_SHAPE_SKIP = void 0;
|
|
8
8
|
exports.mountAllCaps = mountAllCaps;
|
|
@@ -228,6 +228,22 @@ function mountAllCaps(services) {
|
|
|
228
228
|
discoverDevices: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'discoverDevices'),
|
|
229
229
|
};
|
|
230
230
|
}, remoteCapProxy),
|
|
231
|
+
deviceRemovalHook: (0, generated_cap_routers_js_1.createCapRouter_deviceRemovalHook)((_ctx, addonId) => {
|
|
232
|
+
if (!reg)
|
|
233
|
+
return null;
|
|
234
|
+
if (addonId !== undefined) {
|
|
235
|
+
return reg.getProviderByAddonId('device-removal-hook', addonId);
|
|
236
|
+
}
|
|
237
|
+
const entries = reg.getCollectionEntries('device-removal-hook');
|
|
238
|
+
if (entries.length === 0)
|
|
239
|
+
return null;
|
|
240
|
+
const providers = entries.map(([, p]) => p);
|
|
241
|
+
const first = providers[0];
|
|
242
|
+
return {
|
|
243
|
+
...first,
|
|
244
|
+
listDeviceData: (0, cap_mount_helpers_js_1.concatCollection)(providers, 'listDeviceData'),
|
|
245
|
+
};
|
|
246
|
+
}, remoteCapProxy),
|
|
231
247
|
deviceState: (0, generated_cap_routers_js_1.createCapRouter_deviceState)((_ctx) => reg?.getSingleton('device-state') ?? null, remoteCapProxy),
|
|
232
248
|
deviceStatus: (0, generated_cap_routers_js_1.createCapRouter_deviceStatus)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'device-status'), remoteCapProxy),
|
|
233
249
|
doorbell: (0, generated_cap_routers_js_1.createCapRouter_doorbell)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'doorbell'), remoteCapProxy),
|