feat: 🔒 allow admins to reset users' 2FA and passkeys
ref: https://frfsh.plus.st/notes/9hqswpwiwjaihcgo
This commit is contained in:
parent
ba694065ee
commit
f973b1986f
5 changed files with 158 additions and 2 deletions
|
@ -1129,6 +1129,11 @@ removeRecipient: "Remove recipient"
|
|||
removeMember: "Remove member"
|
||||
verifiedLink: "Verified link"
|
||||
origin: "Origin"
|
||||
delete2fa: "Delete 2FA"
|
||||
deletePasskeys: "Delete passkeys"
|
||||
delete2faConfirm: "This will irreversibly delete 2FA on this account. Proceed?"
|
||||
deletePasskeysConfirm: "This will irreversibly delete all passkeys and security keys on this account. Proceed?"
|
||||
inputNotMatch: "Input does not match"
|
||||
|
||||
_sensitiveMediaDetection:
|
||||
description: "Reduces the effort of server moderation through automatically recognizing
|
||||
|
|
|
@ -65,6 +65,8 @@ import * as ep___admin_unsuspendUser from "./endpoints/admin/unsuspend-user.js";
|
|||
import * as ep___admin_updateMeta from "./endpoints/admin/update-meta.js";
|
||||
import * as ep___admin_vacuum from "./endpoints/admin/vacuum.js";
|
||||
import * as ep___admin_deleteAccount from "./endpoints/admin/delete-account.js";
|
||||
import * as ep___admin_delete2fa from "./endpoints/admin/delete-2fa.js";
|
||||
import * as ep___admin_deletePasskeys from "./endpoints/admin/delete-passkeys.js";
|
||||
import * as ep___admin_updateUserNote from "./endpoints/admin/update-user-note.js";
|
||||
import * as ep___announcements from "./endpoints/announcements.js";
|
||||
import * as ep___antennas_create from "./endpoints/antennas/create.js";
|
||||
|
@ -418,6 +420,8 @@ const eps = [
|
|||
["admin/update-meta", ep___admin_updateMeta],
|
||||
["admin/vacuum", ep___admin_vacuum],
|
||||
["admin/delete-account", ep___admin_deleteAccount],
|
||||
["admin/delete-2fa", ep___admin_delete2fa],
|
||||
["admin/delete-passkeys", ep___admin_deletePasskeys],
|
||||
["admin/update-user-note", ep___admin_updateUserNote],
|
||||
["announcements", ep___announcements],
|
||||
["antennas/create", ep___antennas_create],
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { Users, UserProfiles } from "@/models/index.js";
|
||||
import { publishMainStream } from "@/services/stream.js";
|
||||
import define from "../../define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["admin"],
|
||||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
|
||||
res: {},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", format: "misskey:id" },
|
||||
},
|
||||
required: ["userId"],
|
||||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
const user = await Users.findOneByOrFail({ id: ps.userId });
|
||||
if (user.isDeleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await UserProfiles.update(user.id, {
|
||||
twoFactorSecret: null,
|
||||
twoFactorEnabled: false,
|
||||
usePasswordLessLogin: false,
|
||||
});
|
||||
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
});
|
||||
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import { Users, UserProfiles, UserSecurityKeys } from "@/models/index.js";
|
||||
import { publishMainStream } from "@/services/stream.js";
|
||||
import define from "../../define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["admin"],
|
||||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
|
||||
res: {},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: "object",
|
||||
properties: {
|
||||
userId: { type: "string", format: "misskey:id" },
|
||||
},
|
||||
required: ["userId"],
|
||||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
const user = await Users.findOneByOrFail({ id: ps.userId });
|
||||
if (user.isDeleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await UserSecurityKeys.delete({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
await UserProfiles.update(user.id, {
|
||||
usePasswordLessLogin: false,
|
||||
});
|
||||
|
||||
const iObj = await Users.pack(user.id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true,
|
||||
});
|
||||
|
||||
publishMainStream(user.id, "meUpdated", iObj);
|
||||
});
|
|
@ -207,7 +207,7 @@
|
|||
v-if="user.host == null && iAmModerator"
|
||||
inline
|
||||
@click="resetPassword"
|
||||
><i class="ph-key ph-bold ph-lg"></i>
|
||||
><i class="ph-password ph-bold ph-lg"></i>
|
||||
{{ i18n.ts.resetPassword }}</FormButton
|
||||
>
|
||||
<FormButton
|
||||
|
@ -221,6 +221,23 @@
|
|||
v-if="$i.isAdmin"
|
||||
inline
|
||||
danger
|
||||
@click="delete2fa"
|
||||
><i class="ph-key ph-bold ph-lg"></i>
|
||||
{{ i18n.ts.delete2fa }}</FormButton
|
||||
>
|
||||
<FormButton
|
||||
v-if="$i.isAdmin"
|
||||
inline
|
||||
danger
|
||||
@click="deletePasskeys"
|
||||
><i class="ph-poker-chip ph-bold ph-lg"></i>
|
||||
{{ i18n.ts.deletePasskeys }}</FormButton
|
||||
>
|
||||
<FormButton
|
||||
v-if="$i.isAdmin"
|
||||
inline
|
||||
primary
|
||||
danger
|
||||
@click="deleteAccount"
|
||||
><i class="ph-user-minus ph-bold ph-lg"></i>
|
||||
{{ i18n.ts.deleteAccount }}</FormButton
|
||||
|
@ -543,6 +560,54 @@ async function applyDriveCapacityOverride() {
|
|||
}
|
||||
}
|
||||
|
||||
async function delete2fa() {
|
||||
const confirm = await os.confirm({
|
||||
type: "warning",
|
||||
text: i18n.ts.delete2faConfirm,
|
||||
});
|
||||
if (confirm.canceled) return;
|
||||
|
||||
const typed = await os.inputText({
|
||||
text: i18n.t("typeToConfirm", { x: user?.username }),
|
||||
});
|
||||
if (typed.canceled) return;
|
||||
|
||||
if (typed.result === user?.username) {
|
||||
await os.apiWithDialog("admin/delete-2fa", {
|
||||
userId: user.id,
|
||||
});
|
||||
} else {
|
||||
os.alert({
|
||||
type: "error",
|
||||
text: i18n.ts.inputNotMatch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePasskeys() {
|
||||
const confirm = await os.confirm({
|
||||
type: "warning",
|
||||
text: i18n.ts.deletePasskeysConfirm,
|
||||
});
|
||||
if (confirm.canceled) return;
|
||||
|
||||
const typed = await os.inputText({
|
||||
text: i18n.t("typeToConfirm", { x: user?.username }),
|
||||
});
|
||||
if (typed.canceled) return;
|
||||
|
||||
if (typed.result === user?.username) {
|
||||
await os.apiWithDialog("admin/delete-passkeys", {
|
||||
userId: user.id,
|
||||
});
|
||||
} else {
|
||||
os.alert({
|
||||
type: "error",
|
||||
text: i18n.ts.inputNotMatch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
const confirm = await os.confirm({
|
||||
type: "warning",
|
||||
|
@ -562,7 +627,7 @@ async function deleteAccount() {
|
|||
} else {
|
||||
os.alert({
|
||||
type: "error",
|
||||
text: "input not match",
|
||||
text: i18n.ts.inputNotMatch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue