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) => {
|
export const normalizeError = (error: unknown) => {
|
||||||
const normalized = error instanceof Error ? error : new Error(String(error));
|
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
|
// Add message if empty otherwise Application Insights will respond 400
|
||||||
// and the error will not be recorded.
|
// and the error will not be recorded.
|
||||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||||
|
|
|
@ -5,16 +5,7 @@ import { conditional, pick } from '@silverhand/essentials';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import { ZodError } from 'zod';
|
import { ZodError } from 'zod';
|
||||||
|
|
||||||
const formatZodError = ({ issues }: ZodError): string[] =>
|
import { formatZodError } from '#src/errors/utils/index.js';
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class RequestError extends Error {
|
export default class RequestError extends Error {
|
||||||
code: LogtoErrorCode;
|
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 { ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
|
||||||
import { trySafe } from '@silverhand/essentials';
|
import { conditional, trySafe } from '@silverhand/essentials';
|
||||||
import type { Middleware } from 'koa';
|
import type { Middleware } from 'koa';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import RequestError from '#src/errors/RequestError/index.js';
|
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> {
|
export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware<StateT, ContextT> {
|
||||||
// Too many error types :-)
|
// Too many error types :-)
|
||||||
|
@ -16,7 +17,12 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
||||||
throw error;
|
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 errorDescriptionGuard = z.object({ errorDescription: z.string() });
|
||||||
const message = trySafe(() => errorDescriptionGuard.parse(data))?.errorDescription;
|
const message = trySafe(() => errorDescriptionGuard.parse(data))?.errorDescription;
|
||||||
|
@ -28,14 +34,14 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
||||||
case ConnectorErrorCodes.InsufficientRequestParameters:
|
case ConnectorErrorCodes.InsufficientRequestParameters:
|
||||||
case ConnectorErrorCodes.InvalidConfig:
|
case ConnectorErrorCodes.InvalidConfig:
|
||||||
case ConnectorErrorCodes.InvalidResponse: {
|
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.SocialAuthCodeInvalid:
|
||||||
case ConnectorErrorCodes.SocialAccessTokenInvalid:
|
case ConnectorErrorCodes.SocialAccessTokenInvalid:
|
||||||
case ConnectorErrorCodes.SocialIdTokenInvalid:
|
case ConnectorErrorCodes.SocialIdTokenInvalid:
|
||||||
case ConnectorErrorCodes.AuthorizationFailed: {
|
case ConnectorErrorCodes.AuthorizationFailed: {
|
||||||
throw new RequestError({ code: `connector.${code}`, status: 401 }, data);
|
throw new RequestError({ code: `connector.${code}`, status: 401 }, requestErrorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
case ConnectorErrorCodes.TemplateNotFound: {
|
case ConnectorErrorCodes.TemplateNotFound: {
|
||||||
|
@ -44,16 +50,16 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
||||||
code: `connector.${code}`,
|
code: `connector.${code}`,
|
||||||
status: 400,
|
status: 400,
|
||||||
},
|
},
|
||||||
data
|
requestErrorData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case ConnectorErrorCodes.NotImplemented: {
|
case ConnectorErrorCodes.NotImplemented: {
|
||||||
throw new RequestError({ code: `connector.${code}`, status: 501 }, data);
|
throw new RequestError({ code: `connector.${code}`, status: 501 }, requestErrorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
case ConnectorErrorCodes.RateLimitExceeded: {
|
case ConnectorErrorCodes.RateLimitExceeded: {
|
||||||
throw new RequestError({ code: `connector.${code}`, status: 429 }, data);
|
throw new RequestError({ code: `connector.${code}`, status: 429 }, requestErrorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
@ -63,7 +69,7 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
||||||
status: 400, // Temporarily use 400 to avoid false positives. May update later.
|
status: 400, // Temporarily use 400 to avoid false positives. May update later.
|
||||||
errorDescription: message,
|
errorDescription: message,
|
||||||
},
|
},
|
||||||
data
|
requestErrorData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,10 +114,9 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
||||||
const samlAssertionParseResult = samlAssertionGuard.safeParse(body);
|
const samlAssertionParseResult = samlAssertionGuard.safeParse(body);
|
||||||
|
|
||||||
if (!samlAssertionParseResult.success) {
|
if (!samlAssertionParseResult.success) {
|
||||||
throw new ConnectorError(
|
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, {
|
||||||
ConnectorErrorCodes.InvalidResponse,
|
zodError: samlAssertionParseResult.error,
|
||||||
samlAssertionParseResult.error
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,7 +133,7 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
||||||
assertThat(
|
assertThat(
|
||||||
validateSamlAssertion,
|
validateSamlAssertion,
|
||||||
new ConnectorError(ConnectorErrorCodes.NotImplemented, {
|
new ConnectorError(ConnectorErrorCodes.NotImplemented, {
|
||||||
message: 'Method `validateSamlAssertion()` is not implemented.',
|
data: 'Method `validateSamlAssertion()` is not implemented.',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const redirectTo = await validateSamlAssertion({ body }, getSession, setSession);
|
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);
|
const result = guard.safeParse(config);
|
||||||
|
|
||||||
if (!result.success) {
|
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 {
|
try {
|
||||||
return JSON.parse(jsonString);
|
return JSON.parse(jsonString);
|
||||||
} catch {
|
} 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);
|
const parsed = parseJson(...args);
|
||||||
|
|
||||||
if (!(parsed !== null && typeof parsed === 'object')) {
|
if (!(parsed !== null && typeof parsed === 'object')) {
|
||||||
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, parsed);
|
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, { data: parsed });
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { LanguageTag } from '@logto/language-kit';
|
import type { LanguageTag } from '@logto/language-kit';
|
||||||
import { isLanguageTag } 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';
|
import { z } from 'zod';
|
||||||
|
|
||||||
// MARK: Foundation
|
// MARK: Foundation
|
||||||
|
@ -59,13 +59,17 @@ export enum ConnectorErrorCodes {
|
||||||
|
|
||||||
export class ConnectorError extends Error {
|
export class ConnectorError extends Error {
|
||||||
public code: ConnectorErrorCodes;
|
public code: ConnectorErrorCodes;
|
||||||
public data: unknown;
|
public data?: unknown;
|
||||||
|
public zodError?: ZodError;
|
||||||
|
|
||||||
constructor(code: ConnectorErrorCodes, data?: unknown) {
|
constructor(code: ConnectorErrorCodes, payload?: { data?: unknown; zodError?: ZodError }) {
|
||||||
const message = typeof data === 'string' ? data : 'Connector error occurred.';
|
const message = `Connector error occurred: ${code}`;
|
||||||
super(message);
|
super(message);
|
||||||
|
this.name = 'ConnectorError';
|
||||||
this.code = code;
|
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