History

Label oauth2 auth methods so multiple templates stay distinguishable (#1756)

Rhys Sullivan committed 2 months ago

Commit a7888741 parent ea5ae6116 files · +122 −5Browse files

Unified · Split

16 files
  1. packages/core/sdk/src/client.ts +1 −0
  2. packages/core/sdk/src/oauth-client.ts +4 −0
  3. packages/core/sdk/src/plugin.ts +1 −0
  4. packages/plugins/openapi/src/api/group.ts +1 −0
  5. packages/plugins/openapi/src/providers/microsoft/graph.ts +4 −0
  6. packages/plugins/openapi/src/providers/microsoft/presets.test.ts +7 −0
  7. packages/plugins/openapi/src/providers/microsoft/presets.ts +7 −0
  8. packages/plugins/openapi/src/react/auth-method-config.test.ts +16 −0
  9. packages/plugins/openapi/src/react/auth-method-config.ts +3 −1
  10. packages/plugins/openapi/src/sdk/config.ts +1 −0
  11. packages/plugins/openapi/src/sdk/configure.test.ts +2 −2
  12. packages/plugins/openapi/src/sdk/derive-auth.test.ts +46 −1
  13. packages/plugins/openapi/src/sdk/derive-auth.ts +7 −0
  14. packages/plugins/openapi/src/sdk/describe-auth-methods.test.ts +17 −0
  15. packages/plugins/openapi/src/sdk/plugin.ts +2 −1
  16. packages/react/src/components/auth-template-editor.tsx +3 −0
packages/core/sdk/src/client.ts+1−0
@@ -126,6 +126,7 @@ export type IntegrationPresetAuthentication = · 125 unmodified lines
126 | {126 | {
127 readonly slug: string;127 readonly slug: string;
128 readonly kind: "oauth2";128 readonly kind: "oauth2";
129 readonly label?: string;
129 readonly authorizationUrl: string;130 readonly authorizationUrl: string;
130 readonly tokenUrl: string;131 readonly tokenUrl: string;
131 readonly resource?: string | null;132 readonly resource?: string | null;
packages/core/sdk/src/oauth-client.ts+4−0
@@ -31,6 +31,10 @@ export type OAuthGrant = "authorization_code" | "client_credentials"; · 30 unmodified lines
31export interface OAuthAuthentication {31export interface OAuthAuthentication {
32 readonly slug: AuthTemplateSlug;32 readonly slug: AuthTemplateSlug;
33 readonly kind: "oauth2";33 readonly kind: "oauth2";
34 /** Display label distinguishing this method when an integration declares
35 * several oauth2 templates (e.g. delegated vs app-only). UIs fall back to
36 * "OAuth2" when absent. */
37 readonly label?: string;
34 readonly authorizationUrl: string;38 readonly authorizationUrl: string;
35 readonly tokenUrl: string;39 readonly tokenUrl: string;
36 /** RFC 8707 Resource Indicator to bind the OAuth flow to this protected40 /** RFC 8707 Resource Indicator to bind the OAuth flow to this protected
packages/core/sdk/src/plugin.ts+1−0
@@ -582,6 +582,7 @@ export type IntegrationPresetAuthentication = · 581 unmodified lines
582 | {582 | {
583 readonly slug: string;583 readonly slug: string;
584 readonly kind: "oauth2";584 readonly kind: "oauth2";
585 readonly label?: string;
585 readonly authorizationUrl: string;586 readonly authorizationUrl: string;
586 readonly tokenUrl: string;587 readonly tokenUrl: string;
587 readonly resource?: string | null;588 readonly resource?: string | null;
packages/plugins/openapi/src/api/group.ts+1−0
@@ -62,6 +62,7 @@ const OpenApiSpecInputPayload = Schema.Union([ · 61 unmodified lines
62const OAuthTemplatePayload = Schema.Struct({62const OAuthTemplatePayload = Schema.Struct({
63 slug: Schema.String,63 slug: Schema.String,
64 kind: Schema.Literal("oauth2"),64 kind: Schema.Literal("oauth2"),
65 label: Schema.optional(Schema.String),
65 authorizationUrl: Schema.String,66 authorizationUrl: Schema.String,
66 tokenUrl: Schema.String,67 tokenUrl: Schema.String,
67 resource: Schema.optional(Schema.NullOr(Schema.String)),68 resource: Schema.optional(Schema.NullOr(Schema.String)),
packages/plugins/openapi/src/providers/microsoft/graph.ts+4−0
@@ -23,7 +23,9 @@ import { · 22 unmodified lines
23import {23import {
24 MICROSOFT_AUTHORIZATION_URL,24 MICROSOFT_AUTHORIZATION_URL,
25 MICROSOFT_AUTH_TEMPLATE_SLUG,25 MICROSOFT_AUTH_TEMPLATE_SLUG,
26 MICROSOFT_CLIENT_CREDENTIALS_AUTH_LABEL,
26 MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG,27 MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG,
28 MICROSOFT_DELEGATED_AUTH_LABEL,
27 MICROSOFT_GRAPH_BASE_SCOPES,29 MICROSOFT_GRAPH_BASE_SCOPES,
28 MICROSOFT_GRAPH_CLIENT_CREDENTIALS_SCOPES,30 MICROSOFT_GRAPH_CLIENT_CREDENTIALS_SCOPES,
29 MICROSOFT_GRAPH_DELEGATED_DEFAULT_SCOPES,31 MICROSOFT_GRAPH_DELEGATED_DEFAULT_SCOPES,
@@ -173,6 +175,7 @@ const microsoftOAuthTemplate = ( · 143 unmodified lines
173 {175 {
174 slug: AuthTemplateSlug.make(MICROSOFT_AUTH_TEMPLATE_SLUG),176 slug: AuthTemplateSlug.make(MICROSOFT_AUTH_TEMPLATE_SLUG),
175 kind: "oauth2",177 kind: "oauth2",
178 label: MICROSOFT_DELEGATED_AUTH_LABEL,
176 authorizationUrl: endpoints.authorizationUrl,179 authorizationUrl: endpoints.authorizationUrl,
177 tokenUrl: endpoints.tokenUrl,180 tokenUrl: endpoints.tokenUrl,
178 scopes,181 scopes,
@@ -180,6 +183,7 @@ const microsoftOAuthTemplate = ( · 1 unmodified line
180 {183 {
181 slug: AuthTemplateSlug.make(MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG),184 slug: AuthTemplateSlug.make(MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG),
182 kind: "oauth2",185 kind: "oauth2",
186 label: MICROSOFT_CLIENT_CREDENTIALS_AUTH_LABEL,
183 authorizationUrl: endpoints.authorizationUrl,187 authorizationUrl: endpoints.authorizationUrl,
184 tokenUrl: endpoints.clientCredentialsTokenUrl,188 tokenUrl: endpoints.clientCredentialsTokenUrl,
185 scopes: [...MICROSOFT_GRAPH_CLIENT_CREDENTIALS_SCOPES],189 scopes: [...MICROSOFT_GRAPH_CLIENT_CREDENTIALS_SCOPES],
packages/plugins/openapi/src/providers/microsoft/presets.test.ts+7−0
@@ -288,6 +288,13 @@ describe("Microsoft Graph scope presets", () => { · 287 unmodified lines
288 expect(preset.defaultSlug).toBeTruthy();288 expect(preset.defaultSlug).toBeTruthy();
289 expect(preset.authTemplate).toHaveLength(2);289 expect(preset.authTemplate).toHaveLength(2);
290 expect(preset.authTemplate?.map((template) => template.kind)).toEqual(["oauth2", "oauth2"]);290 expect(preset.authTemplate?.map((template) => template.kind)).toEqual(["oauth2", "oauth2"]);
291 // Both methods are oauth2, so distinct labels are what keeps the
292 // delegated and app-only tabs tellable apart in the connection dialog.
293 expect(
294 preset.authTemplate?.map((template) =>
295 template.kind === "oauth2" ? template.label : null,
296 ),
297 ).toEqual(["OAuth2 (user)", "OAuth2 (app-only)"]);
291 }298 }
292 });299 });
293 300
packages/plugins/openapi/src/providers/microsoft/presets.ts+7−0
@@ -44,6 +44,11 @@ export const MICROSOFT_AUTHORIZATION_URL = · 43 unmodified lines
44export const MICROSOFT_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";44export const MICROSOFT_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
45export const MICROSOFT_AUTH_TEMPLATE_SLUG = "azureAdDelegated";45export const MICROSOFT_AUTH_TEMPLATE_SLUG = "azureAdDelegated";
46export const MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG = "azureAdClientCredentials";46export const MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG = "azureAdClientCredentials";
47// Both templates are plain oauth2, so without labels they'd render as two
48// identical "OAuth2" methods. Microsoft's terms: delegated (signed-in user)
49// vs app-only (client credentials, no user).
50export const MICROSOFT_DELEGATED_AUTH_LABEL = "OAuth2 (user)";
51export const MICROSOFT_CLIENT_CREDENTIALS_AUTH_LABEL = "OAuth2 (app-only)";
47export const MICROSOFT_GRAPH_BASE_SCOPES: readonly string[] = ["offline_access"];52export const MICROSOFT_GRAPH_BASE_SCOPES: readonly string[] = ["offline_access"];
48export const MICROSOFT_GRAPH_IDENTITY_SCOPE = "User.Read";53export const MICROSOFT_GRAPH_IDENTITY_SCOPE = "User.Read";
49export const MICROSOFT_GRAPH_DEFAULT_SCOPE = "https://graph.microsoft.com/.default";54export const MICROSOFT_GRAPH_DEFAULT_SCOPE = "https://graph.microsoft.com/.default";
@@ -539,6 +544,7 @@ const microsoftGraphCatalogAuthTemplate = (preset: MicrosoftGraphScopePreset) => · 489 unmodified lines
539 {544 {
540 slug: MICROSOFT_AUTH_TEMPLATE_SLUG,545 slug: MICROSOFT_AUTH_TEMPLATE_SLUG,
541 kind: "oauth2" as const,546 kind: "oauth2" as const,
547 label: MICROSOFT_DELEGATED_AUTH_LABEL,
542 authorizationUrl: MICROSOFT_AUTHORIZATION_URL,548 authorizationUrl: MICROSOFT_AUTHORIZATION_URL,
543 tokenUrl: MICROSOFT_TOKEN_URL,549 tokenUrl: MICROSOFT_TOKEN_URL,
544 scopes: microsoftGraphScopesForPresetIds([preset.id]),550 scopes: microsoftGraphScopesForPresetIds([preset.id]),
@@ -546,6 +552,7 @@ const microsoftGraphCatalogAuthTemplate = (preset: MicrosoftGraphScopePreset) => · 1 unmodified line
546 {552 {
547 slug: MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG,553 slug: MICROSOFT_CLIENT_CREDENTIALS_AUTH_TEMPLATE_SLUG,
548 kind: "oauth2" as const,554 kind: "oauth2" as const,
555 label: MICROSOFT_CLIENT_CREDENTIALS_AUTH_LABEL,
549 authorizationUrl: MICROSOFT_AUTHORIZATION_URL,556 authorizationUrl: MICROSOFT_AUTHORIZATION_URL,
550 tokenUrl: MICROSOFT_TOKEN_URL,557 tokenUrl: MICROSOFT_TOKEN_URL,
551 scopes: [...MICROSOFT_GRAPH_CLIENT_CREDENTIALS_SCOPES],558 scopes: [...MICROSOFT_GRAPH_CLIENT_CREDENTIALS_SCOPES],
packages/plugins/openapi/src/react/auth-method-config.test.ts+16−0
@@ -91,6 +91,22 @@ describe("editor round-trip", () => { · 90 unmodified lines
91 });91 });
92 });92 });
93 93
94 it("oauth stored → editor → stored preserves the display label", () => {
95 const stored: Authentication = {
96 slug: AuthTemplateSlug.make("azureAdDelegated"),
97 kind: "oauth2",
98 label: "OAuth2 (user)",
99 authorizationUrl: "https://x.example/auth",
100 tokenUrl: "https://x.example/token",
101 resource: null,
102 scopes: ["a"],
103 };
104 const editor = editorValueFromAuthentication(stored);
105 const back = authenticationFromEditorValue(editor, "azureAdDelegated");
106 expect(back).toEqual(stored);
107 expect(authMethodsFromConfig([stored])[0]?.label).toBe("OAuth2 (user)");
108 });
109
94 it("none editor value yields no method", () => {110 it("none editor value yields no method", () => {
95 expect(authenticationFromEditorValue({ kind: "none" })).toBeNull();111 expect(authenticationFromEditorValue({ kind: "none" })).toBeNull();
96 });112 });
packages/plugins/openapi/src/react/auth-method-config.ts+3−1
@@ -31,7 +31,7 @@ const oauthAuthMethod = (template: Extract<Authentication, { kind: "oauth2" }>): · 30 unmodified lines
31 const slug = String(template.slug);31 const slug = String(template.slug);
32 return {32 return {
33 id: slug,33 id: slug,
34 label: "OAuth2",34 label: template.label ?? "OAuth2",
35 kind: "oauth",35 kind: "oauth",
36 source: slug.startsWith("custom_") ? "custom" : "spec",36 source: slug.startsWith("custom_") ? "custom" : "spec",
37 template: AuthTemplateSlug.make(slug),37 template: AuthTemplateSlug.make(slug),
@@ -78,6 +78,7 @@ export function editorValueFromAuthentication(template: Authentication): AuthTem · 40 unmodified lines
78 if (template.kind === "oauth2") {78 if (template.kind === "oauth2") {
79 return {79 return {
80 kind: "oauth",80 kind: "oauth",
81 ...(template.label !== undefined ? { label: template.label } : {}),
81 authorizationUrl: template.authorizationUrl ?? "",82 authorizationUrl: template.authorizationUrl ?? "",
82 tokenUrl: template.tokenUrl ?? "",83 tokenUrl: template.tokenUrl ?? "",
83 resource: template.resource ?? null,84 resource: template.resource ?? null,
@@ -95,6 +96,7 @@ const oauthTemplateFromEditorValue = ( · 11 unmodified lines
95): Authentication => ({96): Authentication => ({
96 slug: AuthTemplateSlug.make(slug ?? ""),97 slug: AuthTemplateSlug.make(slug ?? ""),
97 kind: "oauth2",98 kind: "oauth2",
99 ...(value.label !== undefined ? { label: value.label } : {}),
98 authorizationUrl: value.authorizationUrl,100 authorizationUrl: value.authorizationUrl,
99 tokenUrl: value.tokenUrl,101 tokenUrl: value.tokenUrl,
100 resource: value.resource ?? null,102 resource: value.resource ?? null,
packages/plugins/openapi/src/sdk/config.ts+1−0
@@ -32,6 +32,7 @@ import { SpecOverridesSchema, type SpecOverrides } from "./spec-overrides"; · 31 unmodified lines
32const OAuthAuthenticationSchema = Schema.Struct({32const OAuthAuthenticationSchema = Schema.Struct({
33 slug: Schema.String,33 slug: Schema.String,
34 kind: Schema.Literal("oauth2"),34 kind: Schema.Literal("oauth2"),
35 label: Schema.optional(Schema.String),
35 authorizationUrl: Schema.String,36 authorizationUrl: Schema.String,
36 tokenUrl: Schema.String,37 tokenUrl: Schema.String,
37 resource: Schema.optional(Schema.NullOr(Schema.String)),38 resource: Schema.optional(Schema.NullOr(Schema.String)),
packages/plugins/openapi/src/sdk/configure.test.ts+2−2
@@ -29,7 +29,7 @@ import { makeTestConfig, memoryCredentialsPlugin } from "@executor-js/sdk/testin · 28 unmodified lines
29 29
30import { openApiPlugin } from "./plugin";30import { openApiPlugin } from "./plugin";
31import type { APIKeyAuthentication, Authentication, AuthenticationInput } from "./types";31import type { APIKeyAuthentication, Authentication, AuthenticationInput } from "./types";
32import { variable } from "@executor-js/sdk/http-auth";32import { variable, type ApiKeyAuthTemplate } from "@executor-js/sdk/http-auth";
33import {33import {
34 makeOpenApiHttpApiTestIntegrationConfig,34 makeOpenApiHttpApiTestIntegrationConfig,
35 serveOpenApiHttpApiTestServer,35 serveOpenApiHttpApiTestServer,
@@ -83,7 +83,7 @@ const customApiKey: AuthenticationInput = { · 47 unmodified lines
83// omits it, which `configure` should backfill with a generated `custom_<id>`.83// omits it, which `configure` should backfill with a generated `custom_<id>`.
84// The cast is confined to this one boundary helper.84// The cast is confined to this one boundary helper.
85const sluglessApiKey = (85const sluglessApiKey = (
86 template: Omit<AuthenticationInput & { type: "apiKey" }, "slug" | "type">,86 template: Omit<ApiKeyAuthTemplate, "slug" | "type">,
87): AuthenticationInput => ({ type: "apiKey", ...template });87): AuthenticationInput => ({ type: "apiKey", ...template });
88 88
89describe("OpenAPI Plugin — configure (custom auth method)", () => {89describe("OpenAPI Plugin — configure (custom auth method)", () => {
packages/plugins/openapi/src/sdk/derive-auth.test.ts+46−1
@@ -1,6 +1,8 @@
1import { describe, expect, it } from "@effect/vitest";1import { describe, expect, it } from "@effect/vitest";
2import { Option } from "effect";
2 3
3import { resolvedOAuthScopes } from "./derive-auth";4import { detectedAuthenticationTemplates, resolvedOAuthScopes } from "./derive-auth";
5import { OAuth2Preset } from "./preview";
4 6
5describe("resolvedOAuthScopes", () => {7describe("resolvedOAuthScopes", () => {
6 it("does not synthesize OIDC scopes for a plain OAuth provider", () => {8 it("does not synthesize OIDC scopes for a plain OAuth provider", () => {
@@ -26,3 +28,46 @@ describe("resolvedOAuthScopes", () => { · 19 unmodified lines
26 ]);28 ]);
27 });29 });
28});30});
31
32const oauth2Preset = (
33 securitySchemeName: string,
34 flow: "authorizationCode" | "clientCredentials",
35) =>
36 OAuth2Preset.make({
37 label: `OAuth2 ${flow === "authorizationCode" ? "Authorization Code" : "Client Credentials"} · ${securitySchemeName}`,
38 securitySchemeName,
39 flow,
40 authorizationUrl:
41 flow === "authorizationCode" ? Option.some("https://example.com/auth") : Option.none(),
42 tokenUrl: "https://example.com/token",
43 resource: Option.none(),
44 refreshUrl: Option.none(),
45 scopes: { read: "Read access" },
46 identityScopes: "auto",
47 });
48
49describe("detectedAuthenticationTemplates", () => {
50 it("stores no label for a lone oauth2 method", () => {
51 const templates = detectedAuthenticationTemplates(
52 [],
53 [oauth2Preset("oauth_app", "authorizationCode")],
54 "https://example.com",
55 );
56 expect(templates).toHaveLength(1);
57 expect(templates[0]).not.toHaveProperty("label");
58 });
59
60 it("labels each oauth2 method from its preset when several are detected", () => {
61 const templates = detectedAuthenticationTemplates(
62 [],
63 [
64 oauth2Preset("oauth_app", "authorizationCode"),
65 oauth2Preset("oauth_app", "clientCredentials"),
66 ],
67 "https://example.com",
68 );
69 expect(
70 templates.map((template) => (template.kind === "oauth2" ? template.label : null)),
71 ).toEqual(["OAuth2 Authorization Code · oauth_app", "OAuth2 Client Credentials · oauth_app"]);
72 });
73});
packages/plugins/openapi/src/sdk/derive-auth.ts+7−0
@@ -126,9 +126,11 @@ const oauthTemplateFromPreset = ( · 125 unmodified lines
126 baseUrl: string,126 baseUrl: string,
127 slug: AuthTemplateSlug,127 slug: AuthTemplateSlug,
128 scopes: readonly string[],128 scopes: readonly string[],
129 label?: string,
129): OAuthAuthentication => ({130): OAuthAuthentication => ({
130 slug,131 slug,
131 kind: "oauth2",132 kind: "oauth2",
133 ...(label !== undefined ? { label } : {}),
132 authorizationUrl: resolveOAuthUrl(134 authorizationUrl: resolveOAuthUrl(
133 Option.getOrElse(preset.authorizationUrl, () => ""),135 Option.getOrElse(preset.authorizationUrl, () => ""),
134 baseUrl,136 baseUrl,
@@ -161,6 +163,10 @@ export const detectedAuthenticationTemplates = ( · 26 unmodified lines
161 apiKeyTemplateFromHeaderPreset(preset, AuthTemplateSlug.make(`apikey-${index}`)),163 apiKeyTemplateFromHeaderPreset(preset, AuthTemplateSlug.make(`apikey-${index}`)),
162 );164 );
163 });165 });
166 // A lone oauth2 method needs no disambiguating label (it renders as plain
167 // "OAuth2"); with several, each carries its preset label so the stored
168 // methods stay tellable apart in the UI.
169 const labelled = oauth2Presets.length > 1;
164 for (const preset of oauth2Presets) {170 for (const preset of oauth2Presets) {
165 const scopes = resolvedOAuthScopes(Object.keys(preset.scopes), preset.identityScopes);171 const scopes = resolvedOAuthScopes(Object.keys(preset.scopes), preset.identityScopes);
166 templates.push(172 templates.push(
@@ -169,6 +175,7 @@ export const detectedAuthenticationTemplates = ( · 2 unmodified lines
169 baseUrl,175 baseUrl,
170 AuthTemplateSlug.make(`oauth-${preset.securitySchemeName}`),176 AuthTemplateSlug.make(`oauth-${preset.securitySchemeName}`),
171 scopes,177 scopes,
178 labelled ? preset.label : undefined,
172 ),179 ),
173 );180 );
174 }181 }
packages/plugins/openapi/src/sdk/describe-auth-methods.test.ts+17−0
@@ -84,6 +84,23 @@ describe("describeOpenApiAuthMethods", () => { · 83 unmodified lines
84 ]);84 ]);
85 });85 });
86 86
87 it("prefers a stored oauth label over the generic OAuth2 fallback", () => {
88 const methods = describeOpenApiAuthMethods(
89 recordWith([
90 {
91 slug: AuthTemplateSlug.make("azureAdDelegated"),
92 kind: "oauth2",
93 label: "OAuth2 (user)",
94 authorizationUrl: "https://auth.example/authorize",
95 tokenUrl: "https://auth.example/token",
96 scopes: ["read"],
97 },
98 ]),
99 );
100
101 expect(methods.map((method) => method.label)).toEqual(["OAuth2 (user)"]);
102 });
103
87 it("returns [] when no auth template is declared and for a foreign config", () => {104 it("returns [] when no auth template is declared and for a foreign config", () => {
88 expect(describeOpenApiAuthMethods(recordWith([]))).toEqual([]);105 expect(describeOpenApiAuthMethods(recordWith([]))).toEqual([]);
89 expect(106 expect(
packages/plugins/openapi/src/sdk/plugin.ts+2−1
@@ -288,6 +288,7 @@ const AuthenticationSchema = Schema.Union([ · 287 unmodified lines
288 Schema.Struct({288 Schema.Struct({
289 slug: Schema.String,289 slug: Schema.String,
290 kind: Schema.Literal("oauth2"),290 kind: Schema.Literal("oauth2"),
291 label: Schema.optional(Schema.String),
291 authorizationUrl: Schema.String,292 authorizationUrl: Schema.String,
292 tokenUrl: Schema.String,293 tokenUrl: Schema.String,
293 resource: Schema.optional(Schema.NullOr(Schema.String)),294 resource: Schema.optional(Schema.NullOr(Schema.String)),
@@ -580,7 +581,7 @@ export const describeOpenApiAuthMethods = ( · 286 unmodified lines
580 if (template.kind === "oauth2") {581 if (template.kind === "oauth2") {
581 return {582 return {
582 id: String(template.slug),583 id: String(template.slug),
583 label: "OAuth2",584 label: template.label ?? "OAuth2",
584 kind: "oauth",585 kind: "oauth",
585 template: String(template.slug),586 template: String(template.slug),
586 oauth: {587 oauth: {
packages/react/src/components/auth-template-editor.tsx+3−0
@@ -31,6 +31,9 @@ export type AuthTemplateEditorValue = · 30 unmodified lines
31 | { readonly kind: "apikey"; readonly placements: readonly Placement[] }31 | { readonly kind: "apikey"; readonly placements: readonly Placement[] }
32 | {32 | {
33 readonly kind: "oauth";33 readonly kind: "oauth";
34 /** Display label of the stored method, carried through untouched so an
35 * editor round-trip doesn't strip it. Not edited here. */
36 readonly label?: string;
34 readonly authorizationUrl: string;37 readonly authorizationUrl: string;
35 readonly tokenUrl: string;38 readonly tokenUrl: string;
36 readonly resource?: string | null;39 readonly resource?: string | null;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t