Skip to content

Commit 336e84b

Browse files
authored
[TASK-239014] Add oauth revoke and introspect endpoints to SDK (#552)
Introduce JS SDK methods and data types for the new `oauth/revoke` and `oauth/introspect` endpoints. We'll include this in an upcoming minor SDK version bump.
1 parent b7f3de8 commit 336e84b

File tree

3 files changed

+159
-49
lines changed

3 files changed

+159
-49
lines changed

src/Client.ts

+95-49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { Agent } from "http"
1+
import type { Agent } from "node:http"
22
import {
3-
Logger,
3+
type Logger,
44
LogLevel,
55
logLevelSeverity,
66
makeConsoleLogger,
@@ -13,76 +13,82 @@ import {
1313
} from "./errors"
1414
import { pick } from "./utils"
1515
import {
16-
GetBlockParameters,
17-
GetBlockResponse,
16+
type GetBlockParameters,
17+
type GetBlockResponse,
1818
getBlock,
19-
UpdateBlockParameters,
20-
UpdateBlockResponse,
19+
type UpdateBlockParameters,
20+
type UpdateBlockResponse,
2121
updateBlock,
22-
DeleteBlockParameters,
23-
DeleteBlockResponse,
22+
type DeleteBlockParameters,
23+
type DeleteBlockResponse,
2424
deleteBlock,
25-
AppendBlockChildrenParameters,
26-
AppendBlockChildrenResponse,
25+
type AppendBlockChildrenParameters,
26+
type AppendBlockChildrenResponse,
2727
appendBlockChildren,
28-
ListBlockChildrenParameters,
29-
ListBlockChildrenResponse,
28+
type ListBlockChildrenParameters,
29+
type ListBlockChildrenResponse,
3030
listBlockChildren,
31-
ListDatabasesParameters,
32-
ListDatabasesResponse,
31+
type ListDatabasesParameters,
32+
type ListDatabasesResponse,
3333
listDatabases,
34-
GetDatabaseParameters,
35-
GetDatabaseResponse,
34+
type GetDatabaseParameters,
35+
type GetDatabaseResponse,
3636
getDatabase,
37-
QueryDatabaseParameters,
38-
QueryDatabaseResponse,
37+
type QueryDatabaseParameters,
38+
type QueryDatabaseResponse,
3939
queryDatabase,
40-
CreateDatabaseParameters,
41-
CreateDatabaseResponse,
40+
type CreateDatabaseParameters,
41+
type CreateDatabaseResponse,
4242
createDatabase,
43-
UpdateDatabaseParameters,
44-
UpdateDatabaseResponse,
43+
type UpdateDatabaseParameters,
44+
type UpdateDatabaseResponse,
4545
updateDatabase,
46-
CreatePageParameters,
47-
CreatePageResponse,
46+
type CreatePageParameters,
47+
type CreatePageResponse,
4848
createPage,
49-
GetPageParameters,
50-
GetPageResponse,
49+
type GetPageParameters,
50+
type GetPageResponse,
5151
getPage,
52-
UpdatePageParameters,
53-
UpdatePageResponse,
52+
type UpdatePageParameters,
53+
type UpdatePageResponse,
5454
updatePage,
55-
GetUserParameters,
56-
GetUserResponse,
55+
type GetUserParameters,
56+
type GetUserResponse,
5757
getUser,
58-
ListUsersParameters,
59-
ListUsersResponse,
58+
type ListUsersParameters,
59+
type ListUsersResponse,
6060
listUsers,
61-
SearchParameters,
62-
SearchResponse,
61+
type SearchParameters,
62+
type SearchResponse,
6363
search,
64-
GetSelfParameters,
65-
GetSelfResponse,
64+
type GetSelfParameters,
65+
type GetSelfResponse,
6666
getSelf,
67-
GetPagePropertyParameters,
68-
GetPagePropertyResponse,
67+
type GetPagePropertyParameters,
68+
type GetPagePropertyResponse,
6969
getPageProperty,
70-
CreateCommentParameters,
71-
CreateCommentResponse,
70+
type CreateCommentParameters,
71+
type CreateCommentResponse,
7272
createComment,
73-
ListCommentsParameters,
74-
ListCommentsResponse,
73+
type ListCommentsParameters,
74+
type ListCommentsResponse,
7575
listComments,
76-
OauthTokenResponse,
77-
OauthTokenParameters,
76+
type OauthTokenResponse,
77+
type OauthTokenParameters,
7878
oauthToken,
79+
type OauthIntrospectResponse,
80+
type OauthIntrospectParameters,
81+
oauthIntrospect,
82+
type OauthRevokeResponse,
83+
type OauthRevokeParameters,
84+
oauthRevoke,
7985
} from "./api-endpoints"
8086
import nodeFetch from "node-fetch"
8187
import {
8288
version as PACKAGE_VERSION,
8389
name as PACKAGE_NAME,
8490
} from "../package.json"
85-
import { SupportedFetch } from "./fetch-types"
91+
import type { SupportedFetch } from "./fetch-types"
8692

8793
export interface ClientOptions {
8894
auth?: string
@@ -131,7 +137,7 @@ export default class Client {
131137
this.#auth = options?.auth
132138
this.#logLevel = options?.logLevel ?? LogLevel.WARN
133139
this.#logger = options?.logger ?? makeConsoleLogger(PACKAGE_NAME)
134-
this.#prefixUrl = (options?.baseUrl ?? "https://api.notion.com") + "/v1/"
140+
this.#prefixUrl = `${options?.baseUrl ?? "https://api.notion.com"}/v1/`
135141
this.#timeoutMs = options?.timeoutMs ?? 60_000
136142
this.#notionVersion = options?.notionVersion ?? Client.defaultNotionVersion
137143
this.#fetch = options?.fetch ?? nodeFetch
@@ -219,22 +225,22 @@ export default class Client {
219225
}
220226

221227
const responseJson: ResponseBody = JSON.parse(responseText)
222-
this.log(LogLevel.INFO, `request success`, { method, path })
228+
this.log(LogLevel.INFO, "request success", { method, path })
223229
return responseJson
224230
} catch (error: unknown) {
225231
if (!isNotionClientError(error)) {
226232
throw error
227233
}
228234

229235
// Log the error if it's one of our known error types
230-
this.log(LogLevel.WARN, `request fail`, {
236+
this.log(LogLevel.WARN, "request fail", {
231237
code: error.code,
232238
message: error.message,
233239
})
234240

235241
if (isHTTPResponseError(error)) {
236242
// The response body may contain sensitive information so it is logged separately at the DEBUG level
237-
this.log(LogLevel.DEBUG, `failed response body`, {
243+
this.log(LogLevel.DEBUG, "failed response body", {
238244
body: error.body,
239245
})
240246
}
@@ -574,6 +580,46 @@ export default class Client {
574580
},
575581
})
576582
},
583+
/**
584+
* Introspect token
585+
*/
586+
introspect: (
587+
args: OauthIntrospectParameters & {
588+
client_id: string
589+
client_secret: string
590+
}
591+
): Promise<OauthIntrospectResponse> => {
592+
return this.request<OauthIntrospectResponse>({
593+
path: oauthIntrospect.path(),
594+
method: oauthIntrospect.method,
595+
query: pick(args, oauthIntrospect.queryParams),
596+
body: pick(args, oauthIntrospect.bodyParams),
597+
auth: {
598+
client_id: args.client_id,
599+
client_secret: args.client_secret,
600+
},
601+
})
602+
},
603+
/**
604+
* Revoke token
605+
*/
606+
revoke: (
607+
args: OauthRevokeParameters & {
608+
client_id: string
609+
client_secret: string
610+
}
611+
): Promise<OauthRevokeResponse> => {
612+
return this.request<OauthRevokeResponse>({
613+
path: oauthRevoke.path(),
614+
method: oauthRevoke.method,
615+
query: pick(args, oauthRevoke.queryParams),
616+
body: pick(args, oauthRevoke.bodyParams),
617+
auth: {
618+
client_id: args.client_id,
619+
client_secret: args.client_secret,
620+
},
621+
})
622+
},
577623
}
578624

579625
/**

src/api-endpoints.ts

+32
Original file line numberDiff line numberDiff line change
@@ -11768,3 +11768,35 @@ export const oauthToken = {
1176811768
bodyParams: ["grant_type", "code", "redirect_uri", "external_account"],
1176911769
path: (): string => `oauth/token`,
1177011770
} as const
11771+
11772+
type OauthRevokeBodyParameters = { token: string }
11773+
11774+
export type OauthRevokeParameters = OauthRevokeBodyParameters
11775+
11776+
export type OauthRevokeResponse = Record<string, never>
11777+
11778+
export const oauthRevoke = {
11779+
method: "post",
11780+
pathParams: [],
11781+
queryParams: [],
11782+
bodyParams: ["token"],
11783+
path: (): string => `oauth/revoke`,
11784+
} as const
11785+
11786+
type OauthIntrospectBodyParameters = { token: string }
11787+
11788+
export type OauthIntrospectParameters = OauthIntrospectBodyParameters
11789+
11790+
export type OauthIntrospectResponse = {
11791+
active: boolean
11792+
scope?: string
11793+
iat?: number
11794+
}
11795+
11796+
export const oauthIntrospect = {
11797+
method: "post",
11798+
pathParams: [],
11799+
queryParams: [],
11800+
bodyParams: ["token"],
11801+
path: (): string => `oauth/introspect`,
11802+
} as const

test/Client.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,36 @@ describe("Notion SDK Client", () => {
44
it("Constructs without throwing", () => {
55
new Client({ auth: "foo" })
66
})
7+
8+
it("calls revoke API with basic auth", async () => {
9+
const mockFetch = jest.fn()
10+
mockFetch.mockResolvedValue({
11+
ok: true,
12+
text: () => "{}",
13+
headers: {},
14+
status: 200,
15+
})
16+
17+
const notion = new Client({ fetch: mockFetch })
18+
19+
await notion.oauth.revoke({
20+
client_id: "client_id",
21+
client_secret: "client_secret",
22+
token: "token",
23+
})
24+
25+
expect(mockFetch).toHaveBeenCalledWith(
26+
"https://api.notion.com/v1/oauth/revoke",
27+
expect.objectContaining({
28+
method: "POST",
29+
headers: expect.objectContaining({
30+
"Notion-Version": "2022-06-28",
31+
"user-agent": expect.stringContaining("notionhq-client"),
32+
authorization: `Basic ${Buffer.from(
33+
"client_id:client_secret"
34+
).toString("base64")}`,
35+
}),
36+
})
37+
)
38+
})
739
})

0 commit comments

Comments
 (0)