Skip to content

Commit bb35660

Browse files
authored
feat(backend): Add OAuth Application endpoints to Backend API client (#5599)
1 parent fb6aa20 commit bb35660

File tree

9 files changed

+183
-0
lines changed

9 files changed

+183
-0
lines changed

.changeset/lucky-planets-drive.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@clerk/backend': minor
3+
---
4+
5+
Adds the ability to perform CRUD operations on OAuth Applications to the Backend API client.
6+
7+
8+
```ts
9+
import { createClerkClient } from '@clerk/backend';
10+
11+
const clerkClient = createClerkClient(...);
12+
13+
await clerkClient.oauthApplications.list({...});
14+
await clerkClient.oauthApplications.get('templateId');
15+
await clerkClient.oauthApplications.create({...});
16+
await clerkClient.oauthApplications.update({...});
17+
await clerkClient.oauthApplications.delete('templateId');
18+
await clerkClient.oauthApplications.rotateSecret('templateId');
19+
```
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import type { ClerkPaginationRequest } from '@clerk/types';
2+
3+
import { joinPaths } from '../../util/path';
4+
import type { DeletedObject } from '../resources';
5+
import type { PaginatedResourceResponse } from '../resources/Deserializer';
6+
import type { OAuthApplication } from '../resources/OAuthApplication';
7+
import { AbstractAPI } from './AbstractApi';
8+
9+
const basePath = '/oauth_applications';
10+
11+
type CreateOAuthApplicationParams = {
12+
/**
13+
* The name of the new OAuth application.
14+
*
15+
* @remarks Max length: 256
16+
*/
17+
name: string;
18+
/**
19+
* An array of redirect URIs of the new OAuth application
20+
*/
21+
redirectUris?: Array<string> | null | undefined;
22+
/**
23+
* Define the allowed scopes for the new OAuth applications that dictate the user payload of the OAuth user info endpoint. Available scopes are `profile`, `email`, `public_metadata`, `private_metadata`. Provide the requested scopes as a string, separated by spaces.
24+
*/
25+
scopes?: string | null | undefined;
26+
/**
27+
* If true, this client is public and you can use the Proof Key of Code Exchange (PKCE) flow.
28+
*/
29+
public?: boolean | null | undefined;
30+
};
31+
32+
type UpdateOAuthApplicationParams = CreateOAuthApplicationParams & {
33+
/**
34+
* The ID of the OAuth application to update
35+
*/
36+
oauthApplicationId: string;
37+
};
38+
39+
export class OAuthApplicationsApi extends AbstractAPI {
40+
public async list(params: ClerkPaginationRequest = {}) {
41+
return this.request<PaginatedResourceResponse<OAuthApplication[]>>({
42+
method: 'GET',
43+
path: basePath,
44+
queryParams: params,
45+
});
46+
}
47+
48+
public async get(oauthApplicationId: string) {
49+
this.requireId(oauthApplicationId);
50+
51+
return this.request<OAuthApplication>({
52+
method: 'GET',
53+
path: joinPaths(basePath, oauthApplicationId),
54+
});
55+
}
56+
57+
public async create(params: CreateOAuthApplicationParams) {
58+
return this.request<OAuthApplication>({
59+
method: 'POST',
60+
path: basePath,
61+
bodyParams: params,
62+
});
63+
}
64+
65+
public async update(params: UpdateOAuthApplicationParams) {
66+
const { oauthApplicationId, ...bodyParams } = params;
67+
68+
this.requireId(oauthApplicationId);
69+
70+
return this.request<OAuthApplication>({
71+
method: 'PATCH',
72+
path: joinPaths(basePath, oauthApplicationId),
73+
bodyParams,
74+
});
75+
}
76+
77+
public async delete(oauthApplicationId: string) {
78+
this.requireId(oauthApplicationId);
79+
80+
return this.request<DeletedObject>({
81+
method: 'DELETE',
82+
path: joinPaths(basePath, oauthApplicationId),
83+
});
84+
}
85+
86+
public async rotateSecret(oauthApplicationId: string) {
87+
this.requireId(oauthApplicationId);
88+
89+
return this.request<OAuthApplication>({
90+
method: 'POST',
91+
path: joinPaths(basePath, oauthApplicationId, 'rotate_secret'),
92+
});
93+
}
94+
}

packages/backend/src/api/endpoints/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export * from './InvitationApi';
1010
export * from './JwksApi';
1111
export * from './JwtTemplatesApi';
1212
export * from './OrganizationApi';
13+
export * from './OAuthApplicationsApi';
1314
export * from './PhoneNumberApi';
1415
export * from './ProxyCheckApi';
1516
export * from './RedirectUrlApi';

packages/backend/src/api/factory.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
InvitationAPI,
1010
JwksAPI,
1111
JwtTemplatesApi,
12+
OAuthApplicationsApi,
1213
OrganizationAPI,
1314
PhoneNumberAPI,
1415
ProxyCheckAPI,
@@ -42,6 +43,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) {
4243
invitations: new InvitationAPI(request),
4344
jwks: new JwksAPI(request),
4445
jwtTemplates: new JwtTemplatesApi(request),
46+
oauthApplications: new OAuthApplicationsApi(request),
4547
organizations: new OrganizationAPI(request),
4648
phoneNumbers: new PhoneNumberAPI(request),
4749
proxyChecks: new ProxyCheckAPI(request),

packages/backend/src/api/resources/Deserializer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Invitation,
1212
JwtTemplate,
1313
OauthAccessToken,
14+
OAuthApplication,
1415
Organization,
1516
OrganizationInvitation,
1617
OrganizationMembership,
@@ -96,6 +97,8 @@ function jsonToObject(item: any): any {
9697
return JwtTemplate.fromJSON(item);
9798
case ObjectType.OauthAccessToken:
9899
return OauthAccessToken.fromJSON(item);
100+
case ObjectType.OAuthApplication:
101+
return OAuthApplication.fromJSON(item);
99102
case ObjectType.Organization:
100103
return Organization.fromJSON(item);
101104
case ObjectType.OrganizationInvitation:

packages/backend/src/api/resources/JSON.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const ObjectType = {
2828
Invitation: 'invitation',
2929
JwtTemplate: 'jwt_template',
3030
OauthAccessToken: 'oauth_access_token',
31+
OAuthApplication: 'oauth_application',
3132
Organization: 'organization',
3233
OrganizationDomain: 'organization_domain',
3334
OrganizationInvitation: 'organization_invitation',
@@ -247,6 +248,25 @@ export interface OauthAccessTokenJSON {
247248
token_secret?: string;
248249
}
249250

251+
export interface OAuthApplicationJSON extends ClerkResourceJSON {
252+
object: typeof ObjectType.OAuthApplication;
253+
id: string;
254+
instance_id: string;
255+
name: string;
256+
client_id: string;
257+
public: boolean;
258+
scopes: string;
259+
redirect_uris: Array<string>;
260+
authorize_url: string;
261+
token_fetch_url: string;
262+
user_info_url: string;
263+
discovery_url: string;
264+
token_introspection_url: string;
265+
created_at: number;
266+
updated_at: number;
267+
client_secret?: string;
268+
}
269+
250270
export interface OrganizationJSON extends ClerkResourceJSON {
251271
object: typeof ObjectType.Organization;
252272
name: string;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { OAuthApplicationJSON } from './JSON';
2+
3+
export class OAuthApplication {
4+
constructor(
5+
readonly id: string,
6+
readonly instanceId: string,
7+
readonly name: string,
8+
readonly clientId: string,
9+
readonly isPublic: boolean, // NOTE: `public` is reserved
10+
readonly scopes: string,
11+
readonly redirectUris: Array<string>,
12+
readonly authorizeUrl: string,
13+
readonly tokenFetchUrl: string,
14+
readonly userInfoUrl: string,
15+
readonly discoveryUrl: string,
16+
readonly tokenIntrospectionUrl: string,
17+
readonly createdAt: number,
18+
readonly updatedAt: number,
19+
readonly clientSecret?: string,
20+
) {}
21+
22+
static fromJSON(data: OAuthApplicationJSON) {
23+
return new OAuthApplication(
24+
data.id,
25+
data.instance_id,
26+
data.name,
27+
data.client_id,
28+
data.public,
29+
data.scopes,
30+
data.redirect_uris,
31+
data.authorize_url,
32+
data.token_fetch_url,
33+
data.user_info_url,
34+
data.discovery_url,
35+
data.token_introspection_url,
36+
data.created_at,
37+
data.updated_at,
38+
data.client_secret,
39+
);
40+
}
41+
}

packages/backend/src/api/resources/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export * from './Invitation';
2626
export * from './JSON';
2727
export * from './JwtTemplate';
2828
export * from './OauthAccessToken';
29+
export * from './OAuthApplication';
2930
export * from './Organization';
3031
export * from './OrganizationInvitation';
3132
export * from './OrganizationMembership';

packages/backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export type {
7070
InvitationJSON,
7171
JwtTemplateJSON,
7272
OauthAccessTokenJSON,
73+
OAuthApplicationJSON,
7374
OrganizationJSON,
7475
OrganizationDomainJSON,
7576
OrganizationDomainVerificationJSON,
@@ -111,6 +112,7 @@ export type {
111112
Invitation,
112113
JwtTemplate,
113114
OauthAccessToken,
115+
OAuthApplication,
114116
Organization,
115117
OrganizationDomain,
116118
OrganizationDomainVerification,

0 commit comments

Comments
 (0)