fix: error id guards break for non-Error throwables (#13250)

Widen the narrowed instanceof Error checks introduced in the TypeScript 6
upgrade with a shared errToId helper that accepts any object carrying a
string id, restoring the login email-matching recovery path.
This commit is contained in:
Tom Moor
2026-08-02 07:19:24 -04:00
committed by GitHub
parent 9ee3c1c999
commit efc68de4a0
5 changed files with 44 additions and 14 deletions
+3 -7
View File
@@ -1,7 +1,7 @@
import path from "node:path";
import { readFile } from "fs-extra";
import invariant from "invariant";
import { toError, errToString } from "@shared/utils/error";
import { toError, errToString, errToId } from "@shared/utils/error";
import { CollectionPermission, UserRole } from "@shared/types";
import env from "@server/env";
import {
@@ -137,11 +137,7 @@ async function accountProvisioner(
} catch (err) {
// The account could not be provisioned for the provided teamId
// check to see if we can try authentication using email matching only
if (
err instanceof Error &&
"id" in err &&
err.id === "invalid_authentication"
) {
if (errToId(err) === "invalid_authentication") {
const authProvider = await AuthenticationProvider.findOne({
where: {
name: authenticationProviderParams.name,
@@ -168,7 +164,7 @@ async function accountProvisioner(
}
if (!result) {
if (err instanceof Error && "id" in err && err.id) {
if (errToId(err)) {
throw err;
} else {
throw InvalidAuthenticationError(errToString(err));
+2 -5
View File
@@ -15,6 +15,7 @@ import {
Table,
Unique,
} from "sequelize-typescript";
import { errToId } from "@shared/utils/error";
import Logger from "@server/logging/Logger";
import AuthenticationProvider from "./AuthenticationProvider";
import User from "./User";
@@ -122,11 +123,7 @@ class UserAuthentication extends IdModel<
return true;
} catch (error) {
if (
error instanceof Error &&
"id" in error &&
error.id === "authentication_required"
) {
if (errToId(error) === "authentication_required") {
return false;
}
+2 -1
View File
@@ -4,6 +4,7 @@ import type { FindOptions, WhereAttributeHash, WhereOptions } from "sequelize";
import { Op } from "sequelize";
import { subMinutes } from "date-fns";
import { randomString } from "@shared/random";
import { errToId } from "@shared/utils/error";
import { QueryNotices, TeamPreference } from "@shared/types";
import {
AuthenticationError,
@@ -147,7 +148,7 @@ router.post(
policies: presentPolicies(user, shares),
};
} catch (err) {
if (err instanceof Error && "id" in err && err.id === "not_found") {
if (errToId(err) === "not_found") {
ctx.response.status = 204;
return;
}
+22 -1
View File
@@ -1,4 +1,4 @@
import { toError, errToString } from "./error";
import { toError, errToString, errToId } from "./error";
describe("toError", () => {
it("returns the same Error instance when given an Error", () => {
@@ -34,3 +34,24 @@ describe("errToString", () => {
expect(errToString(undefined)).toBe("undefined");
});
});
describe("errToId", () => {
it("returns the id of an Error", () => {
const error = Object.assign(new Error("boom"), { id: "invalid_request" });
expect(errToId(error)).toBe("invalid_request");
});
it("returns the id of a plain object", () => {
expect(errToId({ id: "invalid_request", message: "boom" })).toBe(
"invalid_request"
);
});
it("returns undefined when there is no string id", () => {
expect(errToId(new Error("boom"))).toBeUndefined();
expect(errToId({ id: 42 })).toBeUndefined();
expect(errToId("boom")).toBeUndefined();
expect(errToId(null)).toBeUndefined();
expect(errToId(undefined)).toBeUndefined();
});
});
+15
View File
@@ -17,3 +17,18 @@ export function toError(value: unknown): Error {
export function errToString(value: unknown): string {
return value instanceof Error ? value.message : String(value);
}
/**
* Extract the identifier from an unknown value, such as a value caught in a try/catch. Errors
* thrown by the application carry an `id` describing the type of failure, however they may not
* always be instances of Error.
*
* @param value the value to read an identifier from, typically a caught error.
* @returns the identifier when the value has a string `id` property, otherwise undefined.
*/
export function errToId(value: unknown): string | undefined {
if (typeof value !== "object" || value === null || !("id" in value)) {
return undefined;
}
return typeof value.id === "string" ? value.id : undefined;
}