/**
* db-mcp - OAuth Error Classes
*
* Module-prefixed error classes for OAuth 2.0 authentication
* and authorization failures.
*/
import { DbMcpError } from "../types/index.js";
import { ERROR_CODES } from "../utils/logger.js";
// =============================================================================
// Base OAuth Error
// =============================================================================
/**
* Base class for OAuth-related errors
*/
export class OAuthError extends DbMcpError {
/** HTTP status code for this error */
readonly httpStatus: number;
/** WWW-Authenticate header value */
readonly wwwAuthenticate?: string | undefined;
constructor(
message: string,
code: string,
httpStatus: number,
details?: Record<string, unknown>,
wwwAuthenticate?: string,
) {
super(message, code, details);
this.name = "OAuthError";
this.httpStatus = httpStatus;
this.wwwAuthenticate = wwwAuthenticate;
}
}
// =============================================================================
// Authentication Errors (401)
// =============================================================================
/**
* Token is missing from the request
*/
export class TokenMissingError extends OAuthError {
constructor(realm = "db-mcp") {
super(
"No access token provided",
ERROR_CODES.AUTH.TOKEN_MISSING.full,
401,
undefined,
`Bearer realm="${realm}"`,
);
this.name = "TokenMissingError";
}
}
/**
* Token is invalid (malformed, wrong format, etc.)
*/
export class InvalidTokenError extends OAuthError {
constructor(
message = "Invalid access token",
details?: Record<string, unknown>,
) {
super(
message,
ERROR_CODES.AUTH.TOKEN_INVALID.full,
401,
details,
'Bearer error="invalid_token"',
);
this.name = "InvalidTokenError";
}
}
/**
* Token has expired
*/
export class TokenExpiredError extends OAuthError {
constructor(expiredAt?: Date) {
super(
"Access token has expired",
ERROR_CODES.AUTH.TOKEN_EXPIRED.full,
401,
expiredAt ? { expiredAt: expiredAt.toISOString() } : undefined,
'Bearer error="invalid_token", error_description="Token has expired"',
);
this.name = "TokenExpiredError";
}
}
/**
* Token signature is invalid
*/
export class InvalidSignatureError extends OAuthError {
constructor(message = "Token signature verification failed") {
super(
message,
ERROR_CODES.AUTH.SIGNATURE_INVALID.full,
401,
undefined,
'Bearer error="invalid_token", error_description="Signature verification failed"',
);
this.name = "InvalidSignatureError";
}
}
// =============================================================================
// Authorization Errors (403)
// =============================================================================
/**
* Token does not have required scope
*/
export class InsufficientScopeError extends OAuthError {
constructor(requiredScope: string | string[], providedScopes?: string[]) {
const required = Array.isArray(requiredScope)
? requiredScope
: [requiredScope];
const scopeValue = required.join(" ");
super(
`Insufficient scope. Required: ${scopeValue}`,
ERROR_CODES.AUTH.SCOPE_DENIED.full,
403,
{ requiredScope: required, providedScopes },
`Bearer error="insufficient_scope", scope="${scopeValue}"`,
);
this.name = "InsufficientScopeError";
}
}
// =============================================================================
// Server Errors (500)
// =============================================================================
/**
* Failed to discover authorization server metadata
*/
export class AuthServerDiscoveryError extends OAuthError {
constructor(serverUrl: string, cause?: Error) {
super(
`Failed to discover authorization server metadata: ${serverUrl}`,
ERROR_CODES.AUTH.DISCOVERY_FAILED.full,
500,
{
serverUrl,
cause: cause?.message,
},
);
this.name = "AuthServerDiscoveryError";
}
}
/**
* Failed to fetch JWKS
*/
export class JwksFetchError extends OAuthError {
constructor(jwksUri: string, cause?: Error) {
super(
`Failed to fetch JWKS: ${jwksUri}`,
ERROR_CODES.AUTH.JWKS_FETCH_FAILED.full,
500,
{
jwksUri,
cause: cause?.message,
},
);
this.name = "JwksFetchError";
}
}
/**
* Failed to register client
*/
export class ClientRegistrationError extends OAuthError {
constructor(message: string, details?: Record<string, unknown>) {
super(message, ERROR_CODES.AUTH.REGISTRATION_FAILED.full, 500, details);
this.name = "ClientRegistrationError";
}
}
// =============================================================================
// Utility Functions
// =============================================================================
/**
* Check if an error is an OAuth error
*/
export function isOAuthError(error: unknown): error is OAuthError {
return error instanceof OAuthError;
}
/**
* Get WWW-Authenticate header for an OAuth error
*/
export function getWWWAuthenticateHeader(
error: OAuthError,
realm = "db-mcp",
): string {
return error.wwwAuthenticate ?? `Bearer realm="${realm}"`;
}