@camstack/server 1.2.603 → 1.2.605
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/auth-whoami.js +28 -43
- package/dist/api/core/auth-admin-tokens.procedures.js +42 -24
- package/dist/api/core/auth-device-cert.procedures.js +1 -1
- package/dist/api/core/auth-reauth.procedures.js +1 -1
- package/dist/api/core/auth-revoke-credentials.procedures.js +54 -7
- package/dist/api/core/auth-sessions.procedures.js +1 -1
- package/dist/api/core/auth-tokens.procedures.js +250 -0
- package/dist/api/core/auth.router.js +95 -12
- package/dist/api/core/live-events.router.js +1 -1
- package/dist/api/trpc/principal-authorization.js +82 -0
- package/dist/api/trpc/principal-visibility.js +1 -1
- package/dist/api/trpc/trpc-error-principal.js +10 -9
- package/dist/api/trpc/trpc.context.js +82 -256
- package/dist/api/trpc/trpc.middleware.js +42 -83
- package/dist/api/trpc/trpc.router.js +1 -0
- package/dist/api/upload-auth.js +29 -52
- package/dist/auth/addon-route-auth.js +44 -0
- package/dist/auth/addon-route-share-gate.js +18 -16
- package/dist/auth/credential-verifier.js +277 -0
- package/dist/auth/scope-escalation.js +58 -0
- package/dist/core/addon/addon-registry.service.js +17 -0
- package/dist/core/auth/credential-token.service.js +250 -0
- package/dist/core/auth/record-kinds.js +31 -0
- package/dist/main.js +51 -97
- package/package.json +14 -14
- package/dist/auth/addon-route-jwt-gate.js +0 -59
|
@@ -37,6 +37,7 @@ const principal_visibility_js_1 = require("../trpc/principal-visibility.js");
|
|
|
37
37
|
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
38
38
|
const auth_limits_js_1 = require("./auth-limits.js");
|
|
39
39
|
const auth_admin_tokens_procedures_js_1 = require("./auth-admin-tokens.procedures.js");
|
|
40
|
+
const auth_tokens_procedures_js_1 = require("./auth-tokens.procedures.js");
|
|
40
41
|
const auth_revoke_credentials_procedures_js_1 = require("./auth-revoke-credentials.procedures.js");
|
|
41
42
|
const auth_reauth_procedures_js_1 = require("./auth-reauth.procedures.js");
|
|
42
43
|
const auth_device_cert_procedures_js_1 = require("./auth-device-cert.procedures.js");
|
|
@@ -199,6 +200,8 @@ const ShareTokenSummarySchema = zod_1.z.object({
|
|
|
199
200
|
/** Derived convenience flag so listings surface infinite tokens clearly. */
|
|
200
201
|
neverExpires: zod_1.z.boolean(),
|
|
201
202
|
lastUsedAt: zod_1.z.number().optional(),
|
|
203
|
+
/** A pre-D892 `csv_` link: valid until it expires, never minted again. */
|
|
204
|
+
legacy: zod_1.z.boolean().optional(),
|
|
202
205
|
});
|
|
203
206
|
function toShareTokenSummary(record) {
|
|
204
207
|
return {
|
|
@@ -219,9 +222,10 @@ function toShareTokenSummary(record) {
|
|
|
219
222
|
* a leaked restricted token would otherwise widen its own reach.
|
|
220
223
|
*/
|
|
221
224
|
function assertRealUserSession(user, what = 'Share-token management') {
|
|
222
|
-
//
|
|
223
|
-
// must not turn itself into a session
|
|
224
|
-
|
|
225
|
+
// A minted token (admin, scoped, share — legacy or unified) is a script's
|
|
226
|
+
// credential, never a person's: it must not turn itself into a session
|
|
227
|
+
// (handoff codes) or mint links. Provenance, not permission (D892).
|
|
228
|
+
if (user.userDelegated !== true) {
|
|
225
229
|
throw new server_1.TRPCError({
|
|
226
230
|
code: 'FORBIDDEN',
|
|
227
231
|
message: `${what} requires a real user session`,
|
|
@@ -304,6 +308,15 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
304
308
|
const credentialProcedure = makeRateLimitedProcedure(limiters.credential, network);
|
|
305
309
|
/** Challenge-minting begin-legs — looser, they verify nothing. */
|
|
306
310
|
const ceremonyProcedure = makeRateLimitedProcedure(limiters.ceremony, network);
|
|
311
|
+
const credentialTokens = options.credentialTokens ?? null;
|
|
312
|
+
const tokenDeps = { auth, registry, network, credentialTokens, shareTokens };
|
|
313
|
+
/** The unified share link behind `id`, or `null` (a legacy `csv_`, or none). */
|
|
314
|
+
const unifiedShare = async (id) => {
|
|
315
|
+
if (credentialTokens === null)
|
|
316
|
+
return null;
|
|
317
|
+
const record = await credentialTokens.get(id);
|
|
318
|
+
return record !== null && (0, types_2.shareViewGrantOf)(record.scopes) !== null ? record : null;
|
|
319
|
+
};
|
|
307
320
|
const requireShareTokens = () => {
|
|
308
321
|
if (!shareTokens) {
|
|
309
322
|
throw new Error('Share tokens unavailable — service not wired on this node');
|
|
@@ -957,7 +970,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
957
970
|
// hide surfaces from a single probe (F1 #5). Admins summarise to
|
|
958
971
|
// "everything"; a scoped principal's selectors expand to counts.
|
|
959
972
|
const fleet = ctx.deviceFleet?.() ?? [];
|
|
960
|
-
const scope = (0, types_1.summarizeEffectiveScope)(ctx.user.scopes ?? [], fleet, ctx.user.isAdmin);
|
|
973
|
+
const scope = (0, types_1.summarizeEffectiveScope)((0, types_2.capabilityScopesOf)(ctx.user.scopes ?? []), fleet, ctx.user.isAdmin);
|
|
961
974
|
return { ...ctx.user, scope };
|
|
962
975
|
}),
|
|
963
976
|
/**
|
|
@@ -1188,7 +1201,6 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1188
1201
|
.output(zod_1.z.object({ id: zod_1.z.string(), token: zod_1.z.string(), expiresAt: zod_1.z.number().nullable() }))
|
|
1189
1202
|
.mutation(async ({ input, ctx }) => {
|
|
1190
1203
|
assertRealUserSession(ctx.user);
|
|
1191
|
-
const service = requireShareTokens();
|
|
1192
1204
|
// SECURITY (F0.4): a share token is a PRIVILEGE HANDOFF — it authorises
|
|
1193
1205
|
// whoever holds the link to view its devices with no further auth. A
|
|
1194
1206
|
// scoped (non-admin) minter must therefore not be able to mint for a
|
|
@@ -1202,7 +1214,7 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1202
1214
|
? input.scope
|
|
1203
1215
|
: {
|
|
1204
1216
|
...input.scope,
|
|
1205
|
-
deviceIds: (0, principal_visibility_js_1.filterViewableDeviceIds)(ctx.user.scopes ?? [], input.scope.deviceIds, ctx.deviceScopeLookup),
|
|
1217
|
+
deviceIds: (0, principal_visibility_js_1.filterViewableDeviceIds)((0, types_2.capabilityScopesOf)(ctx.user.scopes ?? []), input.scope.deviceIds, ctx.deviceScopeLookup),
|
|
1206
1218
|
};
|
|
1207
1219
|
if (scope.deviceIds.length === 0) {
|
|
1208
1220
|
throw new server_1.TRPCError({
|
|
@@ -1210,7 +1222,20 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1210
1222
|
message: 'None of the requested devices are within your scope — you cannot mint a share token for devices you cannot see',
|
|
1211
1223
|
});
|
|
1212
1224
|
}
|
|
1213
|
-
|
|
1225
|
+
// A thin wrapper for one release (D892): the link is a unified token
|
|
1226
|
+
// whose only scope is the share-view grant. Without the store (a
|
|
1227
|
+
// node that cannot mint) it is still a legacy `csv_`.
|
|
1228
|
+
if (credentialTokens !== null) {
|
|
1229
|
+
const ttl = input.ttlSec ?? share_token_service_js_1.SHARE_TOKEN_TTL_DEFAULT_SEC;
|
|
1230
|
+
const minted = await (0, auth_tokens_procedures_js_1.mintFor)(tokenDeps, ctx, {
|
|
1231
|
+
scopes: [{ type: 'share-view', grant: scope }],
|
|
1232
|
+
expiresIn: ttl,
|
|
1233
|
+
label: `share ${scope.kind}`,
|
|
1234
|
+
allowAccountLink: true,
|
|
1235
|
+
});
|
|
1236
|
+
return { id: minted.record.id, token: minted.token, expiresAt: minted.record.expiresAt };
|
|
1237
|
+
}
|
|
1238
|
+
const { token, record } = await requireShareTokens().create({
|
|
1214
1239
|
userId: ctx.user.id,
|
|
1215
1240
|
scope,
|
|
1216
1241
|
...(input.ttlSec !== undefined ? { ttlSec: input.ttlSec } : {}),
|
|
@@ -1230,6 +1255,23 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1230
1255
|
.output(zod_1.z.object({ id: zod_1.z.string(), expiresAt: zod_1.z.number() }))
|
|
1231
1256
|
.mutation(async ({ input, ctx }) => {
|
|
1232
1257
|
assertRealUserSession(ctx.user);
|
|
1258
|
+
const unified = await unifiedShare(input.id);
|
|
1259
|
+
if (unified !== null && credentialTokens !== null) {
|
|
1260
|
+
if (!ctx.user.isAdmin && unified.subjectId !== ctx.user.id) {
|
|
1261
|
+
throw new server_1.TRPCError({
|
|
1262
|
+
code: 'FORBIDDEN',
|
|
1263
|
+
message: 'Only the token owner or an admin can extend a share token',
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
if (unified.expiresAt === null) {
|
|
1267
|
+
throw (0, user_error_js_1.trpcUserError)('BAD_REQUEST', 'auth.share-never-expiring-extend', undefined, 'A never-expiring share token cannot be extended');
|
|
1268
|
+
}
|
|
1269
|
+
const moved = await credentialTokens.setExpiry(input.id, Date.now() + input.ttlSec * 1000);
|
|
1270
|
+
if (moved === null || moved.expiresAt === null) {
|
|
1271
|
+
throw new server_1.TRPCError({ code: 'NOT_FOUND', message: `Share token expired: ${input.id}` });
|
|
1272
|
+
}
|
|
1273
|
+
return { id: moved.id, expiresAt: moved.expiresAt };
|
|
1274
|
+
}
|
|
1233
1275
|
const service = requireShareTokens();
|
|
1234
1276
|
try {
|
|
1235
1277
|
const record = await service.extend({
|
|
@@ -1259,6 +1301,16 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1259
1301
|
.output(zod_1.z.object({ success: zod_1.z.boolean() }))
|
|
1260
1302
|
.mutation(async ({ input, ctx }) => {
|
|
1261
1303
|
assertRealUserSession(ctx.user);
|
|
1304
|
+
const unified = await unifiedShare(input.id);
|
|
1305
|
+
if (unified !== null && credentialTokens !== null) {
|
|
1306
|
+
if (!ctx.user.isAdmin && unified.subjectId !== ctx.user.id) {
|
|
1307
|
+
throw new server_1.TRPCError({
|
|
1308
|
+
code: 'FORBIDDEN',
|
|
1309
|
+
message: 'Only the token owner or an admin can revoke a share token',
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
return { success: await credentialTokens.revoke(input.id) };
|
|
1313
|
+
}
|
|
1262
1314
|
const service = requireShareTokens();
|
|
1263
1315
|
try {
|
|
1264
1316
|
const removed = await service.revoke({
|
|
@@ -1303,7 +1355,9 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1303
1355
|
.output(zod_1.z.array(ShareTokenSummarySchema))
|
|
1304
1356
|
.query(async ({ ctx, input }) => {
|
|
1305
1357
|
assertRealUserSession(ctx.user);
|
|
1306
|
-
|
|
1358
|
+
// The legacy `csv_` store, or — on a node with only the unified store —
|
|
1359
|
+
// none; with neither, the clear refusal below.
|
|
1360
|
+
const service = credentialTokens === null ? requireShareTokens() : shareTokens;
|
|
1307
1361
|
const owner = input?.owner ?? (ctx.user.isAdmin ? 'all' : 'self');
|
|
1308
1362
|
if (owner === 'all' && !ctx.user.isAdmin) {
|
|
1309
1363
|
throw new server_1.TRPCError({
|
|
@@ -1311,8 +1365,36 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1311
1365
|
message: "Listing every user's share links requires an admin",
|
|
1312
1366
|
});
|
|
1313
1367
|
}
|
|
1314
|
-
const records =
|
|
1315
|
-
|
|
1368
|
+
const records = service === null
|
|
1369
|
+
? []
|
|
1370
|
+
: owner === 'all'
|
|
1371
|
+
? await service.listAll()
|
|
1372
|
+
: await service.listForUser(ctx.user.id);
|
|
1373
|
+
const unified = credentialTokens === null
|
|
1374
|
+
? []
|
|
1375
|
+
: await credentialTokens.list(owner === 'all' ? undefined : ctx.user.id);
|
|
1376
|
+
const unifiedRows = unified.flatMap((record) => {
|
|
1377
|
+
const grant = (0, types_2.shareViewGrantOf)(record.scopes);
|
|
1378
|
+
if (grant === null || record.revokedAt !== null)
|
|
1379
|
+
return [];
|
|
1380
|
+
return [
|
|
1381
|
+
{
|
|
1382
|
+
id: record.id,
|
|
1383
|
+
userId: record.subjectId,
|
|
1384
|
+
tokenPrefix: `token:${record.id.slice(0, 8)}`,
|
|
1385
|
+
scope: grant,
|
|
1386
|
+
createdAt: record.createdAt,
|
|
1387
|
+
expiresAt: record.expiresAt,
|
|
1388
|
+
neverExpires: record.expiresAt === null,
|
|
1389
|
+
...(record.lastUsedAt !== null ? { lastUsedAt: record.lastUsedAt } : {}),
|
|
1390
|
+
legacy: false,
|
|
1391
|
+
},
|
|
1392
|
+
];
|
|
1393
|
+
});
|
|
1394
|
+
return [
|
|
1395
|
+
...unifiedRows,
|
|
1396
|
+
...records.map((record) => ({ ...toShareTokenSummary(record), legacy: true })),
|
|
1397
|
+
];
|
|
1316
1398
|
}),
|
|
1317
1399
|
...(0, auth_sessions_procedures_js_1.createSessionProcedures)({ registry, minter, credentialProcedure }),
|
|
1318
1400
|
...(0, auth_device_cert_procedures_js_1.createDeviceCertProcedures)({
|
|
@@ -1331,8 +1413,9 @@ function createAuthRouter(auth, registry, moleculer = null, shareTokens = null,
|
|
|
1331
1413
|
issueFor,
|
|
1332
1414
|
}),
|
|
1333
1415
|
...(0, auth_reauth_procedures_js_1.createReauthProcedures)({ auth, registry, moleculer, limiters }),
|
|
1334
|
-
...(0, auth_admin_tokens_procedures_js_1.createAdminTokenProcedures)({ auth, registry, network }),
|
|
1335
|
-
...(0,
|
|
1416
|
+
...(0, auth_admin_tokens_procedures_js_1.createAdminTokenProcedures)({ auth, registry, network, credentialTokens, shareTokens }),
|
|
1417
|
+
...(0, auth_tokens_procedures_js_1.createTokenProcedures)({ auth, registry, network, credentialTokens, shareTokens }),
|
|
1418
|
+
...(0, auth_revoke_credentials_procedures_js_1.createRevokeCredentialsProcedures)({ registry, shareTokens, network, credentialTokens }),
|
|
1336
1419
|
// ── External sign-in identities (D782) ────────────────────────
|
|
1337
1420
|
//
|
|
1338
1421
|
// A signed-in user links an OIDC identity to THEIR OWN account. The
|
|
@@ -45,7 +45,7 @@ function createLiveEventsRouter(eb, ar) {
|
|
|
45
45
|
// Share-view principals get a device-filtered stream: only events
|
|
46
46
|
// attributable to an in-scope device are pushed (fail closed —
|
|
47
47
|
// unattributable events are dropped).
|
|
48
|
-
const shareScope = ctx.user.
|
|
48
|
+
const shareScope = (0, types_1.shareViewGrantOf)(ctx.user.scopes ?? []);
|
|
49
49
|
// Non-share scoped tokens get the SAME projection keyed to what they may
|
|
50
50
|
// view (F0.5); admins and broad category-view operators are unfiltered.
|
|
51
51
|
const scopedKeep = shareScope
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authorizeProcedure = authorizeProcedure;
|
|
4
|
+
exports.authorizeAddonRoute = authorizeAddonRoute;
|
|
5
|
+
/**
|
|
6
|
+
* The ONE scope matcher (D892): every authorization decision about a verified
|
|
7
|
+
* principal is a function of `principal.scopes`, here. It answers for a tRPC
|
|
8
|
+
* procedure and for an addon's own HTTP surface (routes and data planes);
|
|
9
|
+
* neither asks what kind of credential the principal came from.
|
|
10
|
+
*
|
|
11
|
+
* Order, the same on both surfaces:
|
|
12
|
+
* 1. a `share-view` grant RESTRICTS the principal to its fail-closed
|
|
13
|
+
* perimeter — nothing else it holds is honoured;
|
|
14
|
+
* 2. `'*'` passes;
|
|
15
|
+
* 3. the capability / addon grants decide.
|
|
16
|
+
*/
|
|
17
|
+
const system_1 = require("@camstack/system");
|
|
18
|
+
const types_1 = require("@camstack/types");
|
|
19
|
+
const addon_route_share_gate_js_1 = require("../../auth/addon-route-share-gate.js");
|
|
20
|
+
const scope_access_js_1 = require("./scope-access.js");
|
|
21
|
+
const share_view_access_js_1 = require("./share-view-access.js");
|
|
22
|
+
/**
|
|
23
|
+
* May a principal holding `scopes` call the tRPC procedure at `path`?
|
|
24
|
+
*
|
|
25
|
+
* A hand-written core route (`auth.me`, `system.info`) has no cap entry: the
|
|
26
|
+
* authentication already passed, and an explicit `adminProcedure` on top of it
|
|
27
|
+
* gates what is destructive. A share-view principal never reaches that
|
|
28
|
+
* pass-through — its allowlist is enumerated.
|
|
29
|
+
*/
|
|
30
|
+
function authorizeProcedure(scopes, path, rawInput, lookup) {
|
|
31
|
+
const grant = (0, types_1.shareViewGrantOf)(scopes);
|
|
32
|
+
if (grant !== null) {
|
|
33
|
+
const result = (0, share_view_access_js_1.checkShareViewAccess)(grant, path, rawInput);
|
|
34
|
+
return result.ok ? { ok: true, mode: 'share', grant } : { ok: false, reason: result.reason };
|
|
35
|
+
}
|
|
36
|
+
if ((0, types_1.scopesGrantAdmin)(scopes))
|
|
37
|
+
return { ok: true, mode: 'all' };
|
|
38
|
+
const capability = (0, types_1.capabilityScopesOf)(scopes);
|
|
39
|
+
if (path in system_1.METHOD_ACCESS_MAP) {
|
|
40
|
+
const result = (0, scope_access_js_1.checkScopeAccess)(capability, path, rawInput, lookup);
|
|
41
|
+
if (!result.ok)
|
|
42
|
+
return { ok: false, reason: result.reason };
|
|
43
|
+
}
|
|
44
|
+
return { ok: true, mode: 'scoped', scopes: capability };
|
|
45
|
+
}
|
|
46
|
+
/** May a principal holding `scopes` reach `/addon/<addonId>/…` with this verb? */
|
|
47
|
+
function authorizeAddonRoute(scopes, target) {
|
|
48
|
+
const grant = (0, types_1.shareViewGrantOf)(scopes);
|
|
49
|
+
if (grant !== null) {
|
|
50
|
+
if (!(target.share ?? []).includes(grant.kind)) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
status: 403,
|
|
54
|
+
error: 'Share token not accepted here',
|
|
55
|
+
reason: `share-view ${grant.kind} not accepted by ${target.addonId}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
shareHeaders: {
|
|
61
|
+
[addon_route_share_gate_js_1.SHARE_KIND_HEADER]: grant.kind,
|
|
62
|
+
[addon_route_share_gate_js_1.SHARE_DEVICES_HEADER]: grant.deviceIds.join(','),
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if ((0, types_1.scopesGrantAdmin)(scopes))
|
|
67
|
+
return { ok: true, shareHeaders: null };
|
|
68
|
+
if (target.access === 'admin') {
|
|
69
|
+
return { ok: false, status: 403, error: 'Admin required', reason: 'admin route' };
|
|
70
|
+
}
|
|
71
|
+
const access = (0, types_1.methodAccessForHttpMethod)(target.method);
|
|
72
|
+
if ((0, types_1.scopesAllowAddonRoutes)(scopes, access) ||
|
|
73
|
+
(0, types_1.scopesAllowAddon)((0, types_1.capabilityScopesOf)(scopes), target.addonId, access)) {
|
|
74
|
+
return { ok: true, shareHeaders: null };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 403,
|
|
79
|
+
error: 'Token scope mismatch',
|
|
80
|
+
reason: `holds no addon:${target.addonId}[${access}] grant`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -147,7 +147,7 @@ function projectListAllForPrincipal(data, isAdmin, scopes, fleet) {
|
|
|
147
147
|
function scopedEventVisibility(user, lookup) {
|
|
148
148
|
if (user.isAdmin)
|
|
149
149
|
return null;
|
|
150
|
-
const scopes = user.scopes ?? [];
|
|
150
|
+
const scopes = (0, types_1.capabilityScopesOf)(user.scopes ?? []);
|
|
151
151
|
if (isBroadEventViewer(scopes))
|
|
152
152
|
return null;
|
|
153
153
|
return (evt) => {
|
|
@@ -29,12 +29,14 @@ function readCredentialIdentity(claims) {
|
|
|
29
29
|
/**
|
|
30
30
|
* Render a principal for one log line.
|
|
31
31
|
*
|
|
32
|
-
* Shapes, in the order they are distinguished
|
|
32
|
+
* Shapes, in the order they are distinguished (the label comes from the
|
|
33
|
+
* principal's audit tag — attribution only, D892):
|
|
33
34
|
*
|
|
34
35
|
* anonymous
|
|
35
36
|
* apocaliss92 (admin)
|
|
36
|
-
* share:ab12cd34 (share-
|
|
37
|
-
* scoped:1a2b3c4d (scoped-token)
|
|
37
|
+
* share:ab12cd34 (legacy-share-token)
|
|
38
|
+
* scoped:1a2b3c4d (legacy-scoped-token)
|
|
39
|
+
* token:ci (token)
|
|
38
40
|
* apocaliss92 (oauth-access session=910a6179-…)
|
|
39
41
|
* apocaliss92 (session)
|
|
40
42
|
*/
|
|
@@ -44,15 +46,14 @@ function describeTrpcPrincipal(user) {
|
|
|
44
46
|
if (!user)
|
|
45
47
|
return 'anonymous';
|
|
46
48
|
const name = user.username || user.id || 'unknown';
|
|
47
|
-
if (user.
|
|
48
|
-
return `${name} (share-view)`;
|
|
49
|
-
if (user.isAdmin)
|
|
49
|
+
if (user.auditTag === 'session' && user.isAdmin)
|
|
50
50
|
return `${name} (admin)`;
|
|
51
51
|
if (user.credential) {
|
|
52
52
|
const session = user.oauthSessionId ? ` session=${user.oauthSessionId}` : '';
|
|
53
53
|
return `${name} (${user.credential}${session})`;
|
|
54
54
|
}
|
|
55
|
-
if (user.
|
|
56
|
-
return `${name} (
|
|
57
|
-
|
|
55
|
+
if (user.auditTag !== undefined) {
|
|
56
|
+
return `${name} (${user.auditTag}${user.isAdmin && user.auditTag !== 'session' ? ' admin' : ''})`;
|
|
57
|
+
}
|
|
58
|
+
return user.isAdmin ? `${name} (admin)` : `${name} (session)`;
|
|
58
59
|
}
|