next-auth
Version:
Authentication for Next.js
128 lines (110 loc) • 3.37 kB
text/typescript
import type { EventCallbacks, InternalOptions, LoggerInstance } from ".."
/**
* Same as the default `Error`, but it is JSON serializable.
* @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
*/
export class UnknownError extends Error {
code: string
constructor(error: Error | string) {
// Support passing error or string
super((error as Error)?.message ?? error)
this.name = "UnknownError"
this.code = (error as any).code
if (error instanceof Error) {
this.stack = error.stack
}
}
toJSON() {
return {
name: this.name,
message: this.message,
stack: this.stack,
}
}
}
export class OAuthCallbackError extends UnknownError {
name = "OAuthCallbackError"
}
/**
* Thrown when an Email address is already associated with an account
* but the user is trying an OAuth account that is not linked to it.
*/
export class AccountNotLinkedError extends UnknownError {
name = "AccountNotLinkedError"
}
export class MissingAPIRoute extends UnknownError {
name = "MissingAPIRouteError"
code = "MISSING_NEXTAUTH_API_ROUTE_ERROR"
}
export class MissingSecret extends UnknownError {
name = "MissingSecretError"
code = "NO_SECRET"
}
export class MissingAuthorize extends UnknownError {
name = "MissingAuthorizeError"
code = "CALLBACK_CREDENTIALS_HANDLER_ERROR"
}
export class MissingAdapter extends UnknownError {
name = "MissingAdapterError"
code = "EMAIL_REQUIRES_ADAPTER_ERROR"
}
export class MissingAdapterMethods extends UnknownError {
name = "MissingAdapterMethodsError"
code = "MISSING_ADAPTER_METHODS_ERROR"
}
export class UnsupportedStrategy extends UnknownError {
name = "UnsupportedStrategyError"
code = "CALLBACK_CREDENTIALS_JWT_ERROR"
}
export class InvalidCallbackUrl extends UnknownError {
name = "InvalidCallbackUrl"
code = "INVALID_CALLBACK_URL_ERROR"
}
type Method = (...args: any[]) => Promise<any>
export function upperSnake(s: string) {
return s.replace(/([A-Z])/g, "_$1").toUpperCase()
}
export function capitalize(s: string) {
return `${s[0].toUpperCase()}${s.slice(1)}`
}
/**
* Wraps an object of methods and adds error handling.
*/
export function eventsErrorHandler(
methods: Partial<EventCallbacks>,
logger: LoggerInstance
): Partial<EventCallbacks> {
return Object.keys(methods).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
const method: Method = methods[name as keyof Method]
return await method(...args)
} catch (e) {
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error)
}
}
return acc
}, {})
}
/** Handles adapter induced errors. */
export function adapterErrorHandler<TAdapter>(
adapter: TAdapter | undefined,
logger: LoggerInstance
): InternalOptions["adapter"] | undefined {
if (!adapter) return
return Object.keys(adapter).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
logger.debug(`adapter_${name}`, { args })
const method: Method = adapter[name as keyof Method]
return await method(...args)
} catch (error) {
logger.error(`adapter_error_${name}`, error as Error)
const e = new UnknownError(error as Error)
e.name = `${capitalize(name)}Error`
throw e
}
}
return acc
}, {})
}