Skip to content

Commit 265b0b9

Browse files
committed
Allow for scope descriptions
This makes a small change to the code introduced in #849 to allow for models to be used as OAuth scopes. Without this check, an object like `{value: "foo", description: "bar"}` gets wrapped in an additional `value` key. When the OpenAPI3 emitter tries to print out the `securitySchemes`, it sees `{value: {...}}` and produces a scope with value `[object Object]` and no description.
1 parent 63739c4 commit 265b0b9

File tree

4 files changed

+78
-10
lines changed

4 files changed

+78
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/http"
5+
---
6+
7+
Allow for OAuth2 scopes to be properly specified with descriptions

packages/http/lib/auth.tsp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ model ApiKeyAuth<Location extends ApiKeyLocation, Name extends string> {
100100
name: Name;
101101
}
102102

103+
model OAuth2Scope {
104+
@doc("The scope identifier")
105+
value: string;
106+
107+
@doc("A description of the scope")
108+
description?: string;
109+
}
110+
111+
alias ScopeList = string[] | OAuth2Scope[];
112+
103113
/**
104114
* OAuth 2.0 is an authorization protocol that gives an API client limited access to user data on a web server.
105115
*
@@ -111,7 +121,7 @@ model ApiKeyAuth<Location extends ApiKeyLocation, Name extends string> {
111121
* @template Scopes The list of OAuth2 scopes, which are common for every flow from `Flows`. This list is combined with the scopes defined in specific OAuth2 flows.
112122
*/
113123
@doc("")
114-
model OAuth2Auth<Flows extends OAuth2Flow[], Scopes extends string[] = []> {
124+
model OAuth2Auth<Flows extends OAuth2Flow[], Scopes extends ScopeList = []> {
115125
@doc("OAuth2 authentication")
116126
type: AuthType.oauth2;
117127

@@ -154,7 +164,7 @@ model AuthorizationCodeFlow {
154164
refreshUrl?: string;
155165

156166
@doc("list of scopes for the credential")
157-
scopes?: string[];
167+
scopes?: ScopeList;
158168
}
159169

160170
@doc("Implicit flow")
@@ -169,7 +179,7 @@ model ImplicitFlow {
169179
refreshUrl?: string;
170180

171181
@doc("list of scopes for the credential")
172-
scopes?: string[];
182+
scopes?: ScopeList;
173183
}
174184

175185
@doc("Resource Owner Password flow")
@@ -184,7 +194,7 @@ model PasswordFlow {
184194
refreshUrl?: string;
185195

186196
@doc("list of scopes for the credential")
187-
scopes?: string[];
197+
scopes?: ScopeList;
188198
}
189199

190200
@doc("Client credentials flow")
@@ -199,7 +209,7 @@ model ClientCredentialsFlow {
199209
refreshUrl?: string;
200210

201211
@doc("list of scopes for the credential")
202-
scopes?: string[];
212+
scopes?: ScopeList;
203213
}
204214

205215
/**

packages/http/src/decorators.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ import {
5252
HttpStatusCodeRange,
5353
HttpStatusCodes,
5454
HttpVerb,
55+
OAuth2Flow,
56+
OAuth2Scope,
57+
Oauth2Auth,
5558
PathParameterOptions,
5659
QueryParameterOptions,
5760
} from "./types.js";
@@ -621,7 +624,11 @@ function extractHttpAuthentication(
621624
];
622625
}
623626

624-
function extractOAuth2Auth(modelType: Model, data: any): HttpAuth {
627+
function normalizeScope(scope: string | OAuth2Scope): OAuth2Scope {
628+
return typeof scope === "string" ? { value: scope } : scope;
629+
}
630+
631+
function extractOAuth2Auth(modelType: Model, data: any): Oauth2Auth<OAuth2Flow[]> {
625632
// Validation of OAuth2Flow models in this function is minimal because the
626633
// type system already validates whether the model represents a flow
627634
// configuration. This code merely avoids runtime errors.
@@ -630,16 +637,19 @@ function extractOAuth2Auth(modelType: Model, data: any): HttpAuth {
630637
? data.flows
631638
: [];
632639

633-
const defaultScopes = Array.isArray(data.defaultScopes) ? data.defaultScopes : [];
640+
const defaultScopes: Array<string | OAuth2Scope> = Array.isArray(data.defaultScopes)
641+
? data.defaultScopes
642+
: [];
643+
634644
return {
635645
id: data.id,
636646
type: data.type,
637647
model: modelType,
638-
flows: flows.map((flow: any) => {
639-
const scopes: Array<string> = flow.scopes ? flow.scopes : defaultScopes;
648+
flows: flows.map((flow: OAuth2Flow) => {
649+
const scopes = flow.scopes ? flow.scopes : defaultScopes;
640650
return {
641651
...flow,
642-
scopes: scopes.map((x: string) => ({ value: x })),
652+
scopes: scopes.map(normalizeScope),
643653
};
644654
}),
645655
};

packages/http/test/http-decorators.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,47 @@ describe("http: decorators", () => {
897897
});
898898
});
899899

900+
it("can specify OAuth2 with object scopes", async () => {
901+
const { Foo, program } = await Tester.compile(t.code`
902+
model MyFlow {
903+
type: OAuth2FlowType.implicit;
904+
authorizationUrl: "https://api.example.com/oauth2/authorize";
905+
refreshUrl: "https://api.example.com/oauth2/refresh";
906+
scopes: [
907+
{value: "read", description: "read data"},
908+
{value: "write", description: "write data"},
909+
];
910+
}
911+
@useAuth(OAuth2Auth<[MyFlow]>)
912+
namespace ${t.namespace("Foo")} {}
913+
`);
914+
915+
expect(getAuthentication(program, Foo)).toEqual({
916+
options: [
917+
{
918+
schemes: [
919+
{
920+
id: "OAuth2Auth",
921+
type: "oauth2",
922+
flows: [
923+
{
924+
type: "implicit",
925+
authorizationUrl: "https://api.example.com/oauth2/authorize",
926+
refreshUrl: "https://api.example.com/oauth2/refresh",
927+
scopes: [
928+
{ value: "read", description: "read data" },
929+
{ value: "write", description: "write data" },
930+
],
931+
},
932+
],
933+
model: expect.objectContaining({ kind: "Model" }),
934+
},
935+
],
936+
},
937+
],
938+
});
939+
});
940+
900941
it("can specify OAuth2 with scopes, which are default for every flow", async () => {
901942
const { Foo, program } = await Tester.compile(t.code`
902943
alias MyAuth<T extends string[]> = OAuth2Auth<Flows=[{

0 commit comments

Comments
 (0)