Skip to content

Commit

Permalink
fix(ts): reduce public type surface (#10557)
Browse files Browse the repository at this point in the history
* fix(ts): reudce public type surface

* change

* fix

* simplify

* simplify

* simplify

* chore: update links in index.ts

* mark `isClientError` as internal

---------

Co-authored-by: Nico Domino <yo@ndo.dev>
  • Loading branch information
balazsorban44 and ndom91 committed Apr 15, 2024
1 parent 855ece6 commit 6ddc329
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 264 deletions.
1 change: 1 addition & 0 deletions packages/core/src/errors.ts
Expand Up @@ -462,6 +462,7 @@ const clientErrors = new Set<ErrorType>([
* Used to only allow sending a certain subset of errors to the client.
* Errors are always logged on the server, but to prevent leaking sensitive information,
* only a subset of errors are sent to the client as-is.
* @internal
*/
export function isClientError(error: Error): error is AuthError {
if (error instanceof AuthError) return clientErrors.has(error.type)
Expand Down
231 changes: 224 additions & 7 deletions packages/core/src/index.ts
Expand Up @@ -49,18 +49,22 @@ import renderPage from "./lib/pages/index.js"
import { logger, setLogger, type LoggerInstance } from "./lib/utils/logger.js"
import { toInternalRequest, toResponse } from "./lib/utils/web.js"

import type { Adapter } from "./adapters.js"
import type { Adapter, AdapterSession, AdapterUser } from "./adapters.js"
import type {
Account,
AuthAction,
CallbacksOptions,
Awaitable,
CookiesOptions,
EventCallbacks,
DefaultSession,
PagesOptions,
Profile,
ResponseInternal,
Session,
Theme,
User,
} from "./types.js"
import type { Provider } from "./providers/index.js"
import { JWTOptions } from "./jwt.js"
import type { CredentialInput, Provider } from "./providers/index.js"
import { JWT, JWTOptions } from "./jwt.js"
import { isAuthAction } from "./lib/utils/actions.js"

export { skipCSRFCheck, raw, setEnvDefaults, createActionURL, isAuthAction }
Expand Down Expand Up @@ -296,7 +300,182 @@ export interface AuthConfig {
* Callbacks are *extremely powerful*, especially in scenarios involving JSON Web Tokens
* as they **allow you to implement access controls without a database** and to **integrate with external databases or APIs**.
*/
callbacks?: Partial<CallbacksOptions>
callbacks?: {
/**
* Controls whether a user is allowed to sign in or not.
* Returning `true` continues the sign-in flow.
* Returning `false` or throwing an error will stop the sign-in flow and redirect the user to the error page.
* Returning a string will redirect the user to the specified URL.
*
* Unhandled errors will throw an `AccessDenied` with the message set to the original error.
*
* [`AccessDenied`](https://authjs.dev/reference/errors#accessdenied)
*
* @example
* ```ts
* callbacks: {
* async signIn({ profile }) {
* // Only allow sign in for users with email addresses ending with "yourdomain.com"
* return profile?.email?.endsWith("@yourdomain.com")
* }
* ```
*/
signIn?: (params: {
user: User | AdapterUser
account: Account | null
/**
* If OAuth provider is used, it contains the full
* OAuth profile returned by your provider.
*/
profile?: Profile
/**
* If Email provider is used, on the first call, it contains a
* `verificationRequest: true` property to indicate it is being triggered in the verification request flow.
* When the callback is invoked after a user has clicked on a sign in link,
* this property will not be present. You can check for the `verificationRequest` property
* to avoid sending emails to addresses or domains on a blocklist or to only explicitly generate them
* for email address in an allow list.
*/
email?: {
verificationRequest?: boolean
}
/** If Credentials provider is used, it contains the user credentials */
credentials?: Record<string, CredentialInput>
}) => Awaitable<boolean | string>
/**
* This callback is called anytime the user is redirected to a callback URL (i.e. on signin or signout).
* By default only URLs on the same host as the origin are allowed.
* You can use this callback to customise that behaviour.
*
* [Documentation](https://authjs.dev/reference/core/types#redirect)
*
* @example
* callbacks: {
* async redirect({ url, baseUrl }) {
* // Allows relative callback URLs
* if (url.startsWith("/")) return `${baseUrl}${url}`
*
* // Allows callback URLs on the same origin
* if (new URL(url).origin === baseUrl) return url
*
* return baseUrl
* }
* }
*/
redirect?: (params: {
/** URL provided as callback URL by the client */
url: string
/** Default base URL of site (can be used as fallback) */
baseUrl: string
}) => Awaitable<string>
/**
* This callback is called whenever a session is checked.
* (i.e. when invoking the `/api/session` endpoint, using `useSession` or `getSession`).
* The return value will be exposed to the client, so be careful what you return here!
* If you want to make anything available to the client which you've added to the token
* through the JWT callback, you have to explicitly return it here as well.
*
* :::note
* ⚠ By default, only a subset (email, name, image)
* of the token is returned for increased security.
* :::
*
* The token argument is only available when using the jwt session strategy, and the
* user argument is only available when using the database session strategy.
*
* [`jwt` callback](https://authjs.dev/reference/core/types#jwt)
*
* @example
* ```ts
* callbacks: {
* async session({ session, token, user }) {
* // Send properties to the client, like an access_token from a provider.
* session.accessToken = token.accessToken
*
* return session
* }
* }
* ```
*/
session?: (
params: ({
session: { user: AdapterUser } & AdapterSession
/** Available when {@link AuthConfig.session} is set to `strategy: "database"`. */
user: AdapterUser
} & {
session: Session
/** Available when {@link AuthConfig.session} is set to `strategy: "jwt"` */
token: JWT
}) & {
/**
* Available when using {@link AuthConfig.session} `strategy: "database"` and an update is triggered for the session.
*
* :::note
* You should validate this data before using it.
* :::
*/
newSession: any
trigger?: "update"
}
) => Awaitable<Session | DefaultSession>
/**
* This callback is called whenever a JSON Web Token is created (i.e. at sign in)
* or updated (i.e whenever a session is accessed in the client). Anything you
* return here will be saved in the JWT and forwarded to the session callback.
* There you can control what should be returned to the client. Anything else
* will be kept from your frontend. The JWT is encrypted by default via your
* AUTH_SECRET environment variable.
*
* [`session` callback](https://authjs.dev/reference/core/types#session)
*/
jwt?: (params: {
/**
* When `trigger` is `"signIn"` or `"signUp"`, it will be a subset of {@link JWT},
* `name`, `email` and `image` will be included.
*
* Otherwise, it will be the full {@link JWT} for subsequent calls.
*/
token: JWT
/**
* Either the result of the {@link OAuthConfig.profile} or the {@link CredentialsConfig.authorize} callback.
* @note available when `trigger` is `"signIn"` or `"signUp"`.
*
* Resources:
* - [Credentials Provider](https://authjs.dev/getting-started/authentication/credentials)
* - [User database model](https://authjs.dev/guides/creating-a-database-adapter#user-management)
*/
user: User | AdapterUser
/**
* Contains information about the provider that was used to sign in.
* Also includes {@link TokenSet}
* @note available when `trigger` is `"signIn"` or `"signUp"`
*/
account: Account | null
/**
* The OAuth profile returned from your provider.
* (In case of OIDC it will be the decoded ID Token or /userinfo response)
* @note available when `trigger` is `"signIn"`.
*/
profile?: Profile
/**
* Check why was the jwt callback invoked. Possible reasons are:
* - user sign-in: First time the callback is invoked, `user`, `profile` and `account` will be present.
* - user sign-up: a user is created for the first time in the database (when {@link AuthConfig.session}.strategy is set to `"database"`)
* - update event: Triggered by the `useSession().update` method.
* In case of the latter, `trigger` will be `undefined`.
*/
trigger?: "signIn" | "signUp" | "update"
/** @deprecated use `trigger === "signUp"` instead */
isNewUser?: boolean
/**
* When using {@link AuthConfig.session} `strategy: "jwt"`, this is the data
* sent from the client via the `useSession().update` method.
*
* ⚠ Note, you should validate this data before using it.
*/
session?: any
}) => Awaitable<JWT | null>
}
/**
* Events are asynchronous functions that do not return a response, they are useful for audit logging.
* You can specify a handler for any of these events below - e.g. for debugging or to create an audit log.
Expand All @@ -307,7 +486,45 @@ export interface AuthConfig {
*
* @default {}
*/
events?: Partial<EventCallbacks>
events?: {
/**
* If using a `credentials` type auth, the user is the raw response from your
* credential provider.
* For other providers, you'll get the User object from your adapter, the account,
* and an indicator if the user was new to your Adapter.
*/
signIn?: (message: {
user: User
account: Account | null
profile?: Profile
isNewUser?: boolean
}) => Awaitable<void>
/**
* The message object will contain one of these depending on
* if you use JWT or database persisted sessions:
* - `token`: The JWT for this session.
* - `session`: The session object from your adapter that is being ended.
*/
signOut?: (
message:
| { session: Awaited<ReturnType<Required<Adapter>["deleteSession"]>> }
| { token: Awaited<ReturnType<JWTOptions["decode"]>> }
) => Awaitable<void>
createUser?: (message: { user: User }) => Awaitable<void>
updateUser?: (message: { user: User }) => Awaitable<void>
linkAccount?: (message: {
user: User | AdapterUser
account: Account
profile: User | AdapterUser
}) => Awaitable<void>
/**
* The message object will contain one of these depending on
* if you use JWT or database persisted sessions:
* - `token`: The JWT for this session.
* - `session`: The session object from your adapter.
*/
session?: (message: { session: Session; token: JWT }) => Awaitable<void>
}
/** You can use the adapter option to pass in your database adapter. */
adapter?: Adapter
/**
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/lib/actions/signout.ts
Expand Up @@ -8,15 +8,14 @@ import type { Cookie, SessionStore } from "../utils/cookie.js"
* If the session strategy is database,
* The session is also deleted from the database.
* In any case, the session cookie is cleared and
* {@link EventCallbacks.signOut} is emitted.
* {@link AuthConfig["events"].signOut} is emitted.
*/
export async function signOut(
cookies: Cookie[],
sessionStore: SessionStore,
options: InternalOptions
): Promise<ResponseInternal> {
const { jwt, events, callbackUrl: redirect, logger, session } = options

const sessionToken = sessionStore.value
if (!sessionToken) return { redirect, cookies }

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/lib/index.ts
Expand Up @@ -5,7 +5,8 @@ import renderPage from "./pages/index.js"
import * as actions from "./actions/index.js"
import { validateCSRF } from "./actions/callback/oauth/csrf-token.js"

import type { AuthConfig, RequestInternal, ResponseInternal } from "../types.js"
import type { RequestInternal, ResponseInternal } from "../types.js"
import type { AuthConfig } from "../index.js"

/** @internal */
export async function AuthInternal(
Expand Down
15 changes: 5 additions & 10 deletions packages/core/src/lib/init.ts
Expand Up @@ -8,13 +8,8 @@ import parseProviders from "./utils/providers.js"
import { logger, type LoggerInstance } from "./utils/logger.js"
import { merge } from "./utils/merge.js"

import type {
AuthConfig,
CallbacksOptions,
EventCallbacks,
InternalOptions,
RequestInternal,
} from "../types.js"
import type { InternalOptions, RequestInternal } from "../types.js"
import type { AuthConfig } from "../index.js"

interface InitParams {
url: URL
Expand All @@ -31,7 +26,7 @@ interface InitParams {
cookies: RequestInternal["cookies"]
}

export const defaultCallbacks: CallbacksOptions = {
export const defaultCallbacks: InternalOptions["callbacks"] = {
signIn() {
return true
},
Expand Down Expand Up @@ -201,9 +196,9 @@ type Method = (...args: any[]) => Promise<any>

/** Wraps an object of methods and adds error handling. */
function eventsErrorHandler(
methods: Partial<EventCallbacks>,
methods: Partial<InternalOptions["events"]>,
logger: LoggerInstance
): Partial<EventCallbacks> {
): Partial<InternalOptions["events"]> {
return Object.keys(methods).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/lib/utils/assert.ts
Expand Up @@ -14,9 +14,10 @@ import {
UntrustedHost,
} from "../../errors.js"

import type { AuthConfig, RequestInternal, SemverString } from "../../types.js"
import type { RequestInternal, SemverString } from "../../types.js"
import type { WarningCode } from "./logger.js"
import { Adapter } from "../../adapters.js"
import type { AuthConfig } from "../../index.js"

type ConfigError =
| InvalidCallbackUrl
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/lib/utils/env.ts
@@ -1,6 +1,7 @@
import type { AuthAction, AuthConfig } from "../../types.js"
import type { AuthAction } from "../../types.js"
import { MissingSecret } from "../../errors.js"
import { logger } from "./logger.js"
import type { AuthConfig } from "../../index.js"

/** Set default env variables on the config object */
export function setEnvDefaults(envObject: any, config: AuthConfig) {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/lib/utils/providers.ts
Expand Up @@ -9,7 +9,8 @@ import type {
ProfileCallback,
Provider,
} from "../../providers/index.js"
import type { AuthConfig, InternalProvider, Profile } from "../../types.js"
import type { InternalProvider, Profile } from "../../types.js"
import type { AuthConfig } from "../../index.js"

/**
* Adds `signinUrl` and `callbackUrl` to each provider
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/lib/utils/web.ts
Expand Up @@ -4,11 +4,11 @@ import { logger } from "./logger.js"

import type {
AuthAction,
AuthConfig,
RequestInternal,
ResponseInternal,
} from "../../types.js"
import { isAuthAction } from "./actions.js"
import type { AuthConfig } from "../../index.js"

async function getBody(req: Request): Promise<Record<string, any> | undefined> {
if (!("body" in req) || !req.body || req.method !== "POST") return
Expand Down
9 changes: 2 additions & 7 deletions packages/core/src/providers/oauth.ts
@@ -1,12 +1,7 @@
import type { Client } from "oauth4webapi"
import type { CommonProviderOptions } from "../providers/index.js"
import type {
AuthConfig,
Awaitable,
Profile,
TokenSet,
User,
} from "../types.js"
import type { Awaitable, Profile, TokenSet, User } from "../types.js"
import type { AuthConfig } from "../index.js"

// TODO: fix types
type AuthorizationParameters = any
Expand Down

0 comments on commit 6ddc329

Please sign in to comment.