mirror of
https://github.com/outline/outline.git
synced 2026-08-03 13:27:25 +03:00
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:
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user