Skip to content

Commit c956af4

Browse files
authored
Guaranteed identity detection for Google and Microsoft connections (#1306)
* Guarantee Google identity health checks * Keep Microsoft identity endpoint available * Auto-name OAuth connections from health checks * Restrict Graph identity health keep to GET
1 parent 5d371c2 commit c956af4

12 files changed

Lines changed: 756 additions & 61 deletions

File tree

packages/plugins/google/src/sdk/discovery.test.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,105 @@ const ConvertedSpec = Schema.Struct({
4646

4747
const decodeConvertedSpec = Schema.decodeUnknownSync(Schema.fromJsonString(ConvertedSpec));
4848

49+
const OAUTH2_URL = "https://www.googleapis.com/discovery/v1/apis/oauth2/v2/rest";
50+
const OAUTH2_USERINFO_SCOPES = [
51+
"openid",
52+
"https://www.googleapis.com/auth/userinfo.email",
53+
"https://www.googleapis.com/auth/userinfo.profile",
54+
] as const;
55+
56+
const oauth2DiscoveryDoc = {
57+
name: "oauth2",
58+
version: "v2",
59+
title: "Google OAuth2 API",
60+
rootUrl: "https://www.googleapis.com/",
61+
servicePath: "",
62+
auth: {
63+
oauth2: {
64+
scopes: {
65+
openid: { description: "Associate you with your personal info on Google" },
66+
"https://www.googleapis.com/auth/userinfo.email": {
67+
description: "See your primary Google Account email address",
68+
},
69+
"https://www.googleapis.com/auth/userinfo.profile": {
70+
description: "See your personal info",
71+
},
72+
},
73+
},
74+
},
75+
methods: {
76+
tokeninfo: {
77+
id: "oauth2.tokeninfo",
78+
httpMethod: "POST",
79+
path: "oauth2/v2/tokeninfo",
80+
parameters: {
81+
access_token: {
82+
location: "query",
83+
type: "string",
84+
},
85+
},
86+
response: { $ref: "Tokeninfo" },
87+
},
88+
},
89+
resources: {
90+
userinfo: {
91+
methods: {
92+
get: {
93+
id: "oauth2.userinfo.get",
94+
httpMethod: "GET",
95+
path: "oauth2/v2/userinfo",
96+
scopes: OAUTH2_USERINFO_SCOPES,
97+
response: { $ref: "Userinfo" },
98+
},
99+
},
100+
resources: {
101+
v2: {
102+
resources: {
103+
me: {
104+
methods: {
105+
get: {
106+
id: "oauth2.userinfo.v2.me.get",
107+
httpMethod: "GET",
108+
path: "userinfo/v2/me",
109+
scopes: OAUTH2_USERINFO_SCOPES,
110+
response: { $ref: "Userinfo" },
111+
},
112+
},
113+
},
114+
},
115+
},
116+
},
117+
},
118+
},
119+
schemas: {
120+
Tokeninfo: {
121+
id: "Tokeninfo",
122+
type: "object",
123+
properties: {
124+
audience: { type: "string" },
125+
scope: { type: "string" },
126+
},
127+
},
128+
Userinfo: {
129+
id: "Userinfo",
130+
type: "object",
131+
properties: {
132+
email: { type: "string" },
133+
family_name: { type: "string" },
134+
gender: { type: "string" },
135+
given_name: { type: "string" },
136+
hd: { type: "string" },
137+
id: { type: "string" },
138+
link: { type: "string" },
139+
locale: { type: "string" },
140+
name: { type: "string" },
141+
picture: { type: "string" },
142+
verified_email: { type: "boolean" },
143+
},
144+
},
145+
},
146+
};
147+
49148
it("accepts only supported HTTPS Google Discovery endpoints", () => {
50149
expect(
51150
normalizeGoogleDiscoveryUrl("https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest/"),
@@ -294,6 +393,46 @@ it.effect("converts Google Discovery documents into Executor-preserving OpenAPI
294393
}),
295394
);
296395

396+
it.effect("converts Google OAuth2 v2 top-level and aliased userinfo methods", () =>
397+
Effect.gen(function* () {
398+
const result = yield* convertGoogleDiscoveryToOpenApi({
399+
discoveryUrl: OAUTH2_URL,
400+
// @effect-diagnostics-next-line preferSchemaOverJson:off
401+
documentText: JSON.stringify(oauth2DiscoveryDoc),
402+
});
403+
404+
const spec = decodeConvertedSpec(result.specText);
405+
const userinfo = spec.paths["/oauth2/v2/userinfo"]?.get;
406+
const userinfoMe = spec.paths["/userinfo/v2/me"]?.get;
407+
const tokeninfo = spec.paths["/oauth2/v2/tokeninfo"]?.post;
408+
409+
expect(userinfo).toMatchObject({
410+
operationId: "userinfo.get",
411+
"x-executor-toolPath": "userinfo.get",
412+
"x-google-scopes": [...OAUTH2_USERINFO_SCOPES],
413+
});
414+
expect(userinfo?.security).toEqual([{ googleOAuth2: [...OAUTH2_USERINFO_SCOPES] }]);
415+
expect(userinfo?.responses).toMatchObject({
416+
"200": {
417+
content: {
418+
"application/json": {
419+
schema: { $ref: "#/components/schemas/Userinfo" },
420+
},
421+
},
422+
},
423+
});
424+
expect(userinfoMe).toMatchObject({
425+
operationId: "userinfo.v2.me.get",
426+
"x-executor-toolPath": "userinfo.v2.me.get",
427+
"x-google-scopes": [...OAUTH2_USERINFO_SCOPES],
428+
});
429+
expect(tokeninfo).toMatchObject({
430+
operationId: "tokeninfo",
431+
"x-executor-toolPath": "tokeninfo",
432+
});
433+
}),
434+
);
435+
297436
it.effect("marks Google Discovery media-download methods as binary responses", () =>
298437
Effect.gen(function* () {
299438
const result = yield* convertGoogleDiscoveryToOpenApi({

0 commit comments

Comments
 (0)