mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
fix: azure with detailed error messages
This commit is contained in:
parent
fb2bb527aa
commit
defddf7e08
7 changed files with 57 additions and 31 deletions
|
@ -4,6 +4,20 @@ import type { TelemetryClient } from 'applicationinsights';
|
|||
export const normalizeError = (error: unknown) => {
|
||||
const normalized = error instanceof Error ? error : new Error(String(error));
|
||||
|
||||
const payload = Object.entries(normalized).reduce(
|
||||
(result, [key, value]) =>
|
||||
['message', 'data'].includes(key) && Boolean(value)
|
||||
? // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
{ ...result, [key]: value }
|
||||
: result,
|
||||
{}
|
||||
);
|
||||
if (typeof payload === 'object' && Object.entries(payload).length > 0) {
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
normalized.message = JSON.stringify(payload);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// Add message if empty otherwise Application Insights will respond 400
|
||||
// and the error will not be recorded.
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
|
|
|
@ -5,16 +5,7 @@ import { conditional, pick } from '@silverhand/essentials';
|
|||
import i18next from 'i18next';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
const formatZodError = ({ issues }: ZodError): string[] =>
|
||||
issues.map((issue) => {
|
||||
const base = `Error in key path "${issue.path.map(String).join('.')}": (${issue.code}) `;
|
||||
|
||||
if (issue.code === 'invalid_type') {
|
||||
return base + `Expected ${issue.expected} but received ${issue.received}.`;
|
||||
}
|
||||
|
||||
return base + issue.message;
|
||||
});
|
||||
import { formatZodError } from '#src/errors/utils/index.js';
|
||||
|
||||
export default class RequestError extends Error {
|
||||
code: LogtoErrorCode;
|
||||
|
|
12
packages/core/src/errors/utils/index.ts
Normal file
12
packages/core/src/errors/utils/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type { ZodError } from 'zod';
|
||||
|
||||
export const formatZodError = ({ issues }: ZodError): string[] =>
|
||||
issues.map((issue) => {
|
||||
const base = `Error in key path "${issue.path.map(String).join('.')}": (${issue.code}) `;
|
||||
|
||||
if (issue.code === 'invalid_type') {
|
||||
return base + `Expected ${issue.expected} but received ${issue.received}.`;
|
||||
}
|
||||
|
||||
return base + issue.message;
|
||||
});
|
|
@ -1,9 +1,10 @@
|
|||
import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
import { conditional, trySafe } from '@silverhand/essentials';
|
||||
import type { Middleware } from 'koa';
|
||||
import { z } from 'zod';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { formatZodError } from '#src/errors/utils/index.js';
|
||||
|
||||
export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware<StateT, ContextT> {
|
||||
// Too many error types :-)
|
||||
|
@ -16,7 +17,12 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
|||
throw error;
|
||||
}
|
||||
|
||||
const { code, data } = error;
|
||||
const { code, data, zodError, name } = error;
|
||||
const requestErrorData = {
|
||||
data,
|
||||
name,
|
||||
zodErrorMessage: conditional(zodError && formatZodError(zodError).join('\n')),
|
||||
};
|
||||
|
||||
const errorDescriptionGuard = z.object({ errorDescription: z.string() });
|
||||
const message = trySafe(() => errorDescriptionGuard.parse(data))?.errorDescription;
|
||||
|
@ -28,14 +34,14 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
|||
case ConnectorErrorCodes.InsufficientRequestParameters:
|
||||
case ConnectorErrorCodes.InvalidConfig:
|
||||
case ConnectorErrorCodes.InvalidResponse: {
|
||||
throw new RequestError({ code: `connector.${code}`, status: 400 }, data);
|
||||
throw new RequestError({ code: `connector.${code}`, status: 400 }, requestErrorData);
|
||||
}
|
||||
|
||||
case ConnectorErrorCodes.SocialAuthCodeInvalid:
|
||||
case ConnectorErrorCodes.SocialAccessTokenInvalid:
|
||||
case ConnectorErrorCodes.SocialIdTokenInvalid:
|
||||
case ConnectorErrorCodes.AuthorizationFailed: {
|
||||
throw new RequestError({ code: `connector.${code}`, status: 401 }, data);
|
||||
throw new RequestError({ code: `connector.${code}`, status: 401 }, requestErrorData);
|
||||
}
|
||||
|
||||
case ConnectorErrorCodes.TemplateNotFound: {
|
||||
|
@ -44,16 +50,16 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
|||
code: `connector.${code}`,
|
||||
status: 400,
|
||||
},
|
||||
data
|
||||
requestErrorData
|
||||
);
|
||||
}
|
||||
|
||||
case ConnectorErrorCodes.NotImplemented: {
|
||||
throw new RequestError({ code: `connector.${code}`, status: 501 }, data);
|
||||
throw new RequestError({ code: `connector.${code}`, status: 501 }, requestErrorData);
|
||||
}
|
||||
|
||||
case ConnectorErrorCodes.RateLimitExceeded: {
|
||||
throw new RequestError({ code: `connector.${code}`, status: 429 }, data);
|
||||
throw new RequestError({ code: `connector.${code}`, status: 429 }, requestErrorData);
|
||||
}
|
||||
|
||||
default: {
|
||||
|
@ -63,7 +69,7 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
|||
status: 400, // Temporarily use 400 to avoid false positives. May update later.
|
||||
errorDescription: message,
|
||||
},
|
||||
data
|
||||
requestErrorData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,10 +114,9 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
|||
const samlAssertionParseResult = samlAssertionGuard.safeParse(body);
|
||||
|
||||
if (!samlAssertionParseResult.success) {
|
||||
throw new ConnectorError(
|
||||
ConnectorErrorCodes.InvalidResponse,
|
||||
samlAssertionParseResult.error
|
||||
);
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, {
|
||||
zodError: samlAssertionParseResult.error,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,7 +133,7 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
|||
assertThat(
|
||||
validateSamlAssertion,
|
||||
new ConnectorError(ConnectorErrorCodes.NotImplemented, {
|
||||
message: 'Method `validateSamlAssertion()` is not implemented.',
|
||||
data: 'Method `validateSamlAssertion()` is not implemented.',
|
||||
})
|
||||
);
|
||||
const redirectTo = await validateSamlAssertion({ body }, getSession, setSession);
|
||||
|
|
|
@ -8,7 +8,7 @@ export function validateConfig<T>(config: unknown, guard: ZodType): asserts conf
|
|||
const result = guard.safeParse(config);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error);
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, { zodError: result.error });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ export const parseJson = (
|
|||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch {
|
||||
throw new ConnectorError(errorCode, errorPayload ?? jsonString);
|
||||
throw new ConnectorError(errorCode, { data: errorPayload ?? jsonString });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -28,7 +28,7 @@ export const parseJsonObject = (...args: Parameters<typeof parseJson>) => {
|
|||
const parsed = parseJson(...args);
|
||||
|
||||
if (!(parsed !== null && typeof parsed === 'object')) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, parsed);
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, { data: parsed });
|
||||
}
|
||||
|
||||
return parsed;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { LanguageTag } from '@logto/language-kit';
|
||||
import { isLanguageTag } from '@logto/language-kit';
|
||||
import type { ZodType } from 'zod';
|
||||
import type { ZodError, ZodType } from 'zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
// MARK: Foundation
|
||||
|
@ -59,13 +59,17 @@ export enum ConnectorErrorCodes {
|
|||
|
||||
export class ConnectorError extends Error {
|
||||
public code: ConnectorErrorCodes;
|
||||
public data: unknown;
|
||||
public data?: unknown;
|
||||
public zodError?: ZodError;
|
||||
|
||||
constructor(code: ConnectorErrorCodes, data?: unknown) {
|
||||
const message = typeof data === 'string' ? data : 'Connector error occurred.';
|
||||
constructor(code: ConnectorErrorCodes, payload?: { data?: unknown; zodError?: ZodError }) {
|
||||
const message = `Connector error occurred: ${code}`;
|
||||
super(message);
|
||||
this.name = 'ConnectorError';
|
||||
this.code = code;
|
||||
this.data = typeof data === 'string' ? { message: data } : data;
|
||||
const { data, zodError } = payload ?? {};
|
||||
this.data = data;
|
||||
this.zodError = zodError;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue