Skip to content

Commit 4101855

Browse files
authored
Merge pull request #539 from abraham/copilot/consolidate-oauth2-flows
Consolidate OAuth2 security schemes into single scheme with multiple flows
2 parents fdae94d + 56a6416 commit 4101855

File tree

5 files changed

+99
-47
lines changed

5 files changed

+99
-47
lines changed

dist/schema.json

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@
156156
},
157157
"security": [
158158
{
159-
"OAuth2ClientCredentials": [
159+
"OAuth2": [
160160
"write:accounts"
161161
]
162162
}
@@ -5552,7 +5552,7 @@
55525552
},
55535553
"security": [
55545554
{
5555-
"OAuth2ClientCredentials": []
5555+
"OAuth2": []
55565556
}
55575557
]
55585558
}
@@ -25644,7 +25644,7 @@
2564425644
},
2564525645
"security": [
2564625646
{
25647-
"OAuth2ClientCredentials": [
25647+
"OAuth2": [
2564825648
"read:statuses"
2564925649
]
2565025650
}
@@ -25807,7 +25807,7 @@
2580725807
},
2580825808
"security": [
2580925809
{
25810-
"OAuth2ClientCredentials": [
25810+
"OAuth2": [
2581125811
"read:statuses"
2581225812
]
2581325813
}
@@ -29967,7 +29967,7 @@
2996729967
},
2996829968
"security": [
2996929969
{
29970-
"OAuth2ClientCredentials": [
29970+
"OAuth2": [
2997129971
"read:statuses"
2997229972
]
2997329973
}
@@ -30356,7 +30356,7 @@
3035630356
},
3035730357
"security": [
3035830358
{
30359-
"OAuth2ClientCredentials": [
30359+
"OAuth2": [
3036030360
"read:statuses"
3036130361
]
3036230362
}
@@ -30571,7 +30571,7 @@
3057130571
},
3057230572
"security": [
3057330573
{
30574-
"OAuth2ClientCredentials": [
30574+
"OAuth2": [
3057530575
"read:statuses"
3057630576
]
3057730577
}
@@ -39193,11 +39193,11 @@
3919339193
"securitySchemes": {
3919439194
"OAuth2": {
3919539195
"type": "oauth2",
39196-
"description": "OAuth 2.0 authentication for user tokens (authorization code flow)",
39196+
"description": "OAuth 2.0 authentication",
3919739197
"flows": {
3919839198
"authorizationCode": {
39199-
"authorizationUrl": "https://mastodon.example/oauth/authorize",
39200-
"tokenUrl": "https://mastodon.example/oauth/token",
39199+
"authorizationUrl": "/oauth/authorize",
39200+
"tokenUrl": "/oauth/token",
3920139201
"scopes": {
3920239202
"profile": "Access to the current user profile information only",
3920339203
"read": "Read access",
@@ -39245,15 +39245,9 @@
3924539245
"admin:write:ip_blocks": "Administrative write access to ip_blocks",
3924639246
"admin:write:reports": "Administrative write access to reports"
3924739247
}
39248-
}
39249-
}
39250-
},
39251-
"OAuth2ClientCredentials": {
39252-
"type": "oauth2",
39253-
"description": "OAuth 2.0 authentication for app tokens (client credentials flow)",
39254-
"flows": {
39248+
},
3925539249
"clientCredentials": {
39256-
"tokenUrl": "https://mastodon.example/oauth/token",
39250+
"tokenUrl": "/oauth/token",
3925739251
"scopes": {
3925839252
"read": "Read access",
3925939253
"write": "Write access",

src/__tests__/generators/MethodConverter.bearer-token-security.test.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ describe('MethodConverter Bearer Token Security', () => {
3131
description: 'OAuth 2.0 authentication',
3232
flows: {
3333
authorizationCode: {
34-
authorizationUrl: 'https://mastodon.example/oauth/authorize',
35-
tokenUrl: 'https://mastodon.example/oauth/token',
34+
authorizationUrl: '/oauth/authorize',
35+
tokenUrl: '/oauth/token',
3636
scopes: {},
3737
},
3838
clientCredentials: {
39-
tokenUrl: 'https://mastodon.example/oauth/token',
39+
tokenUrl: '/oauth/token',
4040
scopes: {},
4141
},
4242
},
@@ -143,10 +143,8 @@ describe('MethodConverter Bearer Token Security', () => {
143143

144144
const operation = spec.paths['/api/v1/accounts']?.post;
145145
expect(operation).toBeDefined();
146-
// Should require app token (client credentials flow)
147-
expect(operation?.security).toEqual([
148-
{ OAuth2ClientCredentials: ['write:accounts'] },
149-
]);
146+
// Should require app token (client credentials flow via OAuth2 security scheme)
147+
expect(operation?.security).toEqual([{ OAuth2: ['write:accounts'] }]);
150148
});
151149

152150
test('should handle app token with multiple scopes', () => {
@@ -163,7 +161,7 @@ describe('MethodConverter Bearer Token Security', () => {
163161
const operation = spec.paths['/api/v1/admin/action']?.post;
164162
expect(operation).toBeDefined();
165163
expect(operation?.security).toEqual([
166-
{ OAuth2ClientCredentials: ['admin:write', 'write:accounts'] },
164+
{ OAuth2: ['admin:write', 'write:accounts'] },
167165
]);
168166
});
169167
});
@@ -182,11 +180,8 @@ describe('MethodConverter Bearer Token Security', () => {
182180

183181
const operation = spec.paths['/api/v1/flexible']?.get;
184182
expect(operation).toBeDefined();
185-
// Should support both user token and app token
186-
expect(operation?.security).toEqual([
187-
{ OAuth2: ['read'] }, // User token
188-
{ OAuth2ClientCredentials: ['read'] }, // App token
189-
]);
183+
// Mixed token types use the same OAuth2 security scheme with both flows available
184+
expect(operation?.security).toEqual([{ OAuth2: ['read'] }]);
190185
});
191186

192187
test('should handle token without scopes', () => {

src/__tests__/generators/SpecBuilder.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,76 @@ describe('SpecBuilder', () => {
123123
/\(https:\/\/github\.com\/mastodon\/documentation\/commit\/test123commit456\)\./
124124
);
125125
});
126+
127+
it('should have a single OAuth2 security scheme with both flows', () => {
128+
const mockConfig = {
129+
mastodonDocsCommit: 'test123commit456',
130+
};
131+
132+
mockReadFileSync.mockReturnValue(JSON.stringify(mockConfig));
133+
134+
const spec = specBuilder.buildInitialSpec();
135+
136+
// Check that there is only one OAuth2 security scheme
137+
expect(spec.components?.securitySchemes).toBeDefined();
138+
expect(spec.components?.securitySchemes?.OAuth2).toBeDefined();
139+
expect(
140+
spec.components?.securitySchemes?.OAuth2ClientCredentials
141+
).toBeUndefined();
142+
143+
// Check the OAuth2 security scheme has both flows
144+
const oauth2 = spec.components?.securitySchemes?.OAuth2 as any;
145+
expect(oauth2.type).toBe('oauth2');
146+
expect(oauth2.description).toBe('OAuth 2.0 authentication');
147+
expect(oauth2.flows?.authorizationCode).toBeDefined();
148+
expect(oauth2.flows?.clientCredentials).toBeDefined();
149+
});
150+
151+
it('should use path-only URLs for OAuth2 flows', () => {
152+
const mockConfig = {
153+
mastodonDocsCommit: 'test123commit456',
154+
};
155+
156+
mockReadFileSync.mockReturnValue(JSON.stringify(mockConfig));
157+
158+
const spec = specBuilder.buildInitialSpec();
159+
160+
const oauth2 = spec.components?.securitySchemes?.OAuth2 as any;
161+
162+
// Check authorizationCode flow URLs are path-only
163+
expect(oauth2.flows?.authorizationCode?.authorizationUrl).toBe(
164+
'/oauth/authorize'
165+
);
166+
expect(oauth2.flows?.authorizationCode?.tokenUrl).toBe('/oauth/token');
167+
168+
// Check clientCredentials flow URL is path-only
169+
expect(oauth2.flows?.clientCredentials?.tokenUrl).toBe('/oauth/token');
170+
});
171+
172+
it('should include scopes for both OAuth2 flows', () => {
173+
const mockConfig = {
174+
mastodonDocsCommit: 'test123commit456',
175+
};
176+
177+
mockReadFileSync.mockReturnValue(JSON.stringify(mockConfig));
178+
179+
const spec = specBuilder.buildInitialSpec();
180+
181+
const oauth2 = spec.components?.securitySchemes?.OAuth2 as any;
182+
183+
// Check authorizationCode flow has scopes (from mock: read, write)
184+
expect(oauth2.flows?.authorizationCode?.scopes).toBeDefined();
185+
expect(oauth2.flows?.authorizationCode?.scopes.read).toBe('Read access');
186+
expect(oauth2.flows?.authorizationCode?.scopes.write).toBe(
187+
'Write access'
188+
);
189+
190+
// Check clientCredentials flow has scopes
191+
expect(oauth2.flows?.clientCredentials?.scopes).toBeDefined();
192+
expect(oauth2.flows?.clientCredentials?.scopes.read).toBe('Read access');
193+
expect(oauth2.flows?.clientCredentials?.scopes.write).toBe(
194+
'Write access'
195+
);
196+
});
126197
});
127198
});

src/generators/MethodConverter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,13 +1009,13 @@ class MethodConverter {
10091009

10101010
case 'app':
10111011
// Required app token (client credentials flow)
1012-
securityRequirements.push({ OAuth2ClientCredentials: config.scopes });
1012+
securityRequirements.push({ OAuth2: config.scopes });
10131013
break;
10141014

10151015
case 'mixed':
1016-
// Supports both user and app tokens
1017-
securityRequirements.push({ OAuth2: config.scopes }); // User token
1018-
securityRequirements.push({ OAuth2ClientCredentials: config.scopes }); // App token
1016+
// Supports both user and app tokens - both use the same OAuth2 security scheme
1017+
// with different flows (authorizationCode for user tokens, clientCredentials for app tokens)
1018+
securityRequirements.push({ OAuth2: config.scopes });
10191019
break;
10201020

10211021
case 'unknown':

src/generators/SpecBuilder.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,15 @@ class SpecBuilder {
9292
securitySchemes: {
9393
OAuth2: {
9494
type: 'oauth2',
95-
description:
96-
'OAuth 2.0 authentication for user tokens (authorization code flow)',
95+
description: 'OAuth 2.0 authentication',
9796
flows: {
9897
authorizationCode: {
99-
authorizationUrl: 'https://mastodon.example/oauth/authorize',
100-
tokenUrl: 'https://mastodon.example/oauth/token',
98+
authorizationUrl: '/oauth/authorize',
99+
tokenUrl: '/oauth/token',
101100
scopes: scopesObject,
102101
},
103-
},
104-
},
105-
OAuth2ClientCredentials: {
106-
type: 'oauth2',
107-
description:
108-
'OAuth 2.0 authentication for app tokens (client credentials flow)',
109-
flows: {
110102
clientCredentials: {
111-
tokenUrl: 'https://mastodon.example/oauth/token',
103+
tokenUrl: '/oauth/token',
112104
scopes: clientCredentialsScopes,
113105
},
114106
},

0 commit comments

Comments
 (0)