Skip to content

Commit 4a8fe40

Browse files
authored
chore(backend): Add Typedoc configuration (#5751)
1 parent 1d5a306 commit 4a8fe40

File tree

12 files changed

+121
-18
lines changed

12 files changed

+121
-18
lines changed

.changeset/true-bears-divide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': patch
3+
---
4+
5+
Improve JSDoc comments

.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,9 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
139139
"clerk-react/use-sign-in.mdx",
140140
"clerk-react/use-sign-up.mdx",
141141
"clerk-react/use-user.mdx",
142+
"backend/verify-token-options.mdx",
143+
"backend/verify-token.mdx",
144+
"backend/verify-webhook-options.mdx",
145+
"backend/verify-webhook.mdx",
142146
]
143147
`;

.typedoc/__tests__/file-structure.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('Typedoc output', () => {
3232

3333
expect(folders).toMatchInlineSnapshot(`
3434
[
35+
"backend",
3536
"clerk-react",
3637
"nextjs",
3738
"shared",

.typedoc/custom-plugin.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ function getCatchAllReplacements() {
9898
pattern: /\*\*Default\*\* `([^`]+)`/g,
9999
replace: 'Defaults to `$1`.',
100100
},
101+
{
102+
/**
103+
* By default, `@example` is output with "**Example** `value`". We want to capture the value and place it inside "Example: `value`."
104+
*/
105+
pattern: /\*\*Example\*\* `([^`]+)`/g,
106+
replace: 'Example: `$1`.',
107+
},
101108
];
102109
}
103110

packages/backend/src/jwt/verifyJwt.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ export function decodeJwt(token: string): JwtReturnType<Jwt, TokenVerificationEr
9494
return { data };
9595
}
9696

97+
/**
98+
* @inline
99+
*/
97100
export type VerifyJwtOptions = {
98101
/**
99102
* A string or list of [audiences](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3). If passed, it is checked against the `aud` claim in the token.
@@ -103,7 +106,7 @@ export type VerifyJwtOptions = {
103106
* An allowlist of origins to verify against, to protect your application from the subdomain cookie leaking attack.
104107
* @example
105108
* ```ts
106-
* authorizedParties: ['http://localhost:3000', 'https://example.com']
109+
* ['http://localhost:3000', 'https://example.com']
107110
* ```
108111
*/
109112
authorizedParties?: string[];

packages/backend/src/tokens/__tests__/authObjects.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe('makeAuthObjectSerializable', () => {
1010
const serializableAuthObject = makeAuthObjectSerializable(authObject);
1111

1212
for (const key in serializableAuthObject) {
13+
// @ts-expect-error - Testing
1314
expect(typeof serializableAuthObject[key]).not.toBe('function');
1415
}
1516
});

packages/backend/src/tokens/__tests__/request.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ const mockRequest = (headers = {}, requestUrl = 'http://clerk.com/path') => {
231231
};
232232

233233
/* An otherwise bare state on a request. */
234+
// @ts-expect-error - Testing
234235
const mockOptions = (options?) => {
235236
return {
236237
secretKey: 'deadbeef',
@@ -249,10 +250,12 @@ const mockOptions = (options?) => {
249250
} satisfies AuthenticateRequestOptions;
250251
};
251252

253+
// @ts-expect-error - Testing
252254
const mockRequestWithHeaderAuth = (headers?, requestUrl?) => {
253255
return mockRequest({ authorization: `Bearer ${mockJwt}`, ...headers }, requestUrl);
254256
};
255257

258+
// @ts-expect-error - Testing
256259
const mockRequestWithCookies = (headers?, cookies = {}, requestUrl?) => {
257260
const cookieStr = Object.entries(cookies)
258261
.map(([k, v]) => `${k}=${v}`)

packages/backend/src/tokens/keys.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ export function loadClerkJWKFromLocal(localKey?: string): JsonWebKey {
8484
return getFromCache(LocalJwkKid);
8585
}
8686

87+
/**
88+
* @internal
89+
*/
8790
export type LoadClerkJWKFromRemoteOptions = {
8891
/**
8992
* @internal
@@ -94,15 +97,15 @@ export type LoadClerkJWKFromRemoteOptions = {
9497
*/
9598
jwksCacheTtlInMs?: number;
9699
/**
97-
* A flag to skip ignore cache and always fetch JWKS before each jwt verification.
100+
* A flag to ignore the JWKS cache and always fetch JWKS before each JWT verification.
98101
*/
99102
skipJwksCache?: boolean;
100103
/**
101104
* The Clerk Secret Key from the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard.
102105
*/
103106
secretKey?: string;
104107
/**
105-
* The [Clerk Backend API](https://clerk.com/docs/reference/backend-api) endpoint.
108+
* The [Clerk Backend API](https://clerk.com/docs/reference/backend-api){{ target: '_blank' }} endpoint.
106109
* @default 'https://api.clerk.com'
107110
*/
108111
apiUrl?: string;

packages/backend/src/tokens/verify.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { decodeJwt, verifyJwt } from '../jwt/verifyJwt';
77
import type { LoadClerkJWKFromRemoteOptions } from './keys';
88
import { loadClerkJWKFromLocal, loadClerkJWKFromRemote } from './keys';
99

10+
/**
11+
* @interface
12+
*/
1013
export type VerifyTokenOptions = Omit<VerifyJwtOptions, 'key'> &
1114
Omit<LoadClerkJWKFromRemoteOptions, 'kid'> & {
1215
/**
@@ -15,6 +18,70 @@ export type VerifyTokenOptions = Omit<VerifyJwtOptions, 'key'> &
1518
jwtKey?: string;
1619
};
1720

21+
/**
22+
* > [!WARNING]
23+
* > This is a lower-level method intended for more advanced use-cases. It's recommended to use [`authenticateRequest()`](https://clerk.com/docs/references/backend/authenticate-request), which fully authenticates a token passed from the `request` object.
24+
*
25+
* Verifies a Clerk-generated token signature. Networkless if the `jwtKey` is provided. Otherwise, performs a network call to retrieve the JWKS from the [Backend API](https://clerk.com/docs/reference/backend-api/tag/JWKS#operation/GetJWKS){{ target: '_blank' }}.
26+
*
27+
* @param token - The token to verify.
28+
* @param options - Options for verifying the token.
29+
*
30+
* @example
31+
*
32+
* The following example demonstrates how to use the [JavaScript Backend SDK](https://clerk.com/docs/references/backend/overview) to verify the token signature.
33+
*
34+
* In the following example:
35+
*
36+
* 1. The **JWKS Public Key** from the Clerk Dashboard is set in the environment variable `CLERK_JWT_KEY`.
37+
* 1. The session token is retrieved from the `__session` cookie or the Authorization header.
38+
* 1. The token is verified in a networkless manner by passing the `jwtKey` prop.
39+
* 1. The `authorizedParties` prop is passed to verify that the session token is generated from the expected frontend application.
40+
* 1. If the token is valid, the response contains the verified token.
41+
*
42+
* ```ts
43+
* import { verifyToken } from '@clerk/backend'
44+
* import { cookies } from 'next/headers'
45+
*
46+
* export async function GET(request: Request) {
47+
* const cookieStore = cookies()
48+
* const sessToken = cookieStore.get('__session')?.value
49+
* const bearerToken = request.headers.get('Authorization')?.replace('Bearer ', '')
50+
* const token = sessToken || bearerToken
51+
*
52+
* if (!token) {
53+
* return Response.json({ error: 'Token not found. User must sign in.' }, { status: 401 })
54+
* }
55+
*
56+
* try {
57+
* const verifiedToken = await verifyToken(token, {
58+
* jwtKey: process.env.CLERK_JWT_KEY,
59+
* authorizedParties: ['http://localhost:3001', 'api.example.com'], // Replace with your authorized parties
60+
* })
61+
*
62+
* return Response.json({ verifiedToken })
63+
* } catch (error) {
64+
* return Response.json({ error: 'Token not verified.' }, { status: 401 })
65+
* }
66+
* }
67+
* ```
68+
*
69+
* If the token is valid, the response will contain a JSON object that looks something like this:
70+
*
71+
* ```json
72+
* {
73+
* "verifiedToken": {
74+
* "azp": "http://localhost:3000",
75+
* "exp": 1687906422,
76+
* "iat": 1687906362,
77+
* "iss": "https://magical-marmoset-51.clerk.accounts.dev",
78+
* "nbf": 1687906352,
79+
* "sid": "sess_2Ro7e2IxrffdqBboq8KfB6eGbIy",
80+
* "sub": "user_2RfWKJREkjKbHZy0Wqa5qrHeAnb"
81+
* }
82+
* }
83+
* ```
84+
*/
1885
export async function verifyToken(
1986
token: string,
2087
options: VerifyTokenOptions,

packages/backend/src/webhooks.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { Webhook } from 'svix';
44

55
import type { WebhookEvent } from './api/resources/Webhooks';
66

7+
/**
8+
* @inline
9+
*/
710
export type VerifyWebhookOptions = {
11+
/**
12+
* The signing secret for the webhook. It's recommended to use the [`CLERK_WEBHOOK_SIGNING_SECRET` environment variable](https://clerk.com/docs/deployments/clerk-environment-variables#webhooks) instead.
13+
*/
814
signingSecret?: string;
915
};
1016

@@ -17,33 +23,32 @@ const REQUIRED_SVIX_HEADERS = [SVIX_ID_HEADER, SVIX_TIMESTAMP_HEADER, SVIX_SIGNA
1723
export * from './api/resources/Webhooks';
1824

1925
/**
20-
* Verifies the authenticity of a webhook request using Svix.
26+
* Verifies the authenticity of a webhook request using Svix. Returns a promise that resolves to the verified webhook event data.
2127
*
22-
* @param request - The incoming webhook request object
23-
* @param options - Optional configuration object
24-
* @param options.signingSecret - Custom signing secret. If not provided, falls back to CLERK_WEBHOOK_SIGNING_SECRET env variable
25-
* @throws Will throw an error if the webhook signature verification fails
26-
* @returns A promise that resolves to the verified webhook event data
28+
* @param request - The request object.
29+
* @param options - Optional configuration object.
2730
*
2831
* @example
29-
* ```typescript
32+
* See the [guide on syncing data](https://clerk.com/docs/webhooks/sync-data) for more comprehensive and framework-specific examples that you can copy and paste into your app.
33+
*
34+
* ```ts
3035
* try {
31-
* const evt = await verifyWebhook(request);
36+
* const evt = await verifyWebhook(request)
3237
*
3338
* // Access the event data
34-
* const { id } = evt.data;
35-
* const eventType = evt.type;
39+
* const { id } = evt.data
40+
* const eventType = evt.type
3641
*
3742
* // Handle specific event types
3843
* if (evt.type === 'user.created') {
39-
* console.log('New user created:', evt.data.id);
44+
* console.log('New user created:', evt.data.id)
4045
* // Handle user creation
4146
* }
4247
*
43-
* return new Response('Success', { status: 200 });
48+
* return new Response('Success', { status: 200 })
4449
* } catch (err) {
45-
* console.error('Webhook verification failed:', err);
46-
* return new Response('Webhook verification failed', { status: 400 });
50+
* console.error('Webhook verification failed:', err)
51+
* return new Response('Webhook verification failed', { status: 400 })
4752
* }
4853
* ```
4954
*/

0 commit comments

Comments
 (0)