Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/actions/callback/access-token.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -65,32 +65,32 @@ 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)
if (!token.success) {
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: {
error: data.error,
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 })
Comment on lines 70 to +94

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Malformed JSON is currently reported as an "unknown" OAuth failure in both callback fetchers.

Both implementations call response.json() inside the same broad try that also handles transport failures. If the provider returns a non-JSON or truncated body, the resulting parse error falls into the generic catch and gets reclassified as UNKNOWN_OAUTH_ACCESS_TOKEN_ERROR / UNKNOWN_OAUTH_USER_INFO_ERROR instead of the existing *_RES_FORMAT codes.

  • packages/core/src/actions/callback/access-token.ts#L70-L94: catch JSON parse failures separately and map them to INVALID_OAUTH_ACCESS_TOKEN_RES_FORMAT.
  • packages/core/src/actions/callback/userinfo.ts#L64-L84: catch JSON parse failures separately and map them to INVALID_OAUTH_USER_INFO_RES_FORMAT.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/actions/callback/access-token.ts` around lines 70 - 94, The
JSON.parse step (response.json()) needs its own try/catch so parse failures are
classified as response-format errors rather than generic transport errors: wrap
the response.json() call in a small try block and if it throws, log via logger
and throw an AuraAuthError with code "INVALID_OAUTH_ACCESS_TOKEN_RES_FORMAT" in
access-token.ts (affecting the logic around
OAuthAccessTokenResponse/OAuthAccessTokenErrorResponse handling), and similarly
throw "INVALID_OAUTH_USER_INFO_RES_FORMAT" in userinfo.ts (around the OAuth user
info response validation). Keep the outer try to handle transport/network
failures and preserve existing isAuraAuthError rethrows and other error codes;
only reclassify JSON parse exceptions to the new *_RES_FORMAT codes and include
the original parse error as the cause when creating the AuraAuthError.

}
}
17 changes: 7 additions & 10 deletions packages/core/src/actions/callback/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
},
Expand Down Expand Up @@ -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 }
)
Expand All @@ -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" })
}
}

Expand Down
26 changes: 8 additions & 18 deletions packages/core/src/actions/callback/userinfo.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -20,7 +20,7 @@ import { isCustomUserInfoFunction } from "@/shared/assert.ts"
const getDefaultUserInfo = (profile: Record<string, string>): 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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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 })
}
}

Expand Down Expand Up @@ -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 })
}
}
6 changes: 3 additions & 3 deletions packages/core/src/actions/signIn/authorization-url.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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)
Comment on lines 23 to 27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap new URL(baseURL) so malformed provider URLs still surface as AuraAuthError.

The truthiness check only catches missing values. A malformed configured URL still throws a native TypeError on new URL(baseURL), which bypasses the INVALID_OAUTH_PROVIDER_URL_CONFIG path and leaks an unstandardized error from the sign-in entrypoint.

Suggested fix
 export const buildAuthorizationURL = (
     oauth: OAuthProvider,
     redirect_uri: string,
     state: string,
     code_challenge: string,
     code_challenge_method: string
 ): string => {
     const authorizeConfig = oauth.authorize
     const baseURL = typeof authorizeConfig === "string" ? authorizeConfig : (authorizeConfig?.url ?? oauth.authorizeURL)
     if (!baseURL) {
         throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_URL_CONFIG" })
     }
-    const url = new URL(baseURL)
+    let url: URL
+    try {
+        url = new URL(baseURL)
+    } catch (cause) {
+        throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_URL_CONFIG", cause })
+    }
     const authorizeParams = typeof authorizeConfig === "string" ? undefined : authorizeConfig?.params
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 baseURL = typeof authorizeConfig === "string" ? authorizeConfig : (authorizeConfig?.url ?? oauth.authorizeURL)
if (!baseURL) {
throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_URL_CONFIG" })
}
let url: URL
try {
url = new URL(baseURL)
} catch (cause) {
throw new AuraAuthError({ code: "INVALID_OAUTH_PROVIDER_URL_CONFIG", cause })
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/actions/signIn/authorization-url.ts` around lines 23 - 27,
The code currently constructs the provider URL with new URL(baseURL) which can
throw a native TypeError for malformed strings; wrap the URL construction in a
try/catch around the new URL(baseURL) call in the authorization URL logic (the
spot using authorizeConfig/baseURL and creating url) and on any exception
rethrow an AuraAuthError with code "INVALID_OAUTH_PROVIDER_URL_CONFIG" (include
the original error message/details in the AuraAuthError payload or log) so
malformed provider URLs surface as the standardized AuraAuthError instead of
leaking a native TypeError.

const authorizeParams = typeof authorizeConfig === "string" ? undefined : authorizeConfig?.params
Expand Down Expand Up @@ -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 {
Expand Down
19 changes: 6 additions & 13 deletions packages/core/src/actions/signIn/authorization.ts
Original file line number Diff line number Diff line change
@@ -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).
Expand Down Expand Up @@ -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 })
}
}

Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/actions/signUp/signUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { SignUpConfig } from "@/@types/config.ts"
const signUpConfig = (config: SignUpConfig<any, any>) => {
return createEndpointConfig({
schemas: {
body: config?.schema ?? z.object({}),
body: config?.schema ?? z.object(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Zod v4, is z.object() without a shape parameter supported, and what is the recommended empty-object schema fallback?

💡 Result:

In Zod v4, passing an empty object shape—specifically z.object({})—is fully supported and intentionally defined to represent an object that permits no properties [1]. When using z.object({}), the inferred type is Record<string, never>, which explicitly indicates an object with no keys [1][2]. Regarding an empty-object schema fallback: 1. Behavioral Change: In Zod v4, the behavior of .default() has been tightened [3]. Specifically, .default() now requires a value that strictly matches the output type of the schema [4][3]. Consequently, using .default({}) on an object schema is only valid if the schema itself is compatible with an empty object (i.e., it has no required fields, or all its fields have default values) [4][3][5]. 2. Recommended Alternatives: - Use .prefault(): If you intend to provide a default value that matches the input type and is parsed, use the .prefault() API, which was introduced in Zod v4 to provide a more flexible alternative to .default() [3][5]. - Ensure Compatibility: If you must use .default({}), ensure all fields within the object schema have defaults or are optional, as Zod v4 applies defaults more aggressively than previous versions [3][5][6]. As a technical note, while z.object({}) is valid, it is intentionally strict regarding the properties it accepts [2]. If you require an object that can accept any properties, you should use z.looseObject({}) (or equivalent loose configurations) instead, which allows for additional keys [1][7].

Citations:


Use z.object({}) for the empty-body fallback
In Zod v4, the empty-object schema fallback should be an explicit empty shape (z.object({})); relying on z.object() with no shape is not the intended API for constructing an object schema.

Suggested fix
-            body: config?.schema ?? z.object(),
+            body: config?.schema ?? z.object({}),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/actions/signUp/signUp.ts` at line 10, The fallback empty
Zod schema uses z.object() which is not the intended API in Zod v4; update the
fallback in the sign-up action so that body: config?.schema ?? z.object({}) uses
an explicit empty shape. Locate the occurrence of config?.schema and replace the
z.object() fallback with z.object({}) (e.g., in the signUp handler where body is
assigned) to ensure a proper empty-object schema.

searchParams: RedirectOptionsSchema,
},
})
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/api/credentials.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand All @@ -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" },
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/api/signIn.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Comment on lines +77 to 80

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use userMessage (not internal message) when projecting AuraAuthError into API payloads.
These catch paths now read error.message, but AuraAuthError separates internal diagnostics (message) from user-facing text (userMessage). This creates inconsistent client messaging and can expose internal wording.

  • packages/core/src/api/signIn.ts#L77-L80: set message = error.userMessage when isAuraAuthError(error) is true.
  • packages/core/src/api/credentials.ts#L65-L68: set message = error.userMessage for Aura errors before building the error object.
  • packages/core/src/api/signOut.ts#L49-L52: set message = error.userMessage in the Aura error branch.
  • packages/core/src/api/signUp.ts#L67-L70: set message = error.userMessage in the Aura error branch.
  • packages/core/src/api/updateSession.ts#L66-L69: set message = error.userMessage in the Aura error branch.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/api/signIn.ts` around lines 77 - 80, When projecting
AuraAuthError into API payloads, replace uses of the internal error.message with
the user-facing error.userMessage: inside the isAuraAuthError(error) branches
(e.g., in signIn.ts where code = error.code and message = error.message), set
message = error.userMessage; make the same change in the corresponding Aura
error branches in credentials.ts, signOut.ts, signUp.ts, and updateSession.ts so
each branch uses error.userMessage when building the response object (preserve
existing code = error.code behavior and only swap the message source).

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/api/signOut.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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
}
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/api/signUp.ts
Original file line number Diff line number Diff line change
@@ -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 <Payload extends Record<string, unknown> = Record<string, unknown>>({
ctx,
Expand All @@ -26,7 +26,7 @@ export const signUp = async <Payload extends Record<string, unknown> = Record<st
payload,
})
if (!user) {
throw new AuthValidationError("USER_CREATION_FAILED", "Failed to create user with the provided payload.")
throw new AuraAuthError({ code: "USER_CREATION_FAILED" })
}
const sessionToken = await sessionStrategy.createSession(user)
const csrfToken = await createCSRF(ctx.jose)
Expand Down Expand Up @@ -64,7 +64,7 @@ export const signUp = async <Payload extends Record<string, unknown> = Record<st
} catch (error) {
let code = "SIGN_UP_ERROR"
let message = "An error occurred during sign-up."
if (isAuthErrorWithCode(error)) {
if (isAuraAuthError(error)) {
code = error.code
message = error.message
}
Expand Down
Loading
Loading