@faable/faable 1.36.0 → 1.37.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.
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
|
|
2
2
|
import { log } from '../../../log.js';
|
|
3
3
|
import { json_option, tenant_options } from '../options.js';
|
|
4
|
+
import { fetch_items } from '../paging.js';
|
|
4
5
|
import { print_json, yes_no, when } from '../render.js';
|
|
5
6
|
|
|
7
|
+
// Identity rows carry the RAW provider access/refresh tokens — strip them
|
|
8
|
+
// before anything (json output included) can print one.
|
|
9
|
+
const sanitize_identity = (i) => {
|
|
10
|
+
const { access_token: _a, access_token_secret: _s, refresh_token: _r, ...safe } = i;
|
|
11
|
+
return safe;
|
|
12
|
+
};
|
|
6
13
|
const users_get = {
|
|
7
14
|
command: 'get <user_id>',
|
|
8
|
-
describe: 'Show a user',
|
|
15
|
+
describe: 'Show a user, including their federated identities',
|
|
9
16
|
builder: yargs => json_option(tenant_options(yargs))
|
|
10
17
|
.positional('user_id', {
|
|
11
18
|
type: 'string',
|
|
@@ -16,8 +23,22 @@ const users_get = {
|
|
|
16
23
|
handler: withAuthHints(async (args) => {
|
|
17
24
|
const api = await requireAuthAdmin(args);
|
|
18
25
|
const user = await api.userGet(args.user_id);
|
|
26
|
+
// Federated identities (GitHub, Google…): who this user IS at the
|
|
27
|
+
// provider — the piece the abuse playbook keeps needing (a Faable email
|
|
28
|
+
// rarely matches the GitHub handle that owns the deployed repos).
|
|
29
|
+
// Best-effort: a tenant where the session can't read /identity still
|
|
30
|
+
// renders the user card.
|
|
31
|
+
const identities = await fetch_items(api.identityList({ query: `user:${args.user_id}` }), true)
|
|
32
|
+
.then(r => r.items.map(i => sanitize_identity(i)))
|
|
33
|
+
.catch(() => []);
|
|
34
|
+
// Resolve connection ids to names ("github") once per distinct id.
|
|
35
|
+
const connection_names = new Map();
|
|
36
|
+
for (const id of new Set(identities.map(i => String(i.connection ?? '')).filter(Boolean))) {
|
|
37
|
+
const conn = (await api.connectionGet(id).catch(() => null));
|
|
38
|
+
connection_names.set(id, conn?.connection_name ?? conn?.connection_type ?? id);
|
|
39
|
+
}
|
|
19
40
|
if (args.json)
|
|
20
|
-
return print_json(user);
|
|
41
|
+
return print_json({ ...user, identities });
|
|
21
42
|
log.info(`👤 ${user.id}`);
|
|
22
43
|
log.info(` Email: ${user.email ?? '-'} (verified: ${yes_no(user.email_verified)})`);
|
|
23
44
|
log.info(` Name: ${user.name ?? '-'}`);
|
|
@@ -36,6 +57,24 @@ const users_get = {
|
|
|
36
57
|
else {
|
|
37
58
|
log.info(' Suspended: no');
|
|
38
59
|
}
|
|
60
|
+
if (identities.length > 0) {
|
|
61
|
+
log.info(` Identities:`);
|
|
62
|
+
for (const identity of identities) {
|
|
63
|
+
const conn = connection_names.get(String(identity.connection ?? '')) ?? '-';
|
|
64
|
+
const pd = (identity.profile_data ?? {});
|
|
65
|
+
const bits = [
|
|
66
|
+
pd.login ?? String(identity.identity_id ?? '-'),
|
|
67
|
+
pd.html_url,
|
|
68
|
+
// Provider-account age is triage gold: a repo pushed from a
|
|
69
|
+
// years-old GitHub account reads differently than one created
|
|
70
|
+
// this morning.
|
|
71
|
+
pd.created_at ? `account since ${String(pd.created_at).slice(0, 10)}` : undefined,
|
|
72
|
+
pd.email && pd.email !== user.email ? `email ${pd.email}` : undefined,
|
|
73
|
+
pd.location ? `location ${pd.location}` : undefined
|
|
74
|
+
].filter(Boolean);
|
|
75
|
+
log.info(` ${conn}: ${bits.join(' · ')}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
39
78
|
})
|
|
40
79
|
};
|
|
41
80
|
|