0
Fork 0
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:
Darcy Ye 2023-06-03 12:19:30 +08:00
parent fb2bb527aa
commit defddf7e08
No known key found for this signature in database
GPG key ID: B46F4C07EDEFC610
7 changed files with 57 additions and 31 deletions

View file

@ -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

View file

@ -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;

View 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;
});

View file

@ -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
);
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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;
}
}