From a053364eaa1ec0151ee64cf82cb4cc8e88341b69 Mon Sep 17 00:00:00 2001 From: Hernan Alvarado Date: Sat, 13 Jun 2026 02:28:33 +0000 Subject: [PATCH] refactor(core): improve user messages with clearer context --- packages/core/deno.json | 2 +- .../core/src/actions/callback/access-token.ts | 14 +- .../core/src/actions/callback/callback.ts | 17 +- .../core/src/actions/callback/userinfo.ts | 26 +- .../src/actions/signIn/authorization-url.ts | 6 +- .../core/src/actions/signIn/authorization.ts | 19 +- packages/core/src/actions/signUp/signUp.ts | 2 +- packages/core/src/api/credentials.ts | 8 +- packages/core/src/api/signIn.ts | 6 +- packages/core/src/api/signOut.ts | 4 +- packages/core/src/api/signUp.ts | 10 +- packages/core/src/api/updateSession.ts | 6 +- packages/core/src/client/client.ts | 8 +- packages/core/src/cookie.ts | 10 +- packages/core/src/jose.ts | 32 +- packages/core/src/oauth/index.ts | 26 +- packages/core/src/router/errorHandler.ts | 58 +- packages/core/src/session/jose-manager.ts | 6 +- packages/core/src/session/stateless.ts | 24 +- packages/core/src/session/strategy.ts | 8 +- packages/core/src/shared/crypto.ts | 16 +- packages/core/src/shared/errors.ts | 162 ---- packages/core/src/shared/unstable_error.ts | 739 ++++++++++++++++++ packages/core/src/shared/utils.ts | 24 +- packages/core/src/validator/registry.ts | 34 +- packages/core/src/validator/validator.ts | 5 +- .../actions/callback/access-token.test.ts | 12 +- .../test/actions/callback/callback.test.ts | 22 +- .../test/actions/callback/userinfo.test.ts | 16 +- .../test/actions/signIn/authorization.test.ts | 10 +- .../core/test/actions/signIn/signIn.test.ts | 20 +- .../core/test/actions/signOut/signOut.test.ts | 28 +- packages/core/test/api/signIn.test.ts | 9 +- .../core/test/api/signInCredentials.test.ts | 4 +- packages/core/test/api/signOut.test.ts | 4 +- packages/core/test/api/signUp.test.ts | 12 +- packages/core/test/api/updateSession.test.ts | 3 +- packages/core/test/instance.test.ts | 8 +- packages/core/test/jose.test.ts | 8 +- packages/core/test/oauth.test.ts | 8 +- packages/elysia/package.json | 2 +- packages/express/package.json | 2 +- packages/hono/package.json | 2 +- packages/next/package.json | 2 +- packages/react-router/package.json | 2 +- packages/react/package.json | 2 +- 46 files changed, 964 insertions(+), 484 deletions(-) delete mode 100644 packages/core/src/shared/errors.ts create mode 100644 packages/core/src/shared/unstable_error.ts diff --git a/packages/core/deno.json b/packages/core/deno.json index 17eca9dd..9d7739e2 100644 --- a/packages/core/deno.json +++ b/packages/core/deno.json @@ -31,7 +31,7 @@ }, "imports": { "@/": "./src/", - "@aura-stack/router": "npm:@aura-stack/router@^0.7.2", + "@aura-stack/router": "npm:@aura-stack/router@^0.8.0", "zod": "npm:zod@4.3.5", "arktype": "npm:arktype@2.2.0", "valibot": "npm:valibot@1.4.0", diff --git a/packages/core/src/actions/callback/access-token.ts b/packages/core/src/actions/callback/access-token.ts index 7bbb1681..be289496 100644 --- a/packages/core/src/actions/callback/access-token.ts +++ b/packages/core/src/actions/callback/access-token.ts @@ -1,5 +1,5 @@ import { fetchAsync } from "@/shared/fetch-async.ts" -import { AuthInternalError, OAuthProtocolError } from "@/shared/errors.ts" +import { AuraAuthError, isAuraAuthError } from "@/shared/unstable_error.ts" import { OAuthAccessTokenErrorResponse, OAuthAccessTokenResponse } from "@/schemas.ts" import type { InternalLogger, OAuthProviderCredentials } from "@/@types/index.ts" @@ -33,7 +33,7 @@ export const createAccessToken = async ( has_code_verifier: Boolean(codeVerifier), }, }) - throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "The OAuth provider configuration is invalid.") + throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_URL_CONFIG" }) } const tokenURL = typeof accessToken === "string" ? accessToken : accessToken.url @@ -65,7 +65,7 @@ export const createAccessToken = async ( }) if (!response.ok) { logger?.log("INVALID_OAUTH_ACCESS_TOKEN_RESPONSE") - throw new OAuthProtocolError("invalid_request", "Invalid access token response") + throw new AuraAuthError({ code: "INVALID_OAUTH_ACCESS_TOKEN_RESPONSE" }) } const json = await response.json() const token = OAuthAccessTokenResponse.safeParse(json) @@ -73,7 +73,7 @@ export const createAccessToken = async ( const { success, data } = OAuthAccessTokenErrorResponse.safeParse(json) if (!success) { logger?.log("INVALID_OAUTH_ACCESS_TOKEN_RESPONSE") - throw new OAuthProtocolError("invalid_request", "Invalid access token response format") + throw new AuraAuthError({ code: "INVALID_OAUTH_ACCESS_TOKEN_RES_FORMAT" }) } logger?.log("OAUTH_ACCESS_TOKEN_ERROR", { structuredData: { @@ -81,16 +81,16 @@ export const createAccessToken = async ( error_description: data.error_description ?? "", }, }) - throw new OAuthProtocolError("INVALID_ACCESS_TOKEN", "Failed to retrieve access token") + throw new AuraAuthError({ code: "UNKNOWN_OAUTH_ACCESS_TOKEN_ERROR" }) } logger?.log("OAUTH_ACCESS_TOKEN_SUCCESS") return token.data } catch (error) { - if (error instanceof OAuthProtocolError) { + if (isAuraAuthError(error)) { throw error } logger?.log("OAUTH_ACCESS_TOKEN_REQUEST_FAILED") - throw new OAuthProtocolError("server_error", "Failed to communicate with OAuth provider", "", { cause: error }) + throw new AuraAuthError({ code: "UNKNOWN_OAUTH_ACCESS_TOKEN_ERROR", cause: error }) } } diff --git a/packages/core/src/actions/callback/callback.ts b/packages/core/src/actions/callback/callback.ts index dcb9b2a4..cda247e2 100644 --- a/packages/core/src/actions/callback/callback.ts +++ b/packages/core/src/actions/callback/callback.ts @@ -3,11 +3,11 @@ import { createEndpoint, createEndpointConfig, HeadersBuilder } from "@aura-stac import { createCSRF } from "@/shared/crypto.ts" import { cacheControl } from "@/shared/headers.ts" import { timingSafeEqual } from "@/shared/utils.ts" +import { getCookie, getExpiredCookie } from "@/cookie.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { getUserInfo } from "@/actions/callback/userinfo.ts" import { OAuthAuthorizationErrorResponse } from "@/schemas.ts" -import { getCookie, getExpiredCookie } from "@/cookie.ts" import { createAccessToken } from "@/actions/callback/access-token.ts" -import { AuthSecurityError, OAuthProtocolError } from "@/shared/errors.ts" import { isRelativeURL, isSameOrigin, isTrustedOrigin } from "@/shared/assert.ts" import { getOriginURL, getTrustedOrigins } from "@/actions/signIn/authorization.ts" import type { OAuthProviderRecord } from "@/@types/index.ts" @@ -50,7 +50,7 @@ const callbackConfig = (oauth: OAuthProviderRecord) => { error_description: error_description ?? "", }, }) - throw new OAuthProtocolError(error, error_description || "OAuth Authorization Error") + throw new AuraAuthError({ code: "AUTH_CALLBACK_MISSING_PARAMETERS" }) } return ctx }, @@ -91,9 +91,9 @@ export const callbackAction = (oauth: OAuthProviderRecord) => { }) return Response.json( { - type: "AUTH_SECURITY_ERROR", - code: "MISMATCHING_STATE", - message: "The provided state passed in the OAuth response does not match the stored state.", + type: "PROTOCOL", + code: "AUTH_MISMATCHING_STATE", + message: "The provided state passed in the OAuth response does not match the stored token state.", }, { headers: clearCookieHeaders.toHeaders(), status: 400 } ) @@ -117,10 +117,7 @@ export const callbackAction = (oauth: OAuthProviderRecord) => { request_origin: requestOrigin, }, }) - throw new AuthSecurityError( - "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", - "Invalid redirect path. Potential open redirect attack detected." - ) + throw new AuraAuthError({ code: "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED" }) } } diff --git a/packages/core/src/actions/callback/userinfo.ts b/packages/core/src/actions/callback/userinfo.ts index c7148d79..5438e433 100644 --- a/packages/core/src/actions/callback/userinfo.ts +++ b/packages/core/src/actions/callback/userinfo.ts @@ -1,7 +1,6 @@ import { fetchAsync } from "@/shared/fetch-async.ts" import { AURA_AUTH_VERSION } from "@/shared/utils.ts" import { OAuthErrorResponse } from "@/schemas.ts" -import { isNativeError, isOAuthProtocolError, OAuthProtocolError } from "@/shared/errors.ts" import type { AccessTokenContext, InternalLogger, @@ -10,6 +9,7 @@ import type { User, } from "@/@types/index.ts" import { isCustomUserInfoFunction } from "@/shared/assert.ts" +import { AuraAuthError, isAuraAuthError } from "@/shared/unstable_error.ts" /** * Map the default user information fields from the OAuth provider's userinfo response @@ -20,7 +20,7 @@ import { isCustomUserInfoFunction } from "@/shared/assert.ts" const getDefaultUserInfo = (profile: Record): User => { const sub = profile?.id ?? profile?.sub ?? profile?.uid ?? profile?.user_id ?? profile?.account_id if (!sub) { - throw new OAuthProtocolError("invalid_userinfo", "OAuth provider did not return a stable user identifier (id/sub/uid).") + throw new AuraAuthError({ code: "INVALID_USER_INFO" }) } return { @@ -58,7 +58,7 @@ const createUserInfoRequest = async (oauthConfig: ProviderConfig, accessToken: s }) if (!response.ok) { logger?.log("OAUTH_USERINFO_INVALID_RESPONSE") - throw new OAuthProtocolError("INVALID_REQUEST", "Invalid userinfo response format") + throw new AuraAuthError({ code: "INVALID_OAUTH_USER_INFO_RESPONSE" }) } const json = await response.json() @@ -71,22 +71,17 @@ const createUserInfoRequest = async (oauthConfig: ProviderConfig, accessToken: s error_description: data.error_description ?? "", }, }) - throw new OAuthProtocolError("INVALID_REQUEST", "An error was received from the OAuth userinfo endpoint.") + throw new AuraAuthError({ code: "INVALID_OAUTH_USER_INFO_RES_FORMAT" }) } logger?.log("OAUTH_USERINFO_SUCCESS") return json } catch (error) { - if (isOAuthProtocolError(error)) { + if (isAuraAuthError(error)) { throw error } logger?.log("OAUTH_USERINFO_REQUEST_FAILED") - if (isNativeError(error)) { - throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information from OAuth provider", "", { - cause: error, - }) - } - throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information", "", { cause: error }) + throw new AuraAuthError({ code: "UNKNOWN_OAUTH_USER_INFO_ERROR", cause: error }) } } @@ -127,15 +122,10 @@ export const getUserInfo = async ( const userInfo = oauthConfig?.profile ? oauthConfig.profile(userProfile) : getDefaultUserInfo(userProfile) return userInfo } catch (error) { - if (isOAuthProtocolError(error)) { + if (isAuraAuthError(error)) { throw error } logger?.log("OAUTH_USERINFO_REQUEST_FAILED") - if (isNativeError(error)) { - throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information from OAuth provider", "", { - cause: error, - }) - } - throw new OAuthProtocolError("SERVER_ERROR", "Failed to fetch user information", "", { cause: error }) + throw new AuraAuthError({ code: "UNKNOWN_CUSTOM_USER_INFO_ERROR", cause: error }) } } diff --git a/packages/core/src/actions/signIn/authorization-url.ts b/packages/core/src/actions/signIn/authorization-url.ts index 03f8c583..92294fea 100644 --- a/packages/core/src/actions/signIn/authorization-url.ts +++ b/packages/core/src/actions/signIn/authorization-url.ts @@ -1,5 +1,5 @@ -import { AuthInternalError } from "@/shared/errors.ts" import { OAuthAuthorization } from "@/schemas.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { createPKCE, createSecretValue } from "@/shared/crypto.ts" import type { OAuthProvider } from "@/@types/index.ts" import type { GlobalContext } from "@aura-stack/router" @@ -22,7 +22,7 @@ export const buildAuthorizationURL = ( const authorizeConfig = oauth.authorize const baseURL = typeof authorizeConfig === "string" ? authorizeConfig : (authorizeConfig?.url ?? oauth.authorizeURL) if (!baseURL) { - throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "Missing authorization URL in OAuth provider configuration.") + throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_URL_CONFIG" }) } const url = new URL(baseURL) const authorizeParams = typeof authorizeConfig === "string" ? undefined : authorizeConfig?.params @@ -69,7 +69,7 @@ export const createAuthorizationURL = async (oauth: OAuthProvider, redirectURI: code_challenge_method: method, }, }) - throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "The OAuth provider configuration is invalid.") + throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_SCHEMA_CONFIG" }) } return { diff --git a/packages/core/src/actions/signIn/authorization.ts b/packages/core/src/actions/signIn/authorization.ts index 704544fe..1caf98a6 100644 --- a/packages/core/src/actions/signIn/authorization.ts +++ b/packages/core/src/actions/signIn/authorization.ts @@ -1,10 +1,10 @@ import { getEnv } from "@/shared/env.ts" -import { AuthInternalError } from "@/shared/errors.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { equals, extractPath, patternToRegex } from "@/shared/utils.ts" import { isRelativeURL, isSameOrigin, isValidURL, isTrustedOrigin } from "@/shared/assert.ts" import type { AuthConfig } from "@/@types/index.ts" -import type { Identities, SchemaTypes } from "@/shared/identity.ts" import type { GlobalContext } from "@aura-stack/router" +import type { Identities, SchemaTypes } from "@/shared/identity.ts" /** * Resolves trusted origins from config (array or function). @@ -38,19 +38,12 @@ export const getBaseURL = async ({ headers?.get("X-Forwarded-Host") ?? null if (host) return `${protocol}://${host}` - throw new AuthInternalError( - "INVALID_OAUTH_CONFIGURATION", - "The URL cannot be constructed. Please set the BASE_URL environment variable or provide trusted proxy host headers." - ) + throw new AuraAuthError({ code: "INVALID_AUTH_CONFIGURATION" }) } try { return new URL(request?.url ?? "not-found").origin - } catch (error) { - throw new AuthInternalError( - "INVALID_OAUTH_CONFIGURATION", - "The URL cannot be constructed. Please set the BASE_URL environment variable or enable trustedProxyHeaders.", - { cause: error } - ) + } catch (cause) { + throw new AuraAuthError({ code: "INVALID_AUTH_CONFIGURATION", cause }) } } @@ -60,7 +53,7 @@ export const getOriginURL = async (request: Request, context?: GlobalContext) => const origin = await getBaseURL({ request, ctx: context }) if (!isTrustedOrigin(origin, trustedOrigins)) { context?.logger?.log("UNTRUSTED_ORIGIN", { structuredData: { origin: origin } }) - throw new AuthInternalError("UNTRUSTED_ORIGIN", "The constructed origin URL is not trusted.") + throw new AuraAuthError({ code: "INVALID_TRUSTED_ORIGIN" }) } return origin } diff --git a/packages/core/src/actions/signUp/signUp.ts b/packages/core/src/actions/signUp/signUp.ts index ec11cd76..70f76e7b 100644 --- a/packages/core/src/actions/signUp/signUp.ts +++ b/packages/core/src/actions/signUp/signUp.ts @@ -7,7 +7,7 @@ import type { SignUpConfig } from "@/@types/config.ts" const signUpConfig = (config: SignUpConfig) => { return createEndpointConfig({ schemas: { - body: config?.schema ?? z.object({}), + body: config?.schema ?? z.object(), searchParams: RedirectOptionsSchema, }, }) diff --git a/packages/core/src/api/credentials.ts b/packages/core/src/api/credentials.ts index 01bc5c95..d422d981 100644 --- a/packages/core/src/api/credentials.ts +++ b/packages/core/src/api/credentials.ts @@ -1,6 +1,6 @@ import { HeadersBuilder } from "@aura-stack/router" import { secureApiHeaders } from "@/shared/headers.ts" -import { AuthValidationError, isAuthErrorWithCode } from "@/shared/errors.ts" +import { AuraAuthError, isAuraAuthError } from "@/shared/unstable_error.ts" import { createCSRF, hashPassword, verifyPassword } from "@/shared/crypto.ts" import { createRedirectTo, getBaseURL, getOriginURL } from "@/actions/signIn/authorization.ts" import type { FunctionAPIContext, SignInCredentialsAPIOptions, SignInCredentialsAPIReturn } from "@/@types/api.ts" @@ -29,7 +29,7 @@ export const signInCredentials = async ({ verifySecret: credentials?.verify ?? verifyPassword, }) if (!session) { - throw new AuthValidationError("INVALID_CREDENTIALS", "The provided credentials are invalid.") + throw new AuraAuthError({ code: "AUTH_CREDENTIALS_INVALID" }) } const sessionToken = await sessionStrategy.createSession(session) const csrfToken = await createCSRF(ctx.jose) @@ -62,7 +62,7 @@ export const signInCredentials = async ({ } catch (error) { let code = "CREDENTIALS_SIGN_IN_ERROR" let message = "An error occurred during credentials sign-in." - if (isAuthErrorWithCode(error)) { + if (isAuraAuthError(error)) { code = error.code message = error.message } @@ -77,7 +77,7 @@ export const signInCredentials = async ({ return Response.json({ success: false, redirect: false, redirectURL: null }, { headers, status: 401 }) }, } - if (error instanceof AuthValidationError) { + if (isAuraAuthError(error)) { logger?.log("INVALID_CREDENTIALS", { severity: "warning", structuredData: { path: "/signIn/credentials" }, diff --git a/packages/core/src/api/signIn.ts b/packages/core/src/api/signIn.ts index 1a940bf2..0918afaf 100644 --- a/packages/core/src/api/signIn.ts +++ b/packages/core/src/api/signIn.ts @@ -1,6 +1,6 @@ import { cacheControl, secureApiHeaders } from "@/shared/headers.ts" import { HeadersBuilder } from "@aura-stack/router" -import { AuthInternalError, isAuthErrorWithCode } from "@/shared/errors.ts" +import { AuraAuthError, isAuraAuthError } from "@/shared/unstable_error.ts" import { createAuthorizationURL } from "@/actions/signIn/authorization-url.ts" import { createRedirectTo, createRedirectURI, createSignInURL, getBaseURL } from "@/actions/signIn/authorization.ts" import type { BuiltInOAuthProvider, FunctionAPIContext, LiteralUnion, SignInAPIOptions, SignInAPIReturn } from "@/@types/index.ts" @@ -16,7 +16,7 @@ export const signIn = async ( const headers = new Headers(headersInit) const provider = ctx.oauth[oauth] if (!provider) { - throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", `The OAuth provider "${oauth}" is not configured.`) + throw new AuraAuthError({ code: "UNSUPPORTED_OAUTH_CONFIGURATION" }) } let request = requestInit @@ -74,7 +74,7 @@ export const signIn = async ( } catch (error) { let code = "AUTH_SIGN_IN_FAILED" let message = "An error occurred during the sign-in process." - if (isAuthErrorWithCode(error)) { + if (isAuraAuthError(error)) { code = error.code message = error.message } diff --git a/packages/core/src/api/signOut.ts b/packages/core/src/api/signOut.ts index 696a6000..cf7d5bf6 100644 --- a/packages/core/src/api/signOut.ts +++ b/packages/core/src/api/signOut.ts @@ -1,5 +1,5 @@ import { HeadersBuilder } from "@aura-stack/router" -import { isAuthErrorWithCode } from "@/shared/errors.ts" +import { isAuraAuthError } from "@/shared/unstable_error.ts" import { createRedirectTo, getBaseURL, getOriginURL } from "@/actions/signIn/authorization.ts" import type { FunctionAPIContext, SignOutAPIOptions, SignOutAPIReturn } from "@/@types/index.ts" @@ -46,7 +46,7 @@ export const signOut = async ({ } catch (error) { let code = "SIGN_OUT_FAILED" let message = "Failed to sign-out session" - if (isAuthErrorWithCode(error)) { + if (isAuraAuthError(error)) { code = error.code message = error.message } diff --git a/packages/core/src/api/signUp.ts b/packages/core/src/api/signUp.ts index 26b03592..5af4eb95 100644 --- a/packages/core/src/api/signUp.ts +++ b/packages/core/src/api/signUp.ts @@ -1,9 +1,9 @@ -import { createRedirectTo, getBaseURL, getOriginURL } from "@/actions/signIn/authorization.ts" -import type { FunctionAPIContext, SignUpAPIOptions, SignUpAPIReturn } from "@/@types/api.ts" -import { AuthValidationError, isAuthErrorWithCode } from "@/shared/errors.ts" import { createCSRF } from "@/shared/crypto.ts" import { HeadersBuilder } from "@aura-stack/router" import { secureApiHeaders } from "@/shared/headers.ts" +import { AuraAuthError, isAuraAuthError } from "@/shared/unstable_error.ts" +import { createRedirectTo, getBaseURL, getOriginURL } from "@/actions/signIn/authorization.ts" +import type { FunctionAPIContext, SignUpAPIOptions, SignUpAPIReturn } from "@/@types/api.ts" export const signUp = async = Record>({ ctx, @@ -26,7 +26,7 @@ export const signUp = async = Record = Record({ skipCSRFCheck ) if (!session) { - throw new AuthInternalError("UPDATE_SESSION_INVALID", "Failed to update session.") + throw new AuraAuthError({ code: "UPDATE_SESSION_INVALID" }) } const newHeaders = toUnionHeaders(headers, secureApiHeaders) @@ -63,7 +63,7 @@ export const updateSession = async ({ } catch (error) { let code = "UPDATE_SESSION_INVALID" let message = "Failed to update session." - if (isAuthErrorWithCode(error)) { + if (isAuraAuthError(error)) { code = error.code message = error.message } diff --git a/packages/core/src/client/client.ts b/packages/core/src/client/client.ts index 41dc1cfd..b9785e97 100644 --- a/packages/core/src/client/client.ts +++ b/packages/core/src/client/client.ts @@ -1,4 +1,3 @@ -import { AuthClientError } from "@/shared/errors.ts" import { createClient as createClientAPI } from "@aura-stack/router" import type { Session, @@ -18,6 +17,7 @@ import type { SignUpOptions, SignUpReturn, } from "@/@types/index.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" export type { AuthClientOptions } @@ -30,7 +30,7 @@ export const createAuthClient = < options: AuthClientOptions ) => { if (typeof window === "undefined" && !options.baseURL) { - throw new AuthClientError("`baseURL` is required when createAuthClient is used outside the browser.") + throw new AuraAuthError({ code: "CLIENT_BASE_URL_MISSING" }) } const client = createClient({ @@ -213,7 +213,7 @@ export const createAuthClient = < try { const csrfToken = await getCSRFToken() if (!csrfToken) { - throw new AuthClientError("Failed to fetch CSRF token for session update.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISSING" }) } const { session, redirectTo } = options ?? {} if (!session) { @@ -263,7 +263,7 @@ export const createAuthClient = < try { const csrfToken = await getCSRFToken() if (!csrfToken) { - throw new AuthClientError("Failed to fetch CSRF token for sign-out.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISSING" }) } // @ts-ignore - Fix type here - go to @aura-stack/router. diff --git a/packages/core/src/cookie.ts b/packages/core/src/cookie.ts index a0d7be5f..3a3cf8a3 100644 --- a/packages/core/src/cookie.ts +++ b/packages/core/src/cookie.ts @@ -1,5 +1,5 @@ import { env } from "@/shared/env.ts" -import { AuthInternalError } from "@/shared/errors.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { parse, parseSetCookie, serialize, type SerializeOptions } from "@aura-stack/router/cookie" import type { CookieStoreConfig, CookieConfig, InternalLogger } from "@/@types/index.ts" @@ -83,11 +83,11 @@ export const getExpiredCookie = (options?: SerializeOptions) => { export const getCookie = (request: Request | Headers, cookieName: string) => { const cookies = request instanceof Request ? request.headers.get("Cookie") : request.get("Cookie") if (!cookies) { - throw new AuthInternalError("COOKIE_NOT_FOUND", "No cookies found. There is no active session") + throw new AuraAuthError({ code: "COOKIE_NOT_FOUND" }) } const value = parse(cookies)[cookieName] if (!value) { - throw new AuthInternalError("COOKIE_NOT_FOUND", `Cookie "${cookieName}" not found. There is no active session`) + throw new AuraAuthError({ code: "COOKIE_INVALID_VALUE" }) } return value } @@ -102,11 +102,11 @@ export const getCookie = (request: Request | Headers, cookieName: string) => { export const getSetCookie = (response: Response | Headers, cookieName: string) => { const cookies = response instanceof Response ? response.headers.getSetCookie() : response.getSetCookie() if (!cookies) { - throw new AuthInternalError("COOKIE_NOT_FOUND", "No cookies found in response.") + throw new AuraAuthError({ code: "SET_COOKIE_NOT_FOUND" }) } const strCookie = cookies.find((cookie) => cookie.startsWith(`${cookieName}=`)) if (!strCookie) { - throw new AuthInternalError("COOKIE_NOT_FOUND", `Cookie "${cookieName}" not found in response.`) + throw new AuraAuthError({ code: "SET_COOKIE_INVALID_VALUE" }) } return parseSetCookie(strCookie).value } diff --git a/packages/core/src/jose.ts b/packages/core/src/jose.ts index 8bdb5161..c78e5b97 100644 --- a/packages/core/src/jose.ts +++ b/packages/core/src/jose.ts @@ -14,7 +14,6 @@ import { type JWTDecryptOptions, } from "@aura-stack/jose" export { base64url, type JWTPayload } from "@aura-stack/jose/jose" -import { AuthInternalError, AuthJoseInitializationError, AuthSecurityError } from "@/shared/errors.ts" import { isCryptoKey, isCryptoKeyPair, @@ -29,6 +28,7 @@ import { import { importPEMKeyPair } from "@/shared/crypto.ts" export { encoder, getRandomBytes, getSubtleCrypto } from "@aura-stack/jose/crypto" import type { User, SessionConfig, JWTKey, AsymmetricKeyPairFromEnv } from "@/@types/index.ts" +import { AuraAuthError } from "./shared/unstable_error.ts" const getJWTConfig = (config?: SessionConfig) => { return config?.jwt @@ -109,7 +109,7 @@ export const getDecryptOptions = (config?: SessionConfig, options?: JWTDecryptOp export const verifyMaxExpiration = (payload: TypedJWTPayload>) => { const now = Math.floor(Date.now() / 1000) if (payload.mexp && typeof payload.mexp === "number" && now > payload.mexp) { - throw new AuthSecurityError("TOKEN_EXPIRED", "The token has expired based on its maxExpiration (mexp) claim.") + throw new AuraAuthError({ code: "JWT_EXPIRED" }) } } @@ -120,10 +120,7 @@ const getSecrets = async ( ) => { if (isJWTPEMFormattedKeyPair(secret)) { if (!isSealedMode(session)) { - throw new AuthJoseInitializationError( - "INVALID_PEM_KEY_PAIR", - "Multiples PEM Key Pairs from environment variables require 'sealed' JWT mode. For 'signed' or 'encrypted' modes, provide a single PEM key pair or a combined key object." - ) + throw new AuraAuthError({ code: "INVALID_PEM_KEY_PAIR_MODE_MISMATCH" }) } const { sign, encrypt } = secret @@ -144,10 +141,7 @@ const getSecrets = async ( } if (isPEMFormattedKeyPairFromEnv(secret)) { if (isSealedMode(session)) { - throw new AuthJoseInitializationError( - "INVALID_PEM_KEY_PAIR", - "Single PEM key pairs from environment variables require 'signed' or 'encrypted' JWT mode. For 'sealed' mode, provide separate signing and encryption keys or a combined key object." - ) + throw new AuraAuthError({ code: "INVALID_PEM_KEY_PAIR_SINGLE_MISMATCH" }) } const algorithm = getEnv("ALGORITHM") || @@ -237,10 +231,7 @@ const getSecretKey = (secret?: JWTKey) => { encrypt: encryption, } } - throw new AuthInternalError( - "JOSE_INITIALIZATION_FAILED", - "AURA_AUTH_SECRET environment variable is not set and no secret was provided." - ) + throw new AuraAuthError({ code: "JOSE_INITIALIZATION_SECRET_MISSING" }) } /** @@ -261,19 +252,12 @@ export const createJoseInstance = (secret?: JWT const secretKey = getSecretKey(secret) const salt = getEnv("SALT") if (!salt) { - throw new AuthInternalError( - "JOSE_INITIALIZATION_FAILED", - "AURA_AUTH_SALT or AUTH_SALT environment variable is not set. A salt value is required for key derivation." - ) + throw new AuraAuthError({ code: "JOSE_INITIALIZATION_SALT_MISSING" }) } try { createSecret(salt) - } catch (error) { - throw new AuthInternalError( - "INVALID_SALT_SECRET_VALUE", - "AURA_AUTH_SALT/AUTH_SALT is invalid. It must be at least 32 bytes long and meet entropy requirements.", - { cause: error } - ) + } catch (cause) { + throw new AuraAuthError({ code: "INVALID_SALT_SECRET_VALUE", cause }) } const jose = (async () => { diff --git a/packages/core/src/oauth/index.ts b/packages/core/src/oauth/index.ts index 1100d14b..1bde3ad7 100644 --- a/packages/core/src/oauth/index.ts +++ b/packages/core/src/oauth/index.ts @@ -22,9 +22,8 @@ import { atlassian } from "./atlassian.ts" import { clickUp } from "./click-up.ts" import { dribbble } from "./dribbble.ts" import { hubspot } from "./hubspot.ts" -import { formatZodError } from "@/shared/utils.ts" -import { AuthInternalError } from "@/shared/errors.ts" import { OAuthEnvSchema, OAuthProviderCredentialsSchema } from "@/schemas.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" export * from "./github.ts" export * from "./bitbucket.ts" @@ -81,8 +80,7 @@ const defineOAuthEnvironment = (oauth: string) => { clientSecret: getEnv(`${oauth.replace("-", "_").toUpperCase()}_CLIENT_SECRET`), }) if (!loadEnvs.success) { - const msg = JSON.stringify({ [oauth]: formatZodError(loadEnvs.error) }, null, 2) - throw new AuthInternalError("INVALID_ENVIRONMENT_CONFIGURATION", msg) + throw new AuraAuthError({ code: "INVALID_ENVIRONMENT_CONFIGURATION", cause: loadEnvs.error }) } return loadEnvs.data } @@ -93,11 +91,7 @@ const defineOAuthProviderConfig = (config: BuiltInOAuthProvider | OAuthProviderC const oauthConfig = builtInOAuthProviders[config]() const parsed = OAuthProviderCredentialsSchema.safeParse({ ...oauthConfig, ...definition }) if (!parsed.success) { - const details = JSON.stringify({ [config]: formatZodError(parsed.error) }, null, 2) - throw new AuthInternalError( - "INVALID_OAUTH_PROVIDER_CONFIGURATION", - `Invalid configuration for OAuth provider "${config}": ${details}` - ) + throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_SCHEMA_CONFIG", cause: parsed.error }) } return parsed.data } @@ -105,11 +99,7 @@ const defineOAuthProviderConfig = (config: BuiltInOAuthProvider | OAuthProviderC const envConfig = hasCredentials ? {} : defineOAuthEnvironment(config.id) const parsed = OAuthProviderCredentialsSchema.safeParse({ ...envConfig, ...config }) if (!parsed.success) { - const details = JSON.stringify({ [config.id]: formatZodError(parsed.error) }, null, 2) - throw new AuthInternalError( - "INVALID_OAUTH_PROVIDER_CONFIGURATION", - `Invalid configuration for OAuth provider "${config.id}": ${details}` - ) + throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_SCHEMA_CONFIG", cause: parsed.error }) } return parsed.data } @@ -131,10 +121,10 @@ export const createBuiltInOAuthProviders = (oauth: (BuiltInOAuthProvider | OAuth return oauth.reduce((previous, config) => { const oauthConfig = defineOAuthProviderConfig(config) if (oauthConfig.id in previous) { - throw new AuthInternalError( - "DUPLICATED_OAUTH_PROVIDER_ID", - `Duplicate OAuth provider id "${oauthConfig.id}" found. Each provider must have a unique id.` - ) + throw new AuraAuthError({ + code: "DUPLICATED_OAUTH_PROVIDER_ID", + cause: new Error(`Duplicate OAuth provider id "${oauthConfig.id}" found. Each provider must have a unique id.`), + }) } return { ...previous, [oauthConfig.id]: oauthConfig } }, {}) as Record, OAuthProviderCredentials> diff --git a/packages/core/src/router/errorHandler.ts b/packages/core/src/router/errorHandler.ts index 6d130e64..f21e75c5 100644 --- a/packages/core/src/router/errorHandler.ts +++ b/packages/core/src/router/errorHandler.ts @@ -1,5 +1,5 @@ +import { isAuraAuthError } from "@/shared/unstable_error.ts" import { isInvalidZodSchemaError, isRouterError, type RouterConfig } from "@aura-stack/router" -import { isAuthInternalError, isAuthSecurityError, isAuthValidationError, isOAuthProtocolError } from "@/shared/errors.ts" import type { InternalLogger } from "@/@types/index.ts" export const createErrorHandler = (logger?: InternalLogger): RouterConfig["onError"] => { @@ -13,60 +13,8 @@ export const createErrorHandler = (logger?: InternalLogger): RouterConfig["onErr logger?.log("INVALID_REQUEST") return Response.json({ type: "ROUTER_ERROR", code: "INVALID_REQUEST", message: error.errors }, { status: 422 }) } - if (isOAuthProtocolError(error)) { - const { error: errorCode, message, type, errorURI } = error - logger?.log("OAUTH_PROTOCOL_ERROR", { - structuredData: { - error: errorCode, - error_description: message, - error_uri: errorURI ?? "", - }, - }) - return Response.json( - { - type, - message, - }, - { status: 400 } - ) - } - if (isAuthInternalError(error)) { - const { type, code, message } = error - logger?.log("INVALID_OAUTH_CONFIGURATION", { - structuredData: { - error: code, - error_description: message, - }, - }) - return Response.json( - { - type, - message, - }, - { status: 400 } - ) - } - if (isAuthSecurityError(error)) { - const { type, code, message } = error - logger?.log("AUTH_SECURITY_ERROR", { - structuredData: { - error: code, - error_description: message, - }, - }) - return Response.json( - { - type, - code, - message, - }, - { status: 400 } - ) - } - if (isAuthValidationError(error)) { - const { type, code, message } = error - logger?.log("IDENTITY_VALIDATION_FAILED", { structuredData: { error: code, error_description: message } }) - return Response.json({ type, code, message }, { status: 422 }) + if (isAuraAuthError(error)) { + return error.toResponse() } logger?.log("SERVER_ERROR", { structuredData: { error_type: error.name, error_message: error.message } }) return Response.json( diff --git a/packages/core/src/session/jose-manager.ts b/packages/core/src/session/jose-manager.ts index 1139be12..5a663340 100644 --- a/packages/core/src/session/jose-manager.ts +++ b/packages/core/src/session/jose-manager.ts @@ -1,4 +1,4 @@ -import { AuthInvalidConfigurationError } from "@/shared/errors.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import type { JoseInstance, User, JWTConfig, JWTManager } from "@/@types/index.ts" export const createJoseManager = ( @@ -8,9 +8,7 @@ export const createJoseManager = ( const mode = config?.mode ?? "sealed" if (!["sealed", "signed", "encrypted"].includes(mode)) { - throw new AuthInvalidConfigurationError( - `[auth] invalid JWT mode "${mode}". Valid options are: "sealed", "signed", "encrypted".` - ) + throw new AuraAuthError({ code: "JWT_INVALID_MODE" }) } return { diff --git a/packages/core/src/session/stateless.ts b/packages/core/src/session/stateless.ts index 27f96e17..178037b4 100644 --- a/packages/core/src/session/stateless.ts +++ b/packages/core/src/session/stateless.ts @@ -1,7 +1,6 @@ import { getCookie } from "@/cookie.ts" import { verifyCSRF } from "@/shared/crypto.ts" import { getErrorName } from "@/shared/utils.ts" -import { AuthSecurityError } from "@/shared/errors.ts" import { createJoseManager } from "@/session/jose-manager.ts" import { createCookieManager } from "@/session/cookie-manager.ts" import type { @@ -13,6 +12,7 @@ import type { GetStatelessSessionReturn, DeepPartial, } from "@/@types/index.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" export const createStatelessStrategy = ({ config, @@ -53,13 +53,15 @@ export const createStatelessStrategy = ({ const header = headers.get("X-CSRF-Token") try { session = getCookie(headers, cookies().sessionToken.name) - } catch { - throw new AuthSecurityError("SESSION_TOKEN_MISSING", "The sessionToken is missing.") + } catch (cause) { + //throw new AuthSecurityError("SESSION_TOKEN_MISSING", "The sessionToken is missing.") + throw new AuraAuthError({ code: "SESSION_NOT_FOUND", cause }) } try { csrfToken = getCookie(headers, cookies().csrfToken.name) - } catch { - throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF token is missing.") + } catch (cause) { + //throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF token is missing.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISSING", cause }) } logger?.log("CSRF_TOKEN_REQUESTED", { structuredData: { @@ -71,22 +73,26 @@ export const createStatelessStrategy = ({ }) if (!session) { logger?.log("SESSION_TOKEN_MISSING") - throw new AuthSecurityError("SESSION_TOKEN_MISSING", "The sessionToken is missing.") + //throw new AuthSecurityError("SESSION_TOKEN_MISSING", "The sessionToken is missing.") + throw new AuraAuthError({ code: "SESSION_NOT_FOUND" }) } if (!skipCSRFCheck) { if (!csrfToken) { logger?.log("CSRF_TOKEN_MISSING") - throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF token is missing.") + //throw new AuthSecurityError("CSRF_TOKEN_MISSING", "The CSRF token is missing.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISSING" }) } if (!header) { logger?.log("CSRF_HEADER_MISSING") - throw new AuthSecurityError("CSRF_HEADER_MISSING", "The CSRF header is missing.") + //throw new AuthSecurityError("CSRF_HEADER_MISSING", "The CSRF header is missing.") + throw new AuraAuthError({ code: "CSRF_DOUBLE_SUBMIT_FAILED" }) } try { await verifyCSRF(jose, csrfToken, header) } catch (error) { logger?.log("CSRF_TOKEN_INVALID", { structuredData: { error_type: getErrorName(error) } }) - throw new AuthSecurityError("CSRF_TOKEN_INVALID", "CSRF token verification failed") + //throw new AuthSecurityError("CSRF_TOKEN_INVALID", "CSRF token verification failed") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISMATCH" }) } logger?.log("CSRF_TOKEN_VERIFIED") } diff --git a/packages/core/src/session/strategy.ts b/packages/core/src/session/strategy.ts index 6e20061d..e3edad9e 100644 --- a/packages/core/src/session/strategy.ts +++ b/packages/core/src/session/strategy.ts @@ -1,8 +1,8 @@ -import { AuthInvalidConfigurationError } from "@/shared/errors.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { createStatelessStrategy } from "@/session/stateless.ts" -import type { CreateSessionStrategyOptions, SessionStrategy, User } from "@/@types/session.ts" -import type { FromShapeToObject } from "@/@types/utility.ts" import type { Identities } from "@/shared/identity.ts" +import type { FromShapeToObject } from "@/@types/utility.ts" +import type { CreateSessionStrategyOptions, SessionStrategy, User } from "@/@types/session.ts" export const createSessionStrategy = ({ config, @@ -23,6 +23,6 @@ export const createSessionStrategy = ({ identity, }) default: - throw new AuthInvalidConfigurationError(`[auth] unknown session strategy "${strategy}". Valid options are: "jwt".`) + throw new AuraAuthError({ code: "INVALID_SESSION_STRATEGY" }) } } diff --git a/packages/core/src/shared/crypto.ts b/packages/core/src/shared/crypto.ts index d576c466..9fbcadf2 100644 --- a/packages/core/src/shared/crypto.ts +++ b/packages/core/src/shared/crypto.ts @@ -1,6 +1,6 @@ -import { AuthSecurityError } from "@/shared/errors.ts" import { isJWTPayloadWithToken } from "@/shared/assert.ts" import { equals, timingSafeEqual } from "@/shared/utils.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { exportJWK, generateKeyPair, importPKCS8, importSPKI, type GenerateKeyPairOptions } from "@aura-stack/jose/jose" import { base64url, encoder, getRandomBytes, getSubtleCrypto } from "@/jose.ts" import type { AsymmetricKeyPairFromEnv, AuthRuntimeConfig, JoseInstance, User } from "@/@types/index.ts" @@ -31,7 +31,7 @@ export const createPKCE = async (verifier?: string) => { const byteLength = verifier ? undefined : Math.floor(Math.random() * (96 - 32 + 1) + 32) const codeVerifier = verifier ?? createSecretValue(byteLength ?? 64) if (codeVerifier.length < 43 || codeVerifier.length > 128) { - throw new AuthSecurityError("PKCE_VERIFIER_INVALID", "The code verifier must be between 43 and 128 characters in length.") + throw new AuraAuthError({ code: "PKCE_VERIFIER_INVALID" }) } const codeChallenge = await createHash(codeVerifier) return { codeVerifier, codeChallenge, method: "S256" } @@ -67,21 +67,21 @@ export const verifyCSRF = async ( const headerPayload = await jose.verifyJWS(header) if (!isJWTPayloadWithToken(cookiePayload)) { - throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Cookie payload missing token field.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISSING" }) } if (!isJWTPayloadWithToken(headerPayload)) { - throw new AuthSecurityError("CSRF_TOKEN_INVALID", "Header payload missing token field.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISSING" }) } if (!equals(cookiePayload.token.length, headerPayload.token.length)) { - throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISMATCH" }) } if (!timingSafeEqual(cookiePayload.token, headerPayload.token)) { - throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.") + throw new AuraAuthError({ code: "CSRF_TOKEN_MISMATCH" }) } return true - } catch { - throw new AuthSecurityError("CSRF_TOKEN_INVALID", "The CSRF tokens do not match.") + } catch (error) { + throw new AuraAuthError({ code: "CSRF_TOKEN_MISSING", cause: error }) } } diff --git a/packages/core/src/shared/errors.ts b/packages/core/src/shared/errors.ts deleted file mode 100644 index e3826f2a..00000000 --- a/packages/core/src/shared/errors.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { AuthInternalErrorCode, AuthSecurityErrorCode, ErrorType, LiteralUnion } from "@/@types/index.ts" - -interface V8ErrorConstructor extends ErrorConstructor { - captureStackTrace(targetObject: object, constructorOpt?: Function): void -} - -/** - * Type guard to check if the current runtime environment - * supports Error.captureStackTrace. - */ -export const hasCaptureStackTrace = (errorConstructor: ErrorConstructor): errorConstructor is V8ErrorConstructor => { - return "captureStackTrace" in errorConstructor && typeof (errorConstructor as any).captureStackTrace === "function" -} - -/** - * The object returned by the class to users its: - * - type: "OAUTH_PROTOCOL_ERROR" to identify the error type - * - error: A short error code - * - description: A human-readable description of the error. The description is obtained from the message property of the Error class - * - errorURI: A URI with more information about the error - */ -export class OAuthProtocolError extends Error { - readonly type = "OAUTH_PROTOCOL_ERROR" - public readonly error: string - public readonly errorURI?: string - - constructor(error: LiteralUnion>, description?: string, errorURI?: string, options?: ErrorOptions) { - super(description, options) - this.error = error - this.errorURI = errorURI - this.name = new.target.name - if (hasCaptureStackTrace(Error)) { - Error.captureStackTrace(this, new.target) - } - } -} - -/** - * The object returned when an internal error occurs in the Aura Auth library. - * - type: "AUTH_INTERNAL_ERROR" to identify the error type - * - message: A human-readable description of the error. The description is obtained from the message property of the Error class - * - code: An optional error code that can be used to identify the specific error, for example, LIKE "ERR_AUTH_INTERNAL_ERROR" - */ -export class AuthInternalError extends Error { - readonly type = "AUTH_INTERNAL_ERROR" - readonly code: string - - constructor(code: AuthInternalErrorCode, message?: string, options?: ErrorOptions) { - super(message, options) - this.code = code - this.name = new.target.name - if (hasCaptureStackTrace(Error)) { - Error.captureStackTrace(this, new.target) - } - } -} - -/** - * The object returned when a security error occurs in the Aura Auth library. - * - type: "AUTH_SECURITY_ERROR" to identify the error type - * - message: A human-readable description of the error. The description is obtained from the message property of the Error class - * - code: An optional error code that can be used to identify the specific error, for example, LIKE "ERR_AUTH_SECURITY_ERROR" - */ -export class AuthSecurityError extends Error { - readonly type = "AUTH_SECURITY_ERROR" - readonly code: string - - constructor(code: LiteralUnion, message?: string, options?: ErrorOptions) { - super(message, options) - this.code = code - this.name = new.target.name - if (hasCaptureStackTrace(Error)) { - Error.captureStackTrace(this, new.target) - } - } -} - -export class AuthClientError extends Error { - readonly type = "AUTH_CLIENT_ERROR" - readonly code: string - - constructor(code: string, message?: string, options?: ErrorOptions) { - super(message, options) - this.code = code - this.name = new.target.name - if (hasCaptureStackTrace(Error)) { - Error.captureStackTrace(this, new.target) - } - } -} - -export class AuthInvalidConfigurationError extends Error { - readonly type = "AUTH_INVALID_CONFIGURATION_ERROR" - - constructor(message?: string, options?: ErrorOptions) { - super(message, options) - this.name = new.target.name - if (hasCaptureStackTrace(Error)) { - Error.captureStackTrace(this, new.target) - } - } -} - -export class AuthValidationError extends Error { - readonly type = "AUTH_VALIDATION_ERROR" - readonly code: string - - constructor(code: string, message?: string, options?: ErrorOptions) { - super(message, options) - this.code = code - this.name = new.target.name - if (hasCaptureStackTrace(Error)) { - Error.captureStackTrace(this, new.target) - } - } -} - -export class AuthJoseInitializationError extends Error { - readonly type = "JOSE_INITIALIZATION_FAILED" - readonly code: string - - constructor(code: string, message?: string, options?: ErrorOptions) { - super(message, options) - this.code = code - this.name = new.target.name - if (hasCaptureStackTrace(Error)) { - Error.captureStackTrace(this, new.target) - } - } -} - -export const isNativeError = (error: unknown): error is Error => { - return error instanceof Error -} - -export const isOAuthProtocolError = (error: unknown): error is OAuthProtocolError => { - return error instanceof OAuthProtocolError -} - -export const isAuthInternalError = (error: unknown): error is AuthInternalError => { - return error instanceof AuthInternalError -} - -export const isAuthSecurityError = (error: unknown): error is AuthSecurityError => { - return error instanceof AuthSecurityError -} - -export const isAuthClientError = (error: unknown): error is AuthClientError => { - return error instanceof AuthClientError -} - -export const isAuthInvalidConfigurationError = (error: unknown): error is AuthInvalidConfigurationError => { - return error instanceof AuthInvalidConfigurationError -} - -export const isAuthValidationError = (error: unknown): error is AuthValidationError => { - return error instanceof AuthValidationError -} - -export const isAuthErrorWithCode = (error: unknown): error is { code: string; message: string } => { - return isAuthInternalError(error) || isAuthSecurityError(error) || isAuthClientError(error) || isAuthValidationError(error) -} diff --git a/packages/core/src/shared/unstable_error.ts b/packages/core/src/shared/unstable_error.ts new file mode 100644 index 00000000..b1121262 --- /dev/null +++ b/packages/core/src/shared/unstable_error.ts @@ -0,0 +1,739 @@ +export const AuraErrorCode = { + /** + * JWT and JOSE Errors + */ + JWT_EXPIRED: "JWT_EXPIRED", + JWT_INVALID_SIGNATURE: "JWT_INVALID_SIGNATURE", + JWT_MALFORMED: "JWT_MALFORMED", + JWT_ALGORITHM_MISMATCH: "JWT_ALGORITHM_MISMATCH", + JWT_KEY_ROTATION_FAILED: "JWT_KEY_ROTATION_FAILED", + JWT_SEAL_FAILED: "JWT_SEAL_FAILED", + JWT_UNSEAL_FAILED: "JWT_UNSEAL_FAILED", + JWT_INVALID_MODE: "JWT_INVALID_MODE", + /** + * CSRF Tokens Errors + */ + CSRF_TOKEN_MISSING: "CSRF_TOKEN_MISSING", + CSRF_TOKEN_MISMATCH: "CSRF_TOKEN_MISMATCH", + CSRF_ORIGIN_REJECTED: "CSRF_ORIGIN_REJECTED", + CSRF_DOUBLE_SUBMIT_FAILED: "CSRF_DOUBLE_SUBMIT_FAILED", + /** + * Session Errors + */ + SESSION_NOT_FOUND: "SESSION_NOT_FOUND", + SESSION_EXPIRED: "SESSION_EXPIRED", + SESSION_REVOKED: "SESSION_REVOKED", + SESSION_INVALID: "SESSION_INVALID", + SESSION_STRATEGY_MISMATCH: "SESSION_STRATEGY_MISMATCH", + SESSION_STORE_UNAVAILABLE: "SESSION_STORE_UNAVAILABLE", + UPDATE_SESSION_INVALID: "UPDATE_SESSION_INVALID", + INVALID_SESSION_STRATEGY: "INVALID_SESSION_STRATEGY", + /** + * Cookie Errors + */ + COOKIE_NOT_FOUND: "COOKIE_NOT_FOUND", + COOKIE_INVALID_VALUE: "COOKIE_INVALID_VALUE", + SET_COOKIE_NOT_FOUND: "SET_COOKIE_NOT_FOUND", + SET_COOKIE_INVALID_VALUE: "SET_COOKIE_INVALID_VALUE", + /** + * Auth Errors + */ + AUTH_CREDENTIALS_INVALID: "AUTH_CREDENTIALS_INVALID", + AUTH_PROVIDER_REJECTED: "AUTH_PROVIDER_REJECTED", + AUTH_CALLBACK_STATE_INVALID: "AUTH_CALLBACK_STATE_INVALID", + AUTH_MFA_REQUIRED: "AUTH_MFA_REQUIRED", + AUTH_MFA_CODE_INVALID: "AUTH_MFA_CODE_INVALID", + USER_CREATION_FAILED: "USER_CREATION_FAILED", + AUTH_BASIC_CREDENTIALS_INVALID: "AUTH_BASIC_CREDENTIALS_INVALID", + /** + * Configuration Errors + */ + CONFIG_INVALID: "CONFIG_INVALID", + CONFIG_MISSING_REQUIRED: "CONFIG_MISSING_REQUIRED", + CONFIG_BASE_URL_MISSING: "CONFIG_BASE_URL_MISSING", + INVALID_AUTH_CONFIGURATION: "INVALID_AUTH_CONFIGURATION", + INVALID_TRUSTED_ORIGIN: "INVALID_TRUSTED_ORIGIN", + CLIENT_BASE_URL_MISSING: "CLIENT_BASE_URL_MISSING", + POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED: "POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED", + JOSE_INITIALIZATION_SALT_MISSING: "JOSE_INITIALIZATION_SALT_MISSING", + JOSE_INITIALIZATION_SECRET_MISSING: "JOSE_INITIALIZATION_SECRET_MISSING", + INVALID_SALT_SECRET_VALUE: "INVALID_SALT_SECRET_VALUE", + INVALID_PEM_KEY_PAIR_MODE_MISMATCH: "INVALID_PEM_KEY_PAIR_MODE_MISMATCH", + INVALID_PEM_KEY_PAIR_SINGLE_MISMATCH: "INVALID_PEM_KEY_PAIR_SINGLE_MISMATCH", + /** + * OAuth Errors + */ + UNSUPPORTED_OAUTH_CONFIGURATION: "UNSUPPORTED_OAUTH_CONFIGURATION", + INVALID_ACCESS_TOKEN_OAUTH_CONFIG: "INVALID_ACCESS_TOKEN_OAUTH_CONFIG", + INVALID_OAUTH_ACCESS_TOKEN_RESPONSE: "INVALID_OAUTH_ACCESS_TOKEN_RESPONSE", + INVALID_OAUTH_ACCESS_TOKEN_RES_FORMAT: "INVALID_OAUTH_ACCESS_TOKEN_RES_FORMAT", + INVALID_ACCESS_TOKEN: "INVALID_ACCESS_TOKEN", + UNKNOWN_OAUTH_ACCESS_TOKEN_ERROR: "UNKNOWN_OAUTH_ACCESS_TOKEN_ERROR", + INVALID_USER_INFO: "INVALID_USER_INFO", + INVALID_OAUTH_USER_INFO_RESPONSE: "INVALID_OAUTH_USER_INFO_RESPONSE", + INVALID_OAUTH_USER_INFO_RES_FORMAT: "INVALID_OAUTH_USER_INFO_RES_FORMAT", + UNKNOWN_OAUTH_USER_INFO_ERROR: "UNKNOWN_OAUTH_USER_INFO_ERROR", + INVALID_CUSTOM_USER_INFO_ERROR: "INVALID_CUSTOM_USER_INFO_ERROR", + UNKNOWN_CUSTOM_USER_INFO_ERROR: "UNKNOWN_CUSTOM_USER_INFO_ERROR", + AUTH_CALLBACK_MISSING_PARAMETERS: "AUTH_CALLBACK_MISSING_PARAMETERS", + AUTH_MISMATCHING_STATE: "AUTH_MISMATCHING_STATE", + INVALID_OAUTH_PROVIDER_URL_CONFIG: "INVALID_OAUTH_PROVIDER_URL_CONFIG", + INVALID_OAUTH_PROVIDER_SCHEMA_CONFIG: "INVALID_OAUTH_PROVIDER_SCHEMA_CONFIG", + DUPLICATED_OAUTH_PROVIDER_ID: "DUPLICATED_OAUTH_PROVIDER_ID", + INVALID_ENVIRONMENT_CONFIGURATION: "INVALID_ENVIRONMENT_CONFIGURATION", + PKCE_VERIFIER_INVALID: "PKCE_VERIFIER_INVALID", + /** + * Schema Errors + */ + SCHEMA_INVALID_MODE: "SCHEMA_INVALID_MODE", + SCHEMA_UNSUPPORTED: "SCHEMA_UNSUPPORTED", + SCHEMA_PARSER_FAILED: "SCHEMA_PARSER_FAILED", + /** + * Network Errors + */ + NETWORK_REQUEST_FAILED: "NETWORK_REQUEST_FAILED", + NETWORK_TIMEOUT: "NETWORK_TIMEOUT", +} as const + +export type AuraErrorCode = (typeof AuraErrorCode)[keyof typeof AuraErrorCode] + +export type AuraErrorType = + /** + * Authentication lifecycle / session validation failures + */ + | "AUTH_FLOW" + /** + * Identity provider / OAuth specification failures + */ + | "PROTOCOL" + /** + * Gateway timeouts / Fetch errors / Server outages + */ + | "NETWORK" + /** + * Bad user configurations / payload structural checking + */ + | "VALIDATION" + | "INTERNAL" + +interface CatalogEntry { + type: AuraErrorType + statusCode: number + message: string + userMessage: string + name: string +} + +export const ERROR_CATALOG: Record = { + /** + * JWT and JOSE Errors + */ + JWT_EXPIRED: { + type: "AUTH_FLOW", + statusCode: 401, + name: "JwtError", + message: "The provided JSON Web Token has expired based on its 'exp' claim or maxExpiration (mexp) library settings.", + userMessage: "Your session has expired based on its max expiration. Please sign in again.", + }, + JWT_INVALID_SIGNATURE: { + type: "AUTH_FLOW", + statusCode: 401, + name: "JwtError", + message: + "The cryptographic signature verification failed. The token token may have been tampered with or signed with an invalid key.", + userMessage: "Authentication failed. Please sign in again.", + }, + JWT_MALFORMED: { + type: "VALIDATION", + statusCode: 401, + name: "JwtError", + message: "The token string does not conform to the standard JWS/JWT three-part structure (header.payload.signature).", + userMessage: "Authentication failed. Please sign in again.", + }, + JWT_ALGORITHM_MISMATCH: { + type: "VALIDATION", + statusCode: 401, + name: "JwtError", + message: + "The token header specifies an 'alg' that is not permitted by your local library security configuration restrictions.", + userMessage: "Authentication failed. Please sign in again.", + }, + JWT_KEY_ROTATION_FAILED: { + type: "INTERNAL", + statusCode: 500, + name: "JwtError", + message: "Failed to fetch or parse the remote JSON Web Key Set (JWKS) during an automatic signature key rotation cycle.", + userMessage: "An internal error occurred. Please try again.", + }, + JWT_SEAL_FAILED: { + type: "INTERNAL", + statusCode: 500, + name: "JwtError", + message: + "The HKDF key derivation or AES-GCM encryption pipeline failed while trying to encrypt/seal the session payload.", + userMessage: "An internal error occurred. Please try again.", + }, + JWT_UNSEAL_FAILED: { + type: "INTERNAL", + statusCode: 500, + name: "JwtError", + message: + "The decryption pattern or integrity authentication tag validation failed during the token unseal execution loop.", + userMessage: "Authentication failed. Please sign in again.", + }, + JWT_INVALID_MODE: { + type: "VALIDATION", + statusCode: 500, + name: "JwtError", + message: + "The specified session mode does not match structural constraints. Expected configurations: 'sealed', 'signed', or 'encrypted'.", + userMessage: "Invalid JWT mode configured. Valid options are: 'sealed', 'signed', 'encrypted'.", + }, + /** + * CSRF Tokens Errors + */ + CSRF_TOKEN_MISSING: { + type: "AUTH_FLOW", + statusCode: 403, + name: "CsrfError", + message: + "State tracking failed because the required CSRF token cookie could not be extracted from incoming request headers.", + userMessage: "The CSRF token is missing. Please refresh and try again.", + }, + CSRF_TOKEN_MISMATCH: { + type: "AUTH_FLOW", + statusCode: 403, + name: "CsrfError", + message: + "Security violation: The request payload/header anti-forgery token string does not match the value stored in the secure session cookie.", + userMessage: "CSRF token verification failed. Please refresh and try again.", + }, + CSRF_ORIGIN_REJECTED: { + type: "AUTH_FLOW", + statusCode: 403, + name: "CsrfError", + message: + "Cross-Origin request blocked: The incoming Request 'Origin' header does not match the expected application Host or trusted subdomains.", + userMessage: "Request validation failed. Request origin is untrusted.", + }, + CSRF_DOUBLE_SUBMIT_FAILED: { + type: "AUTH_FLOW", + statusCode: 403, + name: "CsrfError", + message: + "The state verification engine failed because the custom 'X-CSRF-Token' header was missing from the mutation request parameters.", + userMessage: "The CSRF header is missing. Please refresh and try again.", + }, + /** + * Session Errors + */ + SESSION_NOT_FOUND: { + type: "AUTH_FLOW", + statusCode: 401, + name: "SessionError", + message: + "The context evaluation phase failed because the target identifier sessionToken could not be pulled from the cookies object context.", + userMessage: "The session token is not found. There is no active session.", + }, + SESSION_EXPIRED: { + type: "AUTH_FLOW", + statusCode: 401, + name: "SessionError", + message: + "The user session lifecycle timestamp has exceeded the absolute maximum duration threshold specified in storage settings.", + userMessage: "Your session has expired. Please sign in again.", + }, + SESSION_REVOKED: { + type: "AUTH_FLOW", + statusCode: 403, + name: "SessionError", + message: + "The session block execution was aborted because the target token was explicitly blacklisted or flagged as inactive in persistence layer checks.", + userMessage: "Your session has been revoked. Please sign in again.", + }, + SESSION_INVALID: { + type: "AUTH_FLOW", + statusCode: 401, + name: "SessionError", + message: + "The framework extracted a session token string, but it failed basic integrity decoding checks or cryptographic signatures.", + userMessage: "The session is not valid. Its signature or decryption parameters failed.", + }, + SESSION_STRATEGY_MISMATCH: { + type: "VALIDATION", + statusCode: 500, + name: "SessionError", + message: + "The storage strategy context configuration doesn't align with active adapter engines (e.g. database adapter passed but strategy forced to pure 'jwt').", + userMessage: "The session handling configuration strategy is mismatched.", + }, + SESSION_STORE_UNAVAILABLE: { + type: "INTERNAL", + statusCode: 503, + name: "SessionError", + message: + "The backing session persistence manager or distributed key-value store cache failed to respond within operational timeout limits.", + userMessage: "Service temporarily unavailable. Please try again.", + }, + UPDATE_SESSION_INVALID: { + type: "AUTH_FLOW", + statusCode: 400, + name: "SessionError", + message: + "The internal call to 'refreshSession' completed, but returned a nullish value, meaning token mutation could not finish cleanly.", + userMessage: "Failed to update session parameters.", + }, + INVALID_SESSION_STRATEGY: { + type: "VALIDATION", + statusCode: 500, + name: "SessionError", + message: "The provided 'session.strategy' option string value is unsupported by the engine runtime core configurations.", + userMessage: "Unknown session strategy configured. Valid options are: 'jwt'.", + }, + + /** + * Cookie Errors + */ + COOKIE_NOT_FOUND: { + type: "AUTH_FLOW", + statusCode: 401, + name: "CookieError", + message: + "The request pipeline expected parsing access to a 'Cookie' header block, but the raw header property evaluates to undefined.", + userMessage: "No cookies found. There is no active session.", + }, + COOKIE_INVALID_VALUE: { + type: "AUTH_FLOW", + statusCode: 401, + name: "CookieError", + message: + "A target cookie identifier was discovered by the parser, but its internal string value payload resolved to blank or nullish data.", + userMessage: "Expected configuration cookie not found or contains an empty value.", + }, + SET_COOKIE_NOT_FOUND: { + type: "INTERNAL", + statusCode: 500, + name: "CookieError", + message: + "The outbound Response middleware pipeline completed execution, but no structural 'Set-Cookie' header operations were registered.", + userMessage: "No cookies found in the application response.", + }, + SET_COOKIE_INVALID_VALUE: { + type: "INTERNAL", + statusCode: 500, + name: "CookieError", + message: + "The system attempted to assign outbound state, but the generated value parameter evaluation payload returned a nullish value.", + userMessage: "The response cookie update target string has a nullish value.", + }, + /** + * Auth Errors + */ + AUTH_CREDENTIALS_INVALID: { + type: "AUTH_FLOW", + statusCode: 401, + name: "AuthError", + message: + "The custom user 'authorize' handler function returned a nullish profile structure object or explicitly threw a mismatch validation signal.", + userMessage: "The user's session couldn't be established with the provided credentials.", + }, + AUTH_PROVIDER_REJECTED: { + type: "PROTOCOL", + statusCode: 502, + name: "AuthError", + message: + "The downstream identity provider rejected verification protocols or explicit request parameters during handshake loops.", + userMessage: "Authentication provider error. Please try again.", + }, + AUTH_CALLBACK_STATE_INVALID: { + type: "PROTOCOL", + statusCode: 400, + name: "AuthError", + message: + "The incoming state value from the third-party endpoint query string failed basic framework schema or parsing validations.", + userMessage: "Invalid authentication state. Please try again.", + }, + AUTH_MFA_REQUIRED: { + type: "AUTH_FLOW", + statusCode: 403, + name: "AuthError", + message: + "Primary credential step passed, but system identity rules dictate intercepting execution to wait for a Multi-Factor token challenge verification.", + userMessage: "Multi-factor authentication check is required to continue.", + }, + AUTH_MFA_CODE_INVALID: { + type: "AUTH_FLOW", + statusCode: 401, + name: "AuthError", + message: + "The custom Multi-Factor authentication TOTP/HOTP or SMS code submission string failed validation verification checks against host rules.", + userMessage: "The multi-factor verification code is invalid.", + }, + USER_CREATION_FAILED: { + type: "INTERNAL", + statusCode: 500, + name: "AuthError", + message: + "The custom lifecycle hook 'onCreateUser' aborted execution, thrown exception handling traps, or returned an unexpected null reference mapping.", + userMessage: "Failed to create user account with the provided metadata payload.", + }, + AUTH_BASIC_CREDENTIALS_INVALID: { + type: "AUTH_FLOW", + statusCode: 401, + name: "AuthError", + message: + "The HTTP Basic Authentication header failed credential verification. The decoded username and password pair did not match any user records or the authentication provider rejected the credentials.", + userMessage: "The username or password is incorrect. Please verify your credentials and try again.", + }, + /** + * Configuration Errors + */ + CONFIG_INVALID: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "The primary configuration object argument failed initial structural layout runtime checks during engine context setups.", + userMessage: "An internal library validation error occurred.", + }, + CONFIG_MISSING_REQUIRED: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "Crucial framework setup options are absent. Verify that required structural fields are present during initialization mappings.", + userMessage: "Required core environment parameters are missing from registration settings.", + }, + CONFIG_BASE_URL_MISSING: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: "", + userMessage: "", + }, + INVALID_AUTH_CONFIGURATION: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "The system cannot establish request resolution routes. Provide a valid 'BASE_URL' system environment configuration value or setup trusted proxy headers.", + userMessage: "The application context URL cannot be constructed. Set BASE_URL or provide proxy host headers.", + }, + INVALID_TRUSTED_ORIGIN: { + type: "VALIDATION", + statusCode: 400, + name: "ConfigError", + message: + "The request location context was blocked. The incoming value does not match patterns mapped inside your array configuration rules.", + userMessage: "The incoming ORIGIN is not trusted. Verify your trustedOrigins configuration.", + }, + CLIENT_BASE_URL_MISSING: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "The client wrapper utility was instantiated inside a non-browser environment (Server Action, API route, etc.) without providing an explicit 'baseURL' fallback string property.", + userMessage: "baseURL is required when createAuthClient is invoked outside browser environments.", + }, + POTENTIAL_OPEN_REDIRECT_ATTACK_DETECTED: { + type: "VALIDATION", + statusCode: 400, + name: "ConfigError", + message: + "The downstream navigation parameter target path evaluation failed security context tracking verification. The destination URL domain is untrusted.", + userMessage: "Invalid redirect path intercepted. Potential open redirect attack detected.", + }, + JOSE_INITIALIZATION_SALT_MISSING: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "Core security initialization failed because both 'AURA_AUTH_SALT' and 'AUTH_SALT' environment string keys are completely missing from runtime access contexts.", + userMessage: "AURA_AUTH_SALT or AUTH_SALT environment variable is not set. Salt required for key derivation.", + }, + JOSE_INITIALIZATION_SECRET_MISSING: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "Core security initialization failed because both 'AURA_AUTH_SECRET' and 'AUTH_SECRET' environment string keys are completely missing from runtime access contexts.", + userMessage: "AURA_AUTH_SECRET environment variable is not set and no fallback secret was provided.", + }, + INVALID_SALT_SECRET_VALUE: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "The extracted salt string parameter does not fit minimum byte length requirements or baseline entropy targets needed for safe PBKDF2 key derivations.", + userMessage: "The encryption salt value must be at least 32 bytes long and meet baseline entropy values.", + }, + INVALID_PEM_KEY_PAIR_MODE_MISMATCH: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "A configuration layout rule conflict was detected. Multiple asymmetric keys were passed but the runtime 'session.mode' parameter was not forced to 'sealed'.", + userMessage: "Multiple PEM Key Pairs found in runtime configurations require 'sealed' JWT mode.", + }, + INVALID_PEM_KEY_PAIR_SINGLE_MISMATCH: { + type: "VALIDATION", + statusCode: 500, + name: "ConfigError", + message: + "A configuration layout rule conflict was detected. A single asymmetric key pair structure was loaded but the session processing mode was set to 'sealed'.", + userMessage: "Single PEM key pairs from configurations require 'signed' or 'encrypted' JWT mode.", + }, + UNSUPPORTED_OAUTH_CONFIGURATION: { + type: "VALIDATION", + statusCode: 400, + name: "OAuthError", + message: + "An execution request flow was initialized targeting a specific identity provider code string that doesn't exist within initialized provider definitions.", + userMessage: "The targeted OAuth provider has not been configured in the initialization parameters.", + }, + INVALID_ACCESS_TOKEN_OAUTH_CONFIG: { + type: "VALIDATION", + statusCode: 500, + name: "OAuthError", + message: + "The runtime provider definition block lacks token endpoints, formatting methods, or client routing configurations required for handshake mutations.", + userMessage: "The remote access token exchange profile setup parameters are invalid.", + }, + INVALID_OAUTH_ACCESS_TOKEN_RESPONSE: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "The outbound HTTP request to the remote identity provider token exchange endpoint failed validation checks. The response 'ok' field resolved to false.", + userMessage: "The authorization server rejected the request during the token exchange handshake.", + }, + INVALID_OAUTH_ACCESS_TOKEN_RES_FORMAT: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "The third-party authentication server responded with an HTTP status 200, but the returned data block structure fails schema verification (e.g. missing 'access_token').", + userMessage: "The identity provider token payload did not satisfy standard schema formats.", + }, + INVALID_ACCESS_TOKEN: { + type: "PROTOCOL", + statusCode: 401, + name: "OAuthError", + message: + "The external authorization endpoint directly responded with an explicit error code payload parameter during token processing loops.", + userMessage: "Failed to clear identity transport verification down to the provider.", + }, + UNKNOWN_OAUTH_ACCESS_TOKEN_ERROR: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "An unexpected runtime code path crash or unclassified transport exception occurred during the remote provider access token exchange execution flow.", + userMessage: "An unclassified token pipeline failure occurred during third-party processing.", + }, + INVALID_USER_INFO: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "The downstream mapping verification phase was aborted because the decoded third-party profile structure does not expose a stable immutable mapping key (id/sub/uid).", + userMessage: "The provider profile identity map did not supply an immutable index key (id/sub/uid).", + }, + INVALID_OAUTH_USER_INFO_RESPONSE: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "The downstream endpoint fetch request to the provider user profile storage API returned an invalid response code status.", + userMessage: "The resource userInfo target server returned an error code response.", + }, + INVALID_OAUTH_USER_INFO_RES_FORMAT: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "The provider profile user data response format did not match semantic JSON object types required for downstream database generation.", + userMessage: "The returned user info profile structure payload is corrupted or unexpected.", + }, + UNKNOWN_OAUTH_USER_INFO_ERROR: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "An unmapped connection trap exploded during asynchronous background operations inside the default profile fetch pipeline routines.", + userMessage: "Failed to communicate clean state down to the user configuration data provider.", + }, + INVALID_CUSTOM_USER_INFO_ERROR: { + type: "VALIDATION", + statusCode: 500, + name: "OAuthError", + message: + "The host application developer supplied a custom 'profile' mapping block callback method, but the return value runtime resolution returned undefined or threw an error.", + userMessage: "The custom userinfo mapper callback returned an empty payload reference or threw.", + }, + UNKNOWN_CUSTOM_USER_INFO_ERROR: { + type: "PROTOCOL", + statusCode: 502, + name: "OAuthError", + message: + "An unclassified system runtime breakdown occurred while trying to process data records down inside the developer's user profile normalization routine.", + userMessage: "An internal engine exception stopped custom resource user tracking map executions.", + }, + AUTH_CALLBACK_MISSING_PARAMETERS: { + type: "PROTOCOL", + statusCode: 400, + name: "OAuthError", + message: + "The incoming callback route handler intercepted a processing execution path where query location search fields are missing vital OAuth spec values ('code' or 'state').", + userMessage: "Expected security parameter state or exchange code is completely missing from request.", + }, + AUTH_MISMATCHING_STATE: { + type: "PROTOCOL", + statusCode: 400, + name: "OAuthError", + message: + "CSRF state attack prevented. The 'state' payload value extracted from incoming third-party route query properties doesn't match local session storage values.", + userMessage: "The provided state passed in the OAuth response does not match the stored token state.", + }, + INVALID_OAUTH_PROVIDER_URL_CONFIG: { + type: "VALIDATION", + statusCode: 500, + name: "OAuthError", + message: + "A required structural provider URL definition configuration parameter is empty or holds an invalid URI layout inside your provider customization registry.", + userMessage: "The authorization gateway URL setup rule is missing from the custom provider setup object.", + }, + INVALID_OAUTH_PROVIDER_SCHEMA_CONFIG: { + type: "VALIDATION", + statusCode: 500, + name: "OAuthError", + message: + "The loaded configuration settings array failed standard library schema validation checks against required engine operational footprints.", + userMessage: "The provider context configuration properties failed standard schema verification checks.", + }, + DUPLICATED_OAUTH_PROVIDER_ID: { + type: "VALIDATION", + statusCode: 500, + name: "OAuthError", + message: + "The registration collection contains duplicate identifier keys. Unique registration indices are mandatory across tracking providers.", + userMessage: "Duplicate identification keys detected in the engine providers registration list.", + }, + INVALID_ENVIRONMENT_CONFIGURATION: { + type: "VALIDATION", + statusCode: 500, + name: "OAuthError", + message: + "The framework cannot resolve environment credentials for the current provider target. Make sure target system secret variables are configured properly.", + userMessage: "Client identification strings or secret hashes are missing from configuration contexts.", + }, + PKCE_VERIFIER_INVALID: { + type: "PROTOCOL", + statusCode: 400, + name: "OAuthError", + message: + "The generated or passed PKCE 'code_verifier' configuration string structure does not fulfill security specification layout rules (must be between 43 and 128 characters long).", + userMessage: "The cryptographic dynamic code verifier does not fit structural specification constraints (43-128 chars).", + }, + /** + * Schema Errors + */ + SCHEMA_INVALID_MODE: { + type: "VALIDATION", + statusCode: 500, + name: "SchemaError", + message: + "The identity mapping configuration validation mode string is unsupported. Supported string flags: 'strip', 'passthrough', 'strict', 'partial'.", + userMessage: "Unsupported schema parsing parameters configuration. Options: 'strip', 'passthrough', 'strict', 'partial'.", + }, + SCHEMA_UNSUPPORTED: { + type: "VALIDATION", + statusCode: 500, + name: "SchemaError", + message: + "The library failed to find a matching validator compiler module. The custom strategy must inherit from supported engines: Zod, Valibot, Typebox, or Arktype.", + userMessage: "Unsupported structural compilation type. Supported adapters: Zod, Valibot, Typebox, Arktype.", + }, + SCHEMA_PARSER_FAILED: { + type: "VALIDATION", + statusCode: 500, + name: "SchemaError", + message: + "The schema validator failed to parse or execute the configured schema. This typically indicates a malformed schema definition or a runtime parser issue inside the selected validation adapter.", + userMessage: + "An internal schema parsing error occurred. Please verify your schema configuration and validation adapter setup.", + }, + /** + * Network Errors + */ + NETWORK_REQUEST_FAILED: { + type: "NETWORK", + statusCode: 502, + name: "NetworkError", + message: + "The internal network wrapper failed to establish a secure HTTP connection handshake with upstream servers or external REST resource targets.", + userMessage: "An internal outgoing transport network execution failed down to external services.", + }, + NETWORK_TIMEOUT: { + type: "NETWORK", + statusCode: 504, + name: "NetworkError", + message: + "The external API target connection pool or request fetch signal context exceeded designated millisecond timeout threshold rules without returning headers.", + userMessage: "The network response time tracking expired before receiving data headers.", + }, +} + +export interface AuraErrorOptions extends ErrorOptions { + code: AuraErrorCode + message?: string + statusCode?: number + userMessage?: string +} + +interface V8ErrorConstructor extends ErrorConstructor { + captureStackTrace(targetObject: object, constructorOpt?: Function): void +} + +/** + * Type guard to check if the current runtime environment + * supports Error.captureStackTrace. + */ +export const hasCaptureStackTrace = (errorConstructor: ErrorConstructor): errorConstructor is V8ErrorConstructor => { + return "captureStackTrace" in errorConstructor && typeof (errorConstructor as any).captureStackTrace === "function" +} + +export class AuraAuthError extends Error { + readonly code: AuraErrorCode + readonly type: AuraErrorType + readonly userMessage: string + readonly statusCode: number + + constructor({ code, message, cause, statusCode, userMessage }: AuraErrorOptions) { + const entry = ERROR_CATALOG[code] + const finalInternalMessage = message ?? entry.message + super(finalInternalMessage, { cause }) + + this.name = entry.name + this.code = code + this.type = entry.type + this.statusCode = statusCode ?? entry.statusCode + this.userMessage = userMessage ?? entry.userMessage + + Object.setPrototypeOf(this, new.target.prototype) + if (hasCaptureStackTrace(Error)) { + Error.captureStackTrace(this, new.target) + } + } + + toResponse() { + return Response.json( + { type: this.type, code: this.code, message: this.userMessage }, + { status: this.statusCode, statusText: this.code } + ) + } +} + +export const isAuraAuthError = (value: unknown): value is AuraAuthError => { + return value instanceof AuraAuthError +} diff --git a/packages/core/src/shared/utils.ts b/packages/core/src/shared/utils.ts index a360b627..57590257 100644 --- a/packages/core/src/shared/utils.ts +++ b/packages/core/src/shared/utils.ts @@ -1,11 +1,9 @@ import { getEnv } from "@/shared/env.ts" import { encoder } from "@aura-stack/jose/crypto" -import { AuthInternalError } from "@/shared/errors.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { isRelativeURL, isValidURL } from "@/shared/assert.ts" -import type { ZodError } from "zod/v4" -import type { APIErrorMap } from "@/@types/index.ts" -export const AURA_AUTH_VERSION = "0.5.0" +export const AURA_AUTH_VERSION = "0.7.2" export const equals = (a: string | number | undefined | null, b: string | number | undefined | null) => { if (a === null || b === null || a === undefined || b === undefined) return false @@ -27,22 +25,6 @@ export const isSecureConnection = (request: Request | Headers, trustedProxyHeade : (url?.startsWith("https://") ?? false) } -export const formatZodError = = Record>(error: ZodError): APIErrorMap => { - if (!error.issues || error.issues.length === 0) { - return {} - } - return error.issues.reduce((previous, issue) => { - const key = issue.path.join(".") - return { - ...previous, - [key]: { - code: issue.code, - message: issue.message, - }, - } - }, {}) -} - export const extractPath = (url: string): string => { const pathRegex = /^https?:\/\/[a-zA-Z0-9_\-.]+(:\d+)?(\/.*)$/ const match = url.match(pathRegex) @@ -112,7 +94,7 @@ export const createBasicAuthHeader = (username: string, password: string): strin const getUsername = getEnv(username) ?? username const getPassword = getEnv(password) ?? password if (!getUsername || !getPassword) { - throw new AuthInternalError("INVALID_OAUTH_CONFIGURATION", "Missing client credentials for OAuth provider configuration.") + throw new AuraAuthError({ code: "AUTH_BASIC_CREDENTIALS_INVALID" }) } const credentials = `${getUsername}:${getPassword}` const binaryCredentials = String.fromCharCode.apply(null, Array.from(encoder.encode(credentials))) diff --git a/packages/core/src/validator/registry.ts b/packages/core/src/validator/registry.ts index d95760cc..d161136e 100644 --- a/packages/core/src/validator/registry.ts +++ b/packages/core/src/validator/registry.ts @@ -1,13 +1,12 @@ import { z } from "zod/v4" import * as valibot from "valibot" import { type } from "arktype" -import { formatZodError } from "@/shared/utils.ts" import { IsObject, Type as Typebox } from "typebox" -import { AuthValidationError } from "@/shared/errors.ts" import { createValidator } from "@/validator/validator.ts" import { UserIdentity, type SchemaTypes } from "@/shared/identity.ts" import { isArkType, isValibotSchema, isZodSchema } from "@/shared/assert.ts" import type { IdentityConfig } from "@/@types/config.ts" +import { AuraAuthError } from "@/shared/unstable_error.ts" export const deriveSchema = ( schema: Schema, @@ -59,10 +58,7 @@ export const deriveSchema = ( }) : Typebox.Partial(schema) } - throw new AuthValidationError( - "INVALID_IDENTITY_VALIDATION_FAILED", - `Unsupported schema mode configuration. Valid options are: "strip", "passthrough", "strict" and "partial".` - ) + throw new AuraAuthError({ code: "SCHEMA_INVALID_MODE" }) } export const deriveSchemaWithJWT = (schema: Schema): any => { @@ -117,7 +113,7 @@ export const deriveSchemaWithJWT = (schema: Schema): mexp: z.number().optional(), }) } - throw new AuthValidationError("INVALID_IDENTITY_VALIDATION_FAILED", "Unsupported schema type for JWT extension.") + throw new AuraAuthError({ code: "SCHEMA_INVALID_MODE" }) } export const getFullSchema = (schema: Schema): any => { @@ -157,23 +153,7 @@ export const getFullSchema = (schema: Schema): any = expires: z.coerce.date().optional(), }) } - throw new AuthValidationError("INVALID_IDENTITY_VALIDATION_FAILED", "Unsupported schema type for schema extension.") -} - -const throwValidationError = (activeSchema: SchemaTypes, error: unknown): never => { - let errorDetails: unknown = {} - if (isZodSchema(activeSchema)) { - errorDetails = formatZodError(error as any) - } else if (isValibotSchema(activeSchema)) { - errorDetails = { issues: error } - } else if (isArkType(activeSchema)) { - errorDetails = { error } - } else if (IsObject(activeSchema)) { - errorDetails = { errors: error } - } - throw new AuthValidationError("INVALID_IDENTITY_VALIDATION_FAILED", JSON.stringify(errorDetails, null, 2), { - cause: isZodSchema(activeSchema) ? error : undefined, - }) + throw new AuraAuthError({ code: "SCHEMA_INVALID_MODE" }) } export const createSchemaRegistry = (config: IdentityConfig) => { @@ -188,7 +168,7 @@ export const createSchemaRegistry = (config: Ident const parse = async (data: unknown = {}): Promise => { const { data: output, success, error } = validator.validate(data) if (!success) { - throwValidationError(schema, error) + throw new AuraAuthError({ code: "SCHEMA_PARSER_FAILED", cause: error }) } return output } @@ -196,7 +176,7 @@ export const createSchemaRegistry = (config: Ident const parseAsPartial = async (data: unknown = {}): Promise => { const { data: output, success, error } = partialValidator.validate(data) if (!success) { - throwValidationError(schemaAsPartial, error) + throw new AuraAuthError({ code: "SCHEMA_PARSER_FAILED", cause: error }) } return output } @@ -204,7 +184,7 @@ export const createSchemaRegistry = (config: Ident const parseWithJWT = async (data: unknown = {}): Promise => { const { data: output, success, error } = jwtValidator.validate(data) if (!success) { - throwValidationError(schemaWithJWT, error) + throw new AuraAuthError({ code: "SCHEMA_PARSER_FAILED", cause: error }) } return output } diff --git a/packages/core/src/validator/validator.ts b/packages/core/src/validator/validator.ts index 57ce6cf3..d7f9d7d2 100644 --- a/packages/core/src/validator/validator.ts +++ b/packages/core/src/validator/validator.ts @@ -1,6 +1,7 @@ import { IsObject } from "typebox" import { Value } from "typebox/value" import { safeParse } from "valibot" +import { AuraAuthError } from "@/shared/unstable_error.ts" import { isValibotSchema, isZodSchema, isArkType } from "@/shared/assert.ts" export type ValidationResult = { success: true; data: T; error: null } | { success: false; data: null; error: any } @@ -14,7 +15,7 @@ export interface SchemaAdapter { */ export const createValidator = (schema: any): SchemaAdapter => { if (!isZodSchema(schema) && !isValibotSchema(schema) && !isArkType(schema) && !IsObject(schema)) { - throw new Error("Unsupported schema type") + throw new AuraAuthError({ code: "SCHEMA_UNSUPPORTED" }) } return { validate: (data: unknown): ValidationResult => { @@ -48,7 +49,7 @@ export const createValidator = (schema: any): SchemaAdapter => { ? { success: true, data: dataToValidate as T, error: null } : { success: false, data: null, error: [...Value.Errors(schema, dataToValidate)] } } - return { success: false, data: null, error: new Error("Unsupported schema type") } + return { success: false, data: null, error: new AuraAuthError({ code: "SCHEMA_UNSUPPORTED" }) } } catch (e) { return { success: false, data: null, error: e } } diff --git a/packages/core/test/actions/callback/access-token.test.ts b/packages/core/test/actions/callback/access-token.test.ts index 338c1a5a..58b630f4 100644 --- a/packages/core/test/actions/callback/access-token.test.ts +++ b/packages/core/test/actions/callback/access-token.test.ts @@ -63,7 +63,9 @@ describe("createAccessToken", async () => { "authorization_code_123", codeVerifier ) - ).rejects.toThrow(/Failed to communicate with OAuth provider/) + ).rejects.toThrow( + /An unexpected runtime code path crash or unclassified transport exception occurred during the remote provider access token exchange execution flow./ + ) expect(fetch).toHaveBeenCalledWith("https://example.com/oauth/access_token", { method: "POST", @@ -92,7 +94,9 @@ describe("createAccessToken", async () => { await expect( createAccessToken(oauthCustomService, "https://myapp.com/auth/callback/oauth-provider", "invalid_code", codeVerifier) - ).rejects.toThrow(/Failed to retrieve access token/) + ).rejects.toThrow( + /An unexpected runtime code path crash or unclassified transport exception occurred during the remote provider access token exchange execution flow./ + ) expect(fetch).toHaveBeenCalledWith("https://example.com/oauth/access_token", { method: "POST", @@ -128,7 +132,9 @@ describe("createAccessToken", async () => { "authorization_code_123", codeVerifier ) - ).rejects.toThrow(/Invalid access token response format/) + ).rejects.toThrow( + "The third-party authentication server responded with an HTTP status 200, but the returned data block structure fails schema verification (e.g. missing 'access_token')." + ) expect(fetch).toHaveBeenCalledWith("https://example.com/oauth/access_token", { method: "POST", diff --git a/packages/core/test/actions/callback/callback.test.ts b/packages/core/test/actions/callback/callback.test.ts index 4ccece22..b955fd33 100644 --- a/packages/core/test/actions/callback/callback.test.ts +++ b/packages/core/test/actions/callback/callback.test.ts @@ -75,10 +75,11 @@ describe("callbackAction", () => { test("without cookies", async () => { const response = await GET(new Request("https://example.com/auth/callback/oauth-provider?code=123&state=abc")) - expect(response.status).toBe(400) + expect(response.status).toBe(401) expect(await response.json()).toEqual({ - type: "AUTH_INTERNAL_ERROR", - message: "No cookies found. There is no active session", + type: "AUTH_FLOW", + code: "COOKIE_NOT_FOUND", + message: "No cookies found. There is no active session.", }) }) @@ -97,9 +98,9 @@ describe("callbackAction", () => { ) expect(response.status).toBe(400) expect(await response.json()).toEqual({ - type: "AUTH_SECURITY_ERROR", - code: "MISMATCHING_STATE", - message: "The provided state passed in the OAuth response does not match the stored state.", + type: "PROTOCOL", + code: "AUTH_MISMATCHING_STATE", + message: "The provided state passed in the OAuth response does not match the stored token state.", }) }) @@ -292,11 +293,12 @@ describe("callbackAction", () => { ) expect(fetch).toHaveBeenCalledTimes(2) - expect(response.status).toBe(422) + expect(response.status).toBe(500) expect(await response.json()).toEqual({ - type: "AUTH_VALIDATION_ERROR", - code: "INVALID_IDENTITY_VALIDATION_FAILED", - message: expect.any(String), + type: "VALIDATION", + code: "SCHEMA_PARSER_FAILED", + message: + "An internal schema parsing error occurred. Please verify your schema configuration and validation adapter setup.", }) }) diff --git a/packages/core/test/actions/callback/userinfo.test.ts b/packages/core/test/actions/callback/userinfo.test.ts index f3b73add..a524a5dc 100644 --- a/packages/core/test/actions/callback/userinfo.test.ts +++ b/packages/core/test/actions/callback/userinfo.test.ts @@ -114,7 +114,9 @@ describe("getUserInfo", () => { getUserInfo(oauthConfig as OAuthProviderCredentials, { access_token: "access_token_123", }) - ).rejects.toThrow(/Failed to fetch user information from OAuth provider/) + ).rejects.toThrow( + /An unclassified system runtime breakdown occurred while trying to process data records down inside the developer's user profile normalization routine/ + ) expect(fetch).toHaveBeenCalledWith("https://example.com/oauth/userinfo", { method: "GET", @@ -145,7 +147,9 @@ describe("getUserInfo", () => { getUserInfo(oauthCustomService, { access_token: "invalid_access_token", }) - ).rejects.toThrow(/Invalid userinfo response format/) + ).rejects.toThrow( + /The downstream endpoint fetch request to the provider user profile storage API returned an invalid response code status./ + ) expect(fetch).toHaveBeenCalledWith("https://example.com/oauth/userinfo", { method: "GET", @@ -170,7 +174,9 @@ describe("getUserInfo", () => { getUserInfo(oauthCustomService, { access_token: "access_token", }) - ).rejects.toThrow(/Failed to fetch user information from OAuth provider/) + ).rejects.toThrow( + /An unmapped connection trap exploded during asynchronous background operations inside the default profile fetch pipeline routines./ + ) expect(fetch).toHaveBeenCalledWith("https://example.com/oauth/userinfo", { method: "GET", @@ -249,6 +255,8 @@ describe("getUserInfo", () => { getUserInfo(oauthConfig, { access_token: "access_token", }) - ).rejects.toThrow(/Failed to fetch user information from OAuth provider/) + ).rejects.toThrow( + /An unclassified system runtime breakdown occurred while trying to process data records down inside the developer's user profile normalization routine/ + ) }) }) diff --git a/packages/core/test/actions/signIn/authorization.test.ts b/packages/core/test/actions/signIn/authorization.test.ts index 25a7f81c..38b9189a 100644 --- a/packages/core/test/actions/signIn/authorization.test.ts +++ b/packages/core/test/actions/signIn/authorization.test.ts @@ -107,7 +107,9 @@ describe("createRedirectURI", () => { basePath: "/auth", trustedProxyHeaders, } as GlobalContext) - ).rejects.toThrow("The constructed origin URL is not trusted.") + ).rejects.toThrow( + "The request location context was blocked. The incoming value does not match patterns mapped inside your array configuration rules." + ) }) } }) @@ -146,7 +148,8 @@ describe("createAuthorizationURL", () => { clientSecret: "2", }, redirectURL: "https://example.com/auth/callback", - expected: "The OAuth provider configuration is invalid.", + expected: + "The loaded configuration settings array failed standard library schema validation checks against required engine operational footprints.", }, { description: "missing clientSecret", @@ -159,7 +162,8 @@ describe("createAuthorizationURL", () => { clientId: "1", }, redirectURL: "https://example.com/auth/callback", - expected: "The OAuth provider configuration is invalid.", + expected: + "The loaded configuration settings array failed standard library schema validation checks against required engine operational footprints.", }, ] diff --git a/packages/core/test/actions/signIn/signIn.test.ts b/packages/core/test/actions/signIn/signIn.test.ts index 639730c1..80d6f510 100644 --- a/packages/core/test/actions/signIn/signIn.test.ts +++ b/packages/core/test/actions/signIn/signIn.test.ts @@ -168,8 +168,9 @@ describe("signIn action", () => { signInURL: null, redirect: false, error: { - code: "UNTRUSTED_ORIGIN", - message: "The constructed origin URL is not trusted.", + code: "INVALID_TRUSTED_ORIGIN", + message: + "The request location context was blocked. The incoming value does not match patterns mapped inside your array configuration rules.", }, }) }) @@ -249,8 +250,9 @@ describe("signIn action", () => { signInURL: null, redirect: false, error: { - code: "UNTRUSTED_ORIGIN", - message: "The constructed origin URL is not trusted.", + code: "INVALID_TRUSTED_ORIGIN", + message: + "The request location context was blocked. The incoming value does not match patterns mapped inside your array configuration rules.", }, }) }) @@ -277,8 +279,9 @@ describe("signIn action", () => { signInURL: null, redirect: false, error: { - code: "UNTRUSTED_ORIGIN", - message: "The constructed origin URL is not trusted.", + code: "INVALID_TRUSTED_ORIGIN", + message: + "The request location context was blocked. The incoming value does not match patterns mapped inside your array configuration rules.", }, }) }) @@ -373,8 +376,9 @@ describe("signIn action", () => { signInURL: null, redirect: false, error: { - code: "UNTRUSTED_ORIGIN", - message: "The constructed origin URL is not trusted.", + code: "INVALID_TRUSTED_ORIGIN", + message: + "The request location context was blocked. The incoming value does not match patterns mapped inside your array configuration rules.", }, }) }) diff --git a/packages/core/test/actions/signOut/signOut.test.ts b/packages/core/test/actions/signOut/signOut.test.ts index 416d0df2..64d2af06 100644 --- a/packages/core/test/actions/signOut/signOut.test.ts +++ b/packages/core/test/actions/signOut/signOut.test.ts @@ -23,11 +23,11 @@ describe("signOut action", async () => { }, }) ) - expect(response.status).toBe(400) + expect(response.status).toBe(401) expect(await response.json()).toEqual({ - type: "AUTH_SECURITY_ERROR", - code: "SESSION_TOKEN_MISSING", - message: "The sessionToken is missing.", + type: "AUTH_FLOW", + code: "SESSION_NOT_FOUND", + message: "The session token is not found. There is no active session.", }) }) @@ -40,11 +40,11 @@ describe("signOut action", async () => { }, }) ) - expect(request.status).toBe(400) + expect(request.status).toBe(403) expect(await request.json()).toEqual({ - type: "AUTH_SECURITY_ERROR", - code: "CSRF_HEADER_MISSING", - message: "The CSRF header is missing.", + type: "AUTH_FLOW", + code: "CSRF_DOUBLE_SUBMIT_FAILED", + message: "The CSRF header is missing. Please refresh and try again.", }) }) @@ -62,11 +62,11 @@ describe("signOut action", async () => { }, }) ) - expect(request.status).toBe(400) + expect(request.status).toBe(403) expect(await request.json()).toEqual({ - type: "AUTH_SECURITY_ERROR", + type: "AUTH_FLOW", code: "CSRF_TOKEN_MISSING", - message: "The CSRF token is missing.", + message: "The CSRF token is missing. Please refresh and try again.", }) decodeJWTMock.mockRestore() }) @@ -146,11 +146,11 @@ describe("signOut action", async () => { }, }) ) - expect(request.status).toBe(400) + expect(request.status).toBe(403) expect(await request.json()).toEqual({ - type: "AUTH_SECURITY_ERROR", + type: "AUTH_FLOW", code: "CSRF_TOKEN_MISSING", - message: "The CSRF token is missing.", + message: "The CSRF token is missing. Please refresh and try again.", }) }) diff --git a/packages/core/test/api/signIn.test.ts b/packages/core/test/api/signIn.test.ts index 58287e92..97722d31 100644 --- a/packages/core/test/api/signIn.test.ts +++ b/packages/core/test/api/signIn.test.ts @@ -18,8 +18,9 @@ describe("signIn API", () => { signInURL: null, redirect: false, error: { - code: "INVALID_OAUTH_CONFIGURATION", - message: 'The OAuth provider "unsupported" is not configured.', + code: "UNSUPPORTED_OAUTH_CONFIGURATION", + message: + "An execution request flow was initialized targeting a specific identity provider code string that doesn't exist within initialized provider definitions.", }, toResponse: expect.any(Function), }) @@ -81,9 +82,9 @@ describe("signIn API", () => { signInURL: null, redirect: false, error: { - code: "INVALID_OAUTH_CONFIGURATION", + code: "INVALID_AUTH_CONFIGURATION", message: - "The URL cannot be constructed. Please set the BASE_URL environment variable or enable trustedProxyHeaders.", + "The system cannot establish request resolution routes. Provide a valid 'BASE_URL' system environment configuration value or setup trusted proxy headers.", }, toResponse: expect.any(Function), }) diff --git a/packages/core/test/api/signInCredentials.test.ts b/packages/core/test/api/signInCredentials.test.ts index ea5099fa..81cd99f4 100644 --- a/packages/core/test/api/signInCredentials.test.ts +++ b/packages/core/test/api/signInCredentials.test.ts @@ -76,9 +76,9 @@ describe("signInCredentials API", () => { redirectURL: null, headers: expect.any(Headers), error: { - code: "INVALID_OAUTH_CONFIGURATION", + code: "INVALID_AUTH_CONFIGURATION", message: - "The URL cannot be constructed. Please set the BASE_URL environment variable or enable trustedProxyHeaders.", + "The system cannot establish request resolution routes. Provide a valid 'BASE_URL' system environment configuration value or setup trusted proxy headers.", }, toResponse: expect.any(Function), }) diff --git a/packages/core/test/api/signOut.test.ts b/packages/core/test/api/signOut.test.ts index de640450..7b5b6efc 100644 --- a/packages/core/test/api/signOut.test.ts +++ b/packages/core/test/api/signOut.test.ts @@ -19,7 +19,9 @@ describe("signOut API", async () => { api.signOut({ headers: new Headers(), }) - ).rejects.toThrow(/The sessionToken is missing/) + ).rejects.toThrow( + /The context evaluation phase failed because the target identifier sessionToken could not be pulled from the cookies object context./ + ) }) test("signOut with valid session token", async () => { diff --git a/packages/core/test/api/signUp.test.ts b/packages/core/test/api/signUp.test.ts index f405d905..54abd425 100644 --- a/packages/core/test/api/signUp.test.ts +++ b/packages/core/test/api/signUp.test.ts @@ -60,7 +60,8 @@ describe("signUp API", () => { redirectURL: null, error: { code: "USER_CREATION_FAILED", - message: "Failed to create user with the provided payload.", + message: + "The custom lifecycle hook 'onCreateUser' aborted execution, thrown exception handling traps, or returned an unexpected null reference mapping.", }, headers: expect.any(Headers), toResponse: expect.any(Function), @@ -89,8 +90,9 @@ describe("signUp API", () => { redirectURL: null, headers: expect.any(Headers), error: { - code: "INVALID_IDENTITY_VALIDATION_FAILED", - message: expect.any(String), + code: "SCHEMA_PARSER_FAILED", + message: + "The schema validator failed to parse or execute the configured schema. This typically indicates a malformed schema definition or a runtime parser issue inside the selected validation adapter.", }, toResponse: expect.any(Function), }) @@ -106,9 +108,9 @@ describe("signUp API", () => { redirectURL: null, headers: expect.any(Headers), error: { - code: "INVALID_OAUTH_CONFIGURATION", + code: "INVALID_AUTH_CONFIGURATION", message: - "The URL cannot be constructed. Please set the BASE_URL environment variable or enable trustedProxyHeaders.", + "The system cannot establish request resolution routes. Provide a valid 'BASE_URL' system environment configuration value or setup trusted proxy headers.", }, toResponse: expect.any(Function), }) diff --git a/packages/core/test/api/updateSession.test.ts b/packages/core/test/api/updateSession.test.ts index ec2942cb..a69faeb6 100644 --- a/packages/core/test/api/updateSession.test.ts +++ b/packages/core/test/api/updateSession.test.ts @@ -29,7 +29,8 @@ describe("updateSession API", () => { redirectURL: null, error: { code: "UPDATE_SESSION_INVALID", - message: "Failed to update session.", + message: + "The internal call to 'refreshSession' completed, but returned a nullish value, meaning token mutation could not finish cleanly.", }, toResponse: expect.any(Function), }) diff --git a/packages/core/test/instance.test.ts b/packages/core/test/instance.test.ts index 8bdc36a5..73fbfb33 100644 --- a/packages/core/test/instance.test.ts +++ b/packages/core/test/instance.test.ts @@ -33,11 +33,11 @@ describe("createAuth", () => { }) ) - expect(response.status).toBe(400) + expect(response.status).toBe(401) expect(await response.json()).toEqual({ - type: "AUTH_SECURITY_ERROR", - code: "SESSION_TOKEN_MISSING", - message: "The sessionToken is missing.", + type: "AUTH_FLOW", + code: "SESSION_NOT_FOUND", + message: "The session token is not found. There is no active session.", }) }) diff --git a/packages/core/test/jose.test.ts b/packages/core/test/jose.test.ts index bb7d891f..fadf90ea 100644 --- a/packages/core/test/jose.test.ts +++ b/packages/core/test/jose.test.ts @@ -176,14 +176,14 @@ describe("createJoseInstance", () => { test("invalid secret", () => { expect(() => createAuth({ oauth: [] })).toThrow( - "AURA_AUTH_SECRET environment variable is not set and no secret was provided." + "Core security initialization failed because both 'AURA_AUTH_SECRET' and 'AUTH_SECRET' environment string keys are completely missing from runtime access contexts." ) }) test("invalid salt", () => { const secret = createSecretValue(32) expect(() => createAuth({ oauth: [], secret })).toThrow( - "AURA_AUTH_SALT or AUTH_SALT environment variable is not set. A salt value is required for key derivation." + "Core security initialization failed because both 'AURA_AUTH_SALT' and 'AUTH_SALT' environment string keys are completely missing from runtime access contexts." ) }) @@ -580,7 +580,7 @@ describe("createJoseInstance", () => { }, }) await expect(jwt.encodeJWT(payload)).rejects.toThrow( - /Single PEM key pairs from environment variables require 'signed' or 'encrypted' JWT mode. For 'sealed' mode, provide separate signing and encryption keys or a combined key object./ + /A configuration layout rule conflict was detected. A single asymmetric key pair structure was loaded but the session processing mode was set to 'sealed'./ ) }) @@ -615,7 +615,7 @@ describe("createJoseInstance", () => { }, }) await expect(jws.signJWS(payload)).rejects.toThrow( - /Multiples PEM Key Pairs from environment variables require 'sealed' JWT mode. For 'signed' or 'encrypted' modes, provide a single PEM key pair or a combined key object./ + /A configuration layout rule conflict was detected. Multiple asymmetric keys were passed but the runtime 'session.mode' parameter was not forced to 'sealed'./ ) }) diff --git a/packages/core/test/oauth.test.ts b/packages/core/test/oauth.test.ts index 9d720aad..12092340 100644 --- a/packages/core/test/oauth.test.ts +++ b/packages/core/test/oauth.test.ts @@ -24,7 +24,9 @@ describe("createBuiltInOAuthProviders", () => { authorizeURL: "https://example.com/authorize", } as OAuthProviderCredentials, ]) - ).toThrow('Invalid configuration for OAuth provider "oauth_provider"') + ).toThrow( + "The loaded configuration settings array failed standard library schema validation checks against required engine operational footprints." + ) }) test("create oauth config override", async () => { @@ -49,6 +51,8 @@ describe("createBuiltInOAuthProviders", () => { }) test("create oauth config with duplicated id", () => { - expect(() => createBuiltInOAuthProviders(["github", "github"])).toThrow('Duplicate OAuth provider id "github"') + expect(() => createBuiltInOAuthProviders(["github", "github"])).toThrow( + "The registration collection contains duplicate identifier keys. Unique registration indices are mandatory across tracking providers" + ) }) }) diff --git a/packages/elysia/package.json b/packages/elysia/package.json index 2a8be41e..5389ffe8 100644 --- a/packages/elysia/package.json +++ b/packages/elysia/package.json @@ -104,4 +104,4 @@ "elysia": ">=1.0.0" }, "packageManager": "pnpm@10.15.0" -} +} \ No newline at end of file diff --git a/packages/express/package.json b/packages/express/package.json index 732d61c0..d2bcfdfe 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -109,4 +109,4 @@ "express": ">=4.0.0" }, "packageManager": "pnpm@10.15.0" -} +} \ No newline at end of file diff --git a/packages/hono/package.json b/packages/hono/package.json index 40b75c6e..32887f97 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -107,4 +107,4 @@ "hono": ">=4.0.0" }, "packageManager": "pnpm@10.15.0" -} +} \ No newline at end of file diff --git a/packages/next/package.json b/packages/next/package.json index 8fe8e796..59135c2f 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -142,4 +142,4 @@ "react-dom": ">=19.0.0" }, "packageManager": "pnpm@10.15.0" -} +} \ No newline at end of file diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 4b8f0f63..1f7ea1d5 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -114,4 +114,4 @@ "react-router": ">=7.0.0" }, "packageManager": "pnpm@10.15.0" -} +} \ No newline at end of file diff --git a/packages/react/package.json b/packages/react/package.json index ae6d5244..dad8503f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -126,4 +126,4 @@ "@types/react": ">=19.0.0" }, "packageManager": "pnpm@10.15.0" -} +} \ No newline at end of file