mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
Merge branch 'admin-display-authenticators' into 'main'
(admin) display authenticators See merge request PronounsPage/PronounsPage!460
This commit is contained in:
commit
cbd466517b
@ -59,6 +59,7 @@ export default {
|
|||||||
'code',
|
'code',
|
||||||
'org',
|
'org',
|
||||||
'impersonate',
|
'impersonate',
|
||||||
|
'community',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -142,7 +142,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="$isGranted('*')" class="list-group list-group-flare mt-3">
|
<div v-if="$isGranted('*') || $isGranted('community')" class="list-group list-group-flare mt-3">
|
||||||
<div class="list-group-item pt-3">
|
<div class="list-group-item pt-3">
|
||||||
<h5>
|
<h5>
|
||||||
<Icon v="user-cog" />
|
<Icon v="user-cog" />
|
||||||
@ -150,17 +150,45 @@
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
|
v-if="$isGranted('*')"
|
||||||
href="#"
|
href="#"
|
||||||
class="list-group-item list-group-item-action list-group-item-hoverable small"
|
class="list-group-item list-group-item-action list-group-item-hoverable small"
|
||||||
@click.prevent="impersonate()"
|
@click.prevent="impersonate()"
|
||||||
><Icon v="user-secret" /> Impersonate
|
><Icon v="user-secret" /> Impersonate
|
||||||
</a>
|
</a>
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
|
v-if="$isGranted('*')"
|
||||||
:to="`/admin/audit-log/${user.username}/${user.id}`"
|
:to="`/admin/audit-log/${user.username}/${user.id}`"
|
||||||
class="list-group-item list-group-item-action list-group-item-hoverable small"
|
class="list-group-item list-group-item-action list-group-item-hoverable small"
|
||||||
>
|
>
|
||||||
<Icon v="file-search" /> Audit log
|
<Icon v="file-search" /> Audit log
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
<div class="list-group-item pt-3">
|
||||||
|
<h6>
|
||||||
|
<Icon v="key" /> Authenticators
|
||||||
|
</h6>
|
||||||
|
<Loading :value="authenticators">
|
||||||
|
<p>
|
||||||
|
<a v-if="!showExpiredAuthenticators" href="#" @click.prevent="showExpiredAuthenticators = true">
|
||||||
|
Include expired ones
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<template v-for="authenticator in authenticators">
|
||||||
|
<li
|
||||||
|
v-if="showExpiredAuthenticators || authenticator.validUntil === null"
|
||||||
|
:class="authenticator.validUntil === null ? '' : 'small text-muted'"
|
||||||
|
>
|
||||||
|
<Tooltip :text="authenticator.type">
|
||||||
|
<Icon :v="authenticatorIcon(authenticator.type)" />
|
||||||
|
</Tooltip>
|
||||||
|
{{ $datetime($ulidTime(authenticator.id)) }}
|
||||||
|
<pre><code>{{ authenticator.payload }}</code></pre>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</Loading>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -276,6 +304,9 @@ export default mainPronoun.extend({
|
|||||||
hasSus: false,
|
hasSus: false,
|
||||||
|
|
||||||
contentWarningDismissed: false,
|
contentWarningDismissed: false,
|
||||||
|
|
||||||
|
authenticators: undefined,
|
||||||
|
showExpiredAuthenticators: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
head() {
|
head() {
|
||||||
@ -342,6 +373,9 @@ export default mainPronoun.extend({
|
|||||||
}
|
}
|
||||||
this.terms = await this.$axios.$get('/terms');
|
this.terms = await this.$axios.$get('/terms');
|
||||||
}
|
}
|
||||||
|
if (this.$isGranted('*') || this.$isGranted('community')) {
|
||||||
|
this.authenticators = await this.$axios.$get(`/admin/authenticators/${this.user.id}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async generateCard(dark) {
|
async generateCard(dark) {
|
||||||
@ -397,6 +431,20 @@ export default mainPronoun.extend({
|
|||||||
await this.$router.push(`/${this.$config.user.route}`);
|
await this.$router.push(`/${this.$config.user.route}`);
|
||||||
setTimeout(() => window.location.reload(), 500);
|
setTimeout(() => window.location.reload(), 500);
|
||||||
},
|
},
|
||||||
|
authenticatorIcon(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'email':
|
||||||
|
case 'changedEmail':
|
||||||
|
return 'envelope';
|
||||||
|
case 'indieauth':
|
||||||
|
return 'indieauth.png';
|
||||||
|
case 'mfa_secret':
|
||||||
|
case 'mfa_recovery':
|
||||||
|
return 'mobile';
|
||||||
|
default:
|
||||||
|
return `b:${type}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import SQL from 'sql-template-strings';
|
import SQL from 'sql-template-strings';
|
||||||
import avatar from '../avatar.ts';
|
import avatar from '../avatar.ts';
|
||||||
import { buildDict, now, shuffle, handleErrorAsync } from '../../src/helpers.ts';
|
import { buildDict, now, shuffle, handleErrorAsync, filterObjectKeys } from '../../src/helpers.ts';
|
||||||
import allLocales from '../../locale/locales.ts';
|
import allLocales from '../../locale/locales.ts';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { caches } from '../../src/cache.js';
|
import { caches } from '../../src/cache.js';
|
||||||
@ -657,4 +657,27 @@ router.get('/admin/audit-log/:username/:id', handleErrorAsync(async (req, res) =
|
|||||||
`));
|
`));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
router.get('/admin/authenticators/:id', handleErrorAsync(async (req, res) => {
|
||||||
|
if (!req.isGranted('community') && !req.isGranted('*')) {
|
||||||
|
return res.status(401).json({ error: 'Unauthorised' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const authenticators = (await req.db.all(SQL`
|
||||||
|
SELECT * FROM authenticators
|
||||||
|
WHERE userId = ${req.params.id}
|
||||||
|
ORDER BY id DESC
|
||||||
|
`)).map((auth) => {
|
||||||
|
delete auth.userId;
|
||||||
|
|
||||||
|
const payload = JSON.parse(auth.payload);
|
||||||
|
auth.payload = typeof payload === 'string'
|
||||||
|
? null
|
||||||
|
: filterObjectKeys(payload, ['id', 'email', 'name', 'instance', 'username']);
|
||||||
|
|
||||||
|
return auth;
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json(authenticators);
|
||||||
|
}));
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -258,7 +258,7 @@ export const randomItemWeighted = <T extends WeightedItem>(array: T[]): T => {
|
|||||||
export const randomNumber = (min: number, max: number): number => Math.floor(Math.random() * (max - min + 1)) + min;
|
export const randomNumber = (min: number, max: number): number => Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
|
||||||
|
|
||||||
const RESTRICTED_AREAS = ['code', 'org', 'impersonate'];
|
const RESTRICTED_AREAS = ['code', 'org', 'impersonate', 'community'];
|
||||||
|
|
||||||
export const isGranted = (user: Pick<User, 'roles'>, locale: string | null, area: string = ''): boolean => {
|
export const isGranted = (user: Pick<User, 'roles'>, locale: string | null, area: string = ''): boolean => {
|
||||||
if (area === '*') {
|
if (area === '*') {
|
||||||
@ -502,3 +502,12 @@ export const parseUserJwt = (token: string): string | JwtPayload | null => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const filterObjectKeys = <T extends Record<string, any>, K extends keyof T>(obj: T, keysToKeep: K[]): Pick<T, K> => {
|
||||||
|
return keysToKeep.reduce((filteredObj, key) => {
|
||||||
|
if (key in obj) {
|
||||||
|
filteredObj[key] = obj[key];
|
||||||
|
}
|
||||||
|
return filteredObj;
|
||||||
|
}, {} as Pick<T, K>);
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user