Changed 2fa delete from body to query for code

as per best practices
This commit is contained in:
Jamie Curnow
2026-01-14 13:24:38 +10:00
parent d33bb02c74
commit c1ad7788f1
5 changed files with 28 additions and 56 deletions

View File

@@ -370,16 +370,16 @@ router
}) })
/** /**
* DELETE /api/users/123/2fa * DELETE /api/users/123/2fa?code=XXXXXX
* *
* Disable 2FA for a user * Disable 2FA for a user
*/ */
.delete(async (req, res, next) => { .delete(async (req, res, next) => {
try { try {
const { code } = await apiValidator( const code = typeof req.query.code === "string" ? req.query.code : null;
getValidationSchema("/users/{userID}/2fa", "delete"), if (!code) {
req.body, throw new errs.ValidationError("Missing required parameter: code");
); }
await internal2FA.disable(res.locals.access, req.params.user_id, code); await internal2FA.disable(res.locals.access, req.params.user_id, code);
res.status(200).send(true); res.status(200).send(true);
} catch (err) { } catch (err) {

View File

@@ -13,32 +13,21 @@
"required": true, "required": true,
"description": "User ID", "description": "User ID",
"example": 2 "example": 2
},
{
"in": "query",
"name": "code",
"schema": {
"type": "string",
"minLength": 6,
"maxLength": 6,
"example": "012345"
},
"required": true,
"description": "2fa Code",
"example": "012345"
} }
], ],
"requestBody": {
"description": "2fa Code Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"code": {
"minLength": 6,
"maxLength": 6,
"type": "string",
"example": "012345"
}
},
"required": ["code"],
"type": "object"
},
"example": {
"code": "012345"
}
}
}
},
"responses": { "responses": {
"200": { "200": {
"content": { "content": {

View File

@@ -27,12 +27,10 @@
"application/json": { "application/json": {
"examples": { "examples": {
"default": { "default": {
"value": [ "value": {
{ "enabled": false,
"enabled": false, "backup_codes_remaining": 0
"backup_codes_remaining": 0 }
}
]
} }
}, },
"schema": { "schema": {

View File

@@ -156,7 +156,6 @@ export async function del({ url, params }: DeleteArgs, abortController?: AbortCo
const method = "DELETE"; const method = "DELETE";
const headers = { const headers = {
...buildAuthHeader(), ...buildAuthHeader(),
[contentTypeHeader]: "application/json",
}; };
const signal = abortController?.signal; const signal = abortController?.signal;
const response = await fetch(apiUrl, { method, headers, signal }); const response = await fetch(apiUrl, { method, headers, signal });

View File

@@ -1,5 +1,3 @@
import { camelizeKeys, decamelizeKeys } from "humps";
import AuthStore from "src/modules/AuthStore";
import * as api from "./base"; import * as api from "./base";
import type { TwoFactorEnableResponse, TwoFactorSetupResponse, TwoFactorStatusResponse } from "./responseTypes"; import type { TwoFactorEnableResponse, TwoFactorSetupResponse, TwoFactorStatusResponse } from "./responseTypes";
@@ -22,25 +20,13 @@ export async function enable2FA(userId: number | "me", code: string): Promise<Tw
}); });
} }
export async function disable2FA(userId: number | "me", code: string): Promise<{ success: boolean }> { export async function disable2FA(userId: number | "me", code: string): Promise<boolean> {
const headers: Record<string, string> = { return await api.del({
"Content-Type": "application/json", url: `/users/${userId}/2fa`,
}; params: {
if (AuthStore.token) { code,
headers.Authorization = `Bearer ${AuthStore.token.token}`; },
}
const response = await fetch(`/api/users/${userId}/2fa`, {
method: "DELETE",
headers,
body: JSON.stringify(decamelizeKeys({ code })),
}); });
const payload = await response.json();
if (!response.ok) {
throw new Error(payload.error?.messageI18n || payload.error?.message || "Failed to disable 2FA");
}
return camelizeKeys(payload) as { success: boolean };
} }
export async function regenerateBackupCodes(userId: number | "me", code: string): Promise<TwoFactorEnableResponse> { export async function regenerateBackupCodes(userId: number | "me", code: string): Promise<TwoFactorEnableResponse> {