diff --git a/.changeset/sour-pears-drop.md b/.changeset/sour-pears-drop.md new file mode 100644 index 00000000000..436d85fbc93 --- /dev/null +++ b/.changeset/sour-pears-drop.md @@ -0,0 +1,6 @@ +--- +'@clerk/backend': patch +'@clerk/nextjs': patch +--- + +Improve JSDoc comments to provide you with better IntelliSense information in your IDE diff --git a/package.json b/package.json index aa36fbc7a7f..3cc7a14cad5 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "turbo": "^2.0.14", "turbo-ignore": "^2.0.6", "typedoc": "0.27.6", + "typedoc-plugin-missing-exports": "3.1.0", "typescript": "catalog:repo", "verdaccio": "^5.26.3", "vitest": "3.0.2", diff --git a/packages/backend/src/jwt/verifyJwt.ts b/packages/backend/src/jwt/verifyJwt.ts index 8ceb2476c5b..4eabd424581 100644 --- a/packages/backend/src/jwt/verifyJwt.ts +++ b/packages/backend/src/jwt/verifyJwt.ts @@ -94,9 +94,25 @@ export function decodeJwt(token: string): JwtReturnType`](https://clerk.com/docs/components/organization/organization-switcher). * - * Common examples: - * - ["/orgs/:slug", "/orgs/:slug/(.*)"] - * - ["/orgs/:id", "/orgs/:id/(.*)"] - * - ["/app/:any/orgs/:slug", "/app/:any/orgs/:slug/(.*)"] + * @example + * ["/orgs/:slug", "/orgs/:slug/(.*)"] + * @example + * ["/orgs/:id", "/orgs/:id/(.*)"] + * @example + * ["/app/:any/orgs/:slug", "/app/:any/orgs/:slug/(.*)"] */ organizationPatterns?: Pattern[]; /** - * URL patterns for resources in the context of a clerk personal account (user-specific, outside any organization). - * If the route also matches the organizationPattern, the organizationPatterns takes precedence. + * URL patterns for resources that exist within the context of a [Clerk Personal Account](https://clerk.com/docs/organizations/organization-workspaces#organization-workspaces-in-the-clerk-dashboard:~:text=Personal%20account) (user-specific, outside any organization). * - * Common examples: - * - ["/user", "/user/(.*)"] - * - ["/user/:any", "/user/:any/(.*)"] + * If the route also matches the `organizationPattern` prop, the `organizationPattern` prop takes precedence. + * + * @example + * ["/user", "/user/(.*)"] + * @example + * ["/user/:any", "/user/:any/(.*)"] */ personalAccountPatterns?: Pattern[]; }; /** - * A pattern representing the structure of a URL path. - * In addition to a valid URL, may include: - * - Named path tokens prefixed with a colon (e.g., ":id", ":slug", ":any") - * - Wildcard token (e.g., ".(*)"), which will match the remainder of the path - * Examples: "/orgs/:slug", "/app/:any/orgs/:id", "/personal-account/(.*)" + * A `Pattern` is a `string` that represents the structure of a URL path. In addition to any valid URL, it may include: + * - Named path parameters prefixed with a colon (e.g., `:id`, `:slug`, `:any`). + * - Wildcard token, `(.*)`, which matches the remainder of the path. + * + * @example + * /orgs/:slug + * + * ```ts + * '/orgs/acmecorp' // matches (`:slug` value: acmecorp) + * '/orgs' // does not match + * '/orgs/acmecorp/settings' // does not match + * ``` + * + * @example + * /app/:any/orgs/:id + * + * ```ts + * '/app/petstore/orgs/org_123' // matches (`:id` value: org_123) + * '/app/dogstore/v2/orgs/org_123' // does not match + * ``` + * + * @example + * /personal-account/(.*) + * + * ```ts + * '/personal-account/settings' // matches + * '/personal-account' // does not match + * ``` */ type Pattern = string; diff --git a/packages/backend/src/tokens/verify.ts b/packages/backend/src/tokens/verify.ts index 523cee883be..30b149e7b72 100644 --- a/packages/backend/src/tokens/verify.ts +++ b/packages/backend/src/tokens/verify.ts @@ -8,7 +8,12 @@ import type { LoadClerkJWKFromRemoteOptions } from './keys'; import { loadClerkJWKFromLocal, loadClerkJWKFromRemote } from './keys'; export type VerifyTokenOptions = Omit & - Omit & { jwtKey?: string }; + Omit & { + /** + * Used to verify the session token in a networkless manner. Supply the PEM public key from the **[**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page -> Show JWT public key -> PEM Public Key** section in the Clerk Dashboard. **It's recommended to use [the environment variable](https://clerk.com/docs/deployments/clerk-environment-variables) instead.** For more information, refer to [Manual JWT verification](https://clerk.com/docs/backend-requests/handling/manual-jwt). + */ + jwtKey?: string; + }; export async function verifyToken( token: string, diff --git a/packages/nextjs/src/app-router/server/auth.ts b/packages/nextjs/src/app-router/server/auth.ts index 403dbf2710c..12e1acd30ec 100644 --- a/packages/nextjs/src/app-router/server/auth.ts +++ b/packages/nextjs/src/app-router/server/auth.ts @@ -11,13 +11,50 @@ import { createProtect } from '../../server/protect'; import { decryptClerkRequestData } from '../../server/utils'; import { buildRequestLike } from './utils'; -type Auth = AuthObject & { redirectToSignIn: RedirectFun> }; - +/** + * `Auth` object of the currently active user and the `redirectToSignIn()` method. + */ +type Auth = AuthObject & { + /** + * The `auth()` helper returns the `redirectToSignIn()` method, which you can use to redirect the user to the sign-in page. + * + * @param [returnBackUrl] {string | URL} - The URL to redirect the user back to after they sign in. + * + * @note + * `auth()` on the server-side can only access redirect URLs defined via [environment variables](https://clerk.com/docs/deployments/clerk-environment-variables#sign-in-and-sign-up-redirects) or [`clerkMiddleware` dynamic keys](https://clerk.com/docs/references/nextjs/clerk-middleware#dynamic-keys). + */ + redirectToSignIn: RedirectFun>; +}; export interface AuthFn { (): Promise; + /** + * `auth` includes a single property, the `protect()` method, which you can use in two ways: + * - to check if a user is authenticated (signed in) + * - to check if a user is authorized (has the correct roles or permissions) to access something, such as a component or a route handler + * + * The following table describes how auth.protect() behaves based on user authentication or authorization status: + * + * | Authenticated | Authorized | `auth.protect()` will | + * | - | - | - | + * | Yes | Yes | Return the [`Auth`](https://clerk.com/docs/references/backend/types/auth-object) object. | + * | Yes | No | Return a `404` error. | + * | No | No | Redirect the user to the sign-in page\*. | + * + * @important + * \*For non-document requests, such as API requests, `auth.protect()` returns a `404` error to users who aren't authenticated. + * + * `auth.protect()` can be used to check if a user is authenticated or authorized to access certain parts of your application or even entire routes. See detailed examples in the [dedicated guide](https://clerk.com/docs/organizations/verify-user-permissions). + */ protect: AuthProtect; } +/** + * The `auth()` helper returns the [`Auth`](https://clerk.com/docs/references/backend/types/auth-object) object of the currently active user, as well as the [`redirectToSignIn()`](https://clerk.com/docs/references/nextjs/auth#redirect-to-sign-in) method. + * + * - Only available for App Router. + * - Only works on the server-side, such as in Server Components, Route Handlers, and Server Actions. + * - Requires [`clerkMiddleware()`](https://clerk.com/docs/references/nextjs/clerk-middleware) to be configured. + */ export const auth: AuthFn = async () => { require('server-only'); diff --git a/packages/nextjs/src/app-router/server/currentUser.ts b/packages/nextjs/src/app-router/server/currentUser.ts index 1e7426843c6..073e2f35888 100644 --- a/packages/nextjs/src/app-router/server/currentUser.ts +++ b/packages/nextjs/src/app-router/server/currentUser.ts @@ -3,6 +3,29 @@ import type { User } from '@clerk/backend'; import { clerkClient } from '../../server/clerkClient'; import { auth } from './auth'; +/** + * The `currentUser` helper returns the [Backend User](https://clerk.com/docs/references/backend/types/backend-user) object of the currently active user. It can be used in Server Components, Route Handlers, and Server Actions. + * + * Under the hood, this helper: + * - calls `fetch()`, so it is automatically deduped per request. + * - uses the [`GET /v1/users/{user_id}`](https://clerk.com/docs/reference/backend-api/tag/Users#operation/GetUser) endpoint. + * - counts towards the [Backend API request rate limit](https://clerk.com/docs/backend-requests/resources/rate-limits#rate-limits). + * + * @example + * ```tsx + * // app/page.tsx + * + * import { currentUser } from '@clerk/nextjs/server' + * + * export default async function Page() { + * const user = await currentUser() + * + * if (!user) return
Not signed in
+ * + * return
Hello {user?.firstName}
+ * } + * ``` + */ export async function currentUser(): Promise { require('server-only'); diff --git a/packages/nextjs/src/server/buildClerkProps.ts b/packages/nextjs/src/server/buildClerkProps.ts index a6f51aa068c..5017f750680 100644 --- a/packages/nextjs/src/server/buildClerkProps.ts +++ b/packages/nextjs/src/server/buildClerkProps.ts @@ -6,24 +6,52 @@ import type { RequestLike } from './types'; type BuildClerkPropsInitState = { user?: User | null; session?: Session | null; organization?: Organization | null }; +type BuildClerkProps = (req: RequestLike, authState?: BuildClerkPropsInitState) => Record; + /** - * To enable Clerk SSR support, include this object to the `props` - * returned from `getServerSideProps`. This will automatically make the auth state available to - * the Clerk components and hooks during SSR, the hydration phase and CSR. + * Clerk uses `buildClerkProps` to inform the client-side helpers of the authentication state of the user. This function is used SSR in the `getServerSideProps` function of your Next.js application. + * + * @example + * **Basic usage** + * + * ```tsx + * // pages/myServerSidePage.tsx + * + * import { getAuth, buildClerkProps } from '@clerk/nextjs/server' + * import { GetServerSideProps } from 'next' + * + * export const getServerSideProps: GetServerSideProps = async (ctx) => { + * const { userId } = getAuth(ctx.req) + * + * if (!userId) { + * // handle user is not signed in. + * } + * + * // Load any data your application needs for the page using the userId + * return { props: { ...buildClerkProps(ctx.req) } } + * } + * ``` + * * @example - * import { getAuth } from '@clerk/nextjs/server'; + * **Usage with `clerkClient`** + * + * The `clerkClient` allows you to access the Clerk API. You can use this to retrieve or update data. + * + * ```tsx + * // pages/api/example.ts * - * export const getServerSideProps = ({ req }) => { - * const { authServerSideProps } = getAuth(req); - * const myData = getMyData(); + * import { getAuth, buildClerkProps, clerkClient } from '@clerk/nextjs/server' + * import { GetServerSideProps } from 'next' * - * return { - * props: { myData, authServerSideProps }, - * }; - * }; + * export const getServerSideProps: GetServerSideProps = async (ctx) => { + * const { userId } = getAuth(ctx.req) + * + * const user = userId ? await clerkClient().users.getUser(userId) : undefined + * + * return { props: { ...buildClerkProps(ctx.req, { user }) } } + * } + * ``` */ -type BuildClerkProps = (req: RequestLike, authState?: BuildClerkPropsInitState) => Record; - export const buildClerkProps: BuildClerkProps = (req, initialState = {}) => { const sanitizedAuthObject = getDynamicAuthData(req, initialState); diff --git a/packages/nextjs/src/server/clerkMiddleware.ts b/packages/nextjs/src/server/clerkMiddleware.ts index 271e7f6e86d..32593989f71 100644 --- a/packages/nextjs/src/server/clerkMiddleware.ts +++ b/packages/nextjs/src/server/clerkMiddleware.ts @@ -48,7 +48,14 @@ type ClerkMiddlewareHandler = ( event: NextMiddlewareEvtParam, ) => NextMiddlewareReturn; +/** + * The `clerkMiddleware()` function accepts an optional object. The following options are available. + * @interface + */ export type ClerkMiddlewareOptions = AuthenticateRequestOptions & { + /** + * If true, additional debug information will be logged to the console. + */ debug?: boolean; }; @@ -84,6 +91,9 @@ interface ClerkMiddleware { (request: NextMiddlewareRequestParam, event: NextMiddlewareEvtParam): NextMiddlewareReturn; } +/** + * The `clerkMiddleware()` helper integrates Clerk authentication into your Next.js application through Middleware. `clerkMiddleware()` is compatible with both the App and Pages routers. + */ // @ts-expect-error TS is not happy here. Will dig into it export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]) => { const [request, event] = parseRequestAndEvent(args); diff --git a/packages/nextjs/src/server/createGetAuth.ts b/packages/nextjs/src/server/createGetAuth.ts index 1f6106faeb9..09a3bd74cca 100644 --- a/packages/nextjs/src/server/createGetAuth.ts +++ b/packages/nextjs/src/server/createGetAuth.ts @@ -29,6 +29,85 @@ export const createGetAuth = ({ }; }); +/** + * The `getAuth()` helper retrieves authentication state from the request object. + * + * @note + * If you are using App Router, use the [`auth()` helper](https://clerk.com/docs/references/nextjs/auth) instead. + * + * @param req - The Next.js request object. + * @param [options] - An optional object that can be used to configure the behavior of the `getAuth()` function. + * @param [options.secretKey] - A string that represents the Secret Key used to sign the session token. If not provided, the Secret Key is retrieved from the environment variable `CLERK_SECRET_KEY`. + * @returns The `Auth` object. See the [Auth reference](https://clerk.com/docs/references/backend/types/auth-object) for more information. + * + * @example + * **Protect API routes** + * + * The following example demonstrates how to protect an API route by checking if the `userId` is present in the `getAuth()` response. + * + * ```tsx + * // app/api/example/route.ts + * import { getAuth } from '@clerk/nextjs/server' + * import type { NextApiRequest, NextApiResponse } from 'next' + * + * export default async function handler(req: NextApiRequest, res: NextApiResponse) { + * const { userId } = getAuth(req) + * + * if (!userId) { + * return res.status(401).json({ error: 'Not authenticated' }) + * } + * + * // Add logic that retrieves the data for the API route + * + * return res.status(200).json({ userId: userId }) + * } + * ``` + * + * @example + * **Usage with `getToken()`** + * + * `getAuth()` returns [`getToken()`](https://clerk.com/docs/references/backend/types/auth-object#get-token), which is a method that returns the current user's session token or a custom JWT template. + * + * ```tsx + * // app/api/example/route.ts + * + * import { getAuth } from '@clerk/nextjs/server' + * import type { NextApiRequest, NextApiResponse } from 'next' + * + * export default async function handler(req: NextApiRequest, res: NextApiResponse) { + * const { getToken } = getAuth(req) + * + * const token = await getToken({ template: 'supabase' }) + * + * // Add logic that retrieves the data + * // from your database using the token + * + * return res.status(200).json({}) + * } + * ``` + * + * @example + * **Usage with `clerkClient`** + * + * `clerkClient` is used to access the [Backend SDK](https://clerk.com/docs/references/backend/overview), which exposes Clerk's Backend API resources. You can use `getAuth()` to pass authentication information that many of the Backend SDK methods require, like the user's ID. + * + * ```tsx + * // app/api/example/route.ts + * + * import { clerkClient, getAuth } from '@clerk/nextjs/server' + * import type { NextApiRequest, NextApiResponse } from 'next' + * + * export default async function handler(req: NextApiRequest, res: NextApiResponse) { + * const { userId } = getAuth(req) + * + * const client = await clerkClient() + * + * const user = userId ? await client.users.getUser(userId) : null + * + * return res.status(200).json({}) + * } + * ``` + */ export const getAuth = createGetAuth({ debugLoggerName: 'getAuth()', noAuthStatusMessage: getAuthAuthHeaderMissing(), diff --git a/packages/nextjs/src/server/protect.ts b/packages/nextjs/src/server/protect.ts index 3c683b42216..ad601de804f 100644 --- a/packages/nextjs/src/server/protect.ts +++ b/packages/nextjs/src/server/protect.ts @@ -12,7 +12,16 @@ import type { import { constants as nextConstants } from '../constants'; import { isNextFetcher } from './nextFetcher'; -type AuthProtectOptions = { unauthorizedUrl?: string; unauthenticatedUrl?: string }; +type AuthProtectOptions = { + /** + * The URL to redirect the user to if they are not authorized. + */ + unauthorizedUrl?: string; + /** + * The URL to redirect the user to if they are not authenticated. + */ + unauthenticatedUrl?: string; +}; /** * Throws a Nextjs notFound error if user is not authenticated or authorized. diff --git a/packages/nextjs/typedoc.json b/packages/nextjs/typedoc.json new file mode 100644 index 00000000000..c09039e578b --- /dev/null +++ b/packages/nextjs/typedoc.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": [ + "./src/server/clerkMiddleware.ts", + "./src/app-router/server/currentUser.ts", + "./src/app-router/server/auth.ts", + "./src/server/buildClerkProps.ts", + "./src/server/createGetAuth.ts" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 135803c7aef..f22b665b3df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -214,6 +214,9 @@ importers: typedoc: specifier: 0.27.6 version: 0.27.6(typescript@5.6.3) + typedoc-plugin-missing-exports: + specifier: 3.1.0 + version: 3.1.0(typedoc@0.27.6(typescript@5.6.3)) typescript: specifier: catalog:repo version: 5.6.3 @@ -10448,6 +10451,7 @@ packages: lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.isinteger@4.0.4: resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} @@ -14062,6 +14066,11 @@ packages: resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} engines: {node: '>= 0.4'} + typedoc-plugin-missing-exports@3.1.0: + resolution: {integrity: sha512-Sogbaj+qDa21NjB3SlIw4JXSwmcl/WOjwiPNaVEcPhpNG/MiRTtpwV81cT7h1cbu9StpONFPbddYWR0KV/fTWA==} + peerDependencies: + typedoc: 0.26.x || 0.27.x + typedoc@0.27.6: resolution: {integrity: sha512-oBFRoh2Px6jFx366db0lLlihcalq/JzyCVp7Vaq1yphL/tbgx2e+bkpkCgJPunaPvPwoTOXSwasfklWHm7GfAw==} engines: {node: '>= 18'} @@ -31741,6 +31750,10 @@ snapshots: typed-array-buffer: 1.0.3 typed-array-byte-offset: 1.0.4 + typedoc-plugin-missing-exports@3.1.0(typedoc@0.27.6(typescript@5.6.3)): + dependencies: + typedoc: 0.27.6(typescript@5.6.3) + typedoc@0.27.6(typescript@5.6.3): dependencies: '@gerrit0/mini-shiki': 1.27.2 diff --git a/typedoc.config.mjs b/typedoc.config.mjs index c5f6540c7a2..c86624b0847 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -1,5 +1,6 @@ import path from 'node:path'; import fs from 'node:fs'; +import { OptionDefaults } from 'typedoc'; const IGNORE_LIST = [ '.DS_Store', @@ -30,14 +31,18 @@ const config = { json: './.typedoc/output.json', entryPointStrategy: 'packages', excludePrivate: true, - blockTags: ['@param', '@returns'], - modifierTags: ['@alpha', '@beta', '@experimental', '@deprecated'], + blockTags: [...OptionDefaults.blockTags, '@warning', '@note', '@important'], + modifierTags: [...OptionDefaults.modifierTags], + exclude: ['**/*+(.spec|.test).ts'], + plugin: ['typedoc-plugin-missing-exports'], packageOptions: { includeVersion: false, excludePrivate: true, sortEntryPoints: true, sort: 'alphabetical', excludeExternals: true, + excludeInternal: true, + excludeNotDocumented: true, gitRevision: 'main', }, entryPoints: getPackages(),