mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-05-13 11:20:27 +02:00
Merge main into proxy branch (formatting and docs)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,13 +4,13 @@ import type {
|
||||
GetHttpAuthenticationConfigRequest,
|
||||
JsonPrimitive,
|
||||
PluginDefinition,
|
||||
} from '@yaakapp/api';
|
||||
import type { Algorithm } from 'jsonwebtoken';
|
||||
} from "@yaakapp/api";
|
||||
import type { Algorithm } from "jsonwebtoken";
|
||||
import {
|
||||
buildHostedCallbackRedirectUri,
|
||||
DEFAULT_LOCALHOST_PORT,
|
||||
stopActiveServer,
|
||||
} from './callbackServer';
|
||||
} from "./callbackServer";
|
||||
import {
|
||||
type CallbackType,
|
||||
DEFAULT_PKCE_METHOD,
|
||||
@@ -18,31 +18,31 @@ import {
|
||||
getAuthorizationCode,
|
||||
PKCE_PLAIN,
|
||||
PKCE_SHA256,
|
||||
} from './grants/authorizationCode';
|
||||
} from "./grants/authorizationCode";
|
||||
import {
|
||||
defaultJwtAlgorithm,
|
||||
getClientCredentials,
|
||||
jwtAlgorithms,
|
||||
} from './grants/clientCredentials';
|
||||
import { getImplicit } from './grants/implicit';
|
||||
import { getPassword } from './grants/password';
|
||||
import type { AccessToken, TokenStoreArgs } from './store';
|
||||
import { deleteToken, getToken, resetDataDirKey } from './store';
|
||||
} from "./grants/clientCredentials";
|
||||
import { getImplicit } from "./grants/implicit";
|
||||
import { getPassword } from "./grants/password";
|
||||
import type { AccessToken, TokenStoreArgs } from "./store";
|
||||
import { deleteToken, getToken, resetDataDirKey } from "./store";
|
||||
|
||||
type GrantType = 'authorization_code' | 'implicit' | 'password' | 'client_credentials';
|
||||
type GrantType = "authorization_code" | "implicit" | "password" | "client_credentials";
|
||||
|
||||
const grantTypes: FormInputSelectOption[] = [
|
||||
{ label: 'Authorization Code', value: 'authorization_code' },
|
||||
{ label: 'Implicit', value: 'implicit' },
|
||||
{ label: 'Resource Owner Password Credential', value: 'password' },
|
||||
{ label: 'Client Credentials', value: 'client_credentials' },
|
||||
{ label: "Authorization Code", value: "authorization_code" },
|
||||
{ label: "Implicit", value: "implicit" },
|
||||
{ label: "Resource Owner Password Credential", value: "password" },
|
||||
{ label: "Client Credentials", value: "client_credentials" },
|
||||
];
|
||||
|
||||
const defaultGrantType = grantTypes[0]?.value;
|
||||
|
||||
function hiddenIfNot(
|
||||
grantTypes: GrantType[],
|
||||
...other: ((values: GetHttpAuthenticationConfigRequest['values']) => boolean)[]
|
||||
...other: ((values: GetHttpAuthenticationConfigRequest["values"]) => boolean)[]
|
||||
) {
|
||||
return (_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) => {
|
||||
const hasGrantType = grantTypes.find((t) => t === String(values.grantType ?? defaultGrantType));
|
||||
@@ -53,37 +53,37 @@ function hiddenIfNot(
|
||||
}
|
||||
|
||||
const authorizationUrls = [
|
||||
'https://github.com/login/oauth/authorize',
|
||||
'https://account.box.com/api/oauth2/authorize',
|
||||
'https://accounts.google.com/o/oauth2/v2/auth',
|
||||
'https://api.imgur.com/oauth2/authorize',
|
||||
'https://bitly.com/oauth/authorize',
|
||||
'https://gitlab.example.com/oauth/authorize',
|
||||
'https://medium.com/m/oauth/authorize',
|
||||
'https://public-api.wordpress.com/oauth2/authorize',
|
||||
'https://slack.com/oauth/authorize',
|
||||
'https://todoist.com/oauth/authorize',
|
||||
'https://www.dropbox.com/oauth2/authorize',
|
||||
'https://www.linkedin.com/oauth/v2/authorization',
|
||||
'https://MY_SHOP.myshopify.com/admin/oauth/access_token',
|
||||
'https://appcenter.intuit.com/app/connect/oauth2/authorize',
|
||||
"https://github.com/login/oauth/authorize",
|
||||
"https://account.box.com/api/oauth2/authorize",
|
||||
"https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"https://api.imgur.com/oauth2/authorize",
|
||||
"https://bitly.com/oauth/authorize",
|
||||
"https://gitlab.example.com/oauth/authorize",
|
||||
"https://medium.com/m/oauth/authorize",
|
||||
"https://public-api.wordpress.com/oauth2/authorize",
|
||||
"https://slack.com/oauth/authorize",
|
||||
"https://todoist.com/oauth/authorize",
|
||||
"https://www.dropbox.com/oauth2/authorize",
|
||||
"https://www.linkedin.com/oauth/v2/authorization",
|
||||
"https://MY_SHOP.myshopify.com/admin/oauth/access_token",
|
||||
"https://appcenter.intuit.com/app/connect/oauth2/authorize",
|
||||
];
|
||||
|
||||
const accessTokenUrls = [
|
||||
'https://github.com/login/oauth/access_token',
|
||||
'https://api-ssl.bitly.com/oauth/access_token',
|
||||
'https://api.box.com/oauth2/token',
|
||||
'https://api.dropboxapi.com/oauth2/token',
|
||||
'https://api.imgur.com/oauth2/token',
|
||||
'https://api.medium.com/v1/tokens',
|
||||
'https://gitlab.example.com/oauth/token',
|
||||
'https://public-api.wordpress.com/oauth2/token',
|
||||
'https://slack.com/api/oauth.access',
|
||||
'https://todoist.com/oauth/access_token',
|
||||
'https://www.googleapis.com/oauth2/v4/token',
|
||||
'https://www.linkedin.com/oauth/v2/accessToken',
|
||||
'https://MY_SHOP.myshopify.com/admin/oauth/authorize',
|
||||
'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer',
|
||||
"https://github.com/login/oauth/access_token",
|
||||
"https://api-ssl.bitly.com/oauth/access_token",
|
||||
"https://api.box.com/oauth2/token",
|
||||
"https://api.dropboxapi.com/oauth2/token",
|
||||
"https://api.imgur.com/oauth2/token",
|
||||
"https://api.medium.com/v1/tokens",
|
||||
"https://gitlab.example.com/oauth/token",
|
||||
"https://public-api.wordpress.com/oauth2/token",
|
||||
"https://slack.com/api/oauth.access",
|
||||
"https://todoist.com/oauth/access_token",
|
||||
"https://www.googleapis.com/oauth2/v4/token",
|
||||
"https://www.linkedin.com/oauth/v2/accessToken",
|
||||
"https://MY_SHOP.myshopify.com/admin/oauth/authorize",
|
||||
"https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
|
||||
];
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
@@ -91,59 +91,59 @@ export const plugin: PluginDefinition = {
|
||||
stopActiveServer();
|
||||
},
|
||||
authentication: {
|
||||
name: 'oauth2',
|
||||
label: 'OAuth 2.0',
|
||||
shortLabel: 'OAuth 2',
|
||||
name: "oauth2",
|
||||
label: "OAuth 2.0",
|
||||
shortLabel: "OAuth 2",
|
||||
actions: [
|
||||
{
|
||||
label: 'Copy Current Token',
|
||||
label: "Copy Current Token",
|
||||
async onSelect(ctx, { contextId, values }) {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
authorizationUrl: stringArg(values, "authorizationUrl"),
|
||||
accessTokenUrl: stringArg(values, "accessTokenUrl"),
|
||||
clientId: stringArg(values, "clientId"),
|
||||
};
|
||||
const token = await getToken(ctx, tokenArgs);
|
||||
if (token == null) {
|
||||
await ctx.toast.show({
|
||||
message: 'No token to copy',
|
||||
color: 'warning',
|
||||
message: "No token to copy",
|
||||
color: "warning",
|
||||
});
|
||||
} else {
|
||||
await ctx.clipboard.copyText(token.response.access_token);
|
||||
await ctx.toast.show({
|
||||
message: 'Token copied to clipboard',
|
||||
icon: 'copy',
|
||||
color: 'success',
|
||||
message: "Token copied to clipboard",
|
||||
icon: "copy",
|
||||
color: "success",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Delete Token',
|
||||
label: "Delete Token",
|
||||
async onSelect(ctx, { contextId, values }) {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
authorizationUrl: stringArg(values, "authorizationUrl"),
|
||||
accessTokenUrl: stringArg(values, "accessTokenUrl"),
|
||||
clientId: stringArg(values, "clientId"),
|
||||
};
|
||||
if (await deleteToken(ctx, tokenArgs)) {
|
||||
await ctx.toast.show({
|
||||
message: 'Token deleted',
|
||||
color: 'success',
|
||||
message: "Token deleted",
|
||||
color: "success",
|
||||
});
|
||||
} else {
|
||||
await ctx.toast.show({
|
||||
message: 'No token to delete',
|
||||
color: 'warning',
|
||||
message: "No token to delete",
|
||||
color: "warning",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Clear Window Session',
|
||||
label: "Clear Window Session",
|
||||
async onSelect(ctx, { contextId }) {
|
||||
await resetDataDirKey(ctx, contextId);
|
||||
},
|
||||
@@ -151,85 +151,85 @@ export const plugin: PluginDefinition = {
|
||||
],
|
||||
args: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'grantType',
|
||||
label: 'Grant Type',
|
||||
type: "select",
|
||||
name: "grantType",
|
||||
label: "Grant Type",
|
||||
defaultValue: defaultGrantType,
|
||||
options: grantTypes,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'clientCredentialsMethod',
|
||||
label: 'Authentication Method',
|
||||
type: "select",
|
||||
name: "clientCredentialsMethod",
|
||||
label: "Authentication Method",
|
||||
description:
|
||||
'"Client Secret" sends client_secret. \n' + '"Client Assertion" sends a signed JWT.',
|
||||
defaultValue: 'client_secret',
|
||||
defaultValue: "client_secret",
|
||||
options: [
|
||||
{ label: 'Client Secret', value: 'client_secret' },
|
||||
{ label: 'Client Assertion', value: 'client_assertion' },
|
||||
{ label: "Client Secret", value: "client_secret" },
|
||||
{ label: "Client Assertion", value: "client_assertion" },
|
||||
],
|
||||
dynamic: hiddenIfNot(['client_credentials']),
|
||||
dynamic: hiddenIfNot(["client_credentials"]),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'clientId',
|
||||
label: 'Client ID',
|
||||
type: "text",
|
||||
name: "clientId",
|
||||
label: "Client ID",
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
type: "text",
|
||||
name: "clientSecret",
|
||||
label: "Client Secret",
|
||||
optional: true,
|
||||
password: true,
|
||||
dynamic: hiddenIfNot(
|
||||
['authorization_code', 'password', 'client_credentials'],
|
||||
(values) => values.clientCredentialsMethod === 'client_secret',
|
||||
["authorization_code", "password", "client_credentials"],
|
||||
(values) => values.clientCredentialsMethod === "client_secret",
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'clientAssertionAlgorithm',
|
||||
label: 'JWT Algorithm',
|
||||
type: "select",
|
||||
name: "clientAssertionAlgorithm",
|
||||
label: "JWT Algorithm",
|
||||
defaultValue: defaultJwtAlgorithm,
|
||||
options: jwtAlgorithms.map((value) => ({
|
||||
label: value === 'none' ? 'None' : value,
|
||||
label: value === "none" ? "None" : value,
|
||||
value,
|
||||
})),
|
||||
dynamic: hiddenIfNot(
|
||||
['client_credentials'],
|
||||
({ clientCredentialsMethod }) => clientCredentialsMethod === 'client_assertion',
|
||||
["client_credentials"],
|
||||
({ clientCredentialsMethod }) => clientCredentialsMethod === "client_assertion",
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'clientAssertionSecret',
|
||||
label: 'JWT Secret',
|
||||
type: "text",
|
||||
name: "clientAssertionSecret",
|
||||
label: "JWT Secret",
|
||||
description:
|
||||
'Can be HMAC, PEM or JWK. Make sure you pick the correct algorithm type above.',
|
||||
"Can be HMAC, PEM or JWK. Make sure you pick the correct algorithm type above.",
|
||||
password: true,
|
||||
optional: true,
|
||||
multiLine: true,
|
||||
dynamic: hiddenIfNot(
|
||||
['client_credentials'],
|
||||
({ clientCredentialsMethod }) => clientCredentialsMethod === 'client_assertion',
|
||||
["client_credentials"],
|
||||
({ clientCredentialsMethod }) => clientCredentialsMethod === "client_assertion",
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'clientAssertionSecretBase64',
|
||||
label: 'JWT secret is base64 encoded',
|
||||
type: "checkbox",
|
||||
name: "clientAssertionSecretBase64",
|
||||
label: "JWT secret is base64 encoded",
|
||||
dynamic: hiddenIfNot(
|
||||
['client_credentials'],
|
||||
({ clientCredentialsMethod }) => clientCredentialsMethod === 'client_assertion',
|
||||
["client_credentials"],
|
||||
({ clientCredentialsMethod }) => clientCredentialsMethod === "client_assertion",
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'authorizationUrl',
|
||||
type: "text",
|
||||
name: "authorizationUrl",
|
||||
optional: true,
|
||||
label: 'Authorization URL',
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
label: "Authorization URL",
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"]),
|
||||
placeholder: authorizationUrls[0],
|
||||
completionOptions: authorizationUrls.map((url) => ({
|
||||
label: url,
|
||||
@@ -237,103 +237,103 @@ export const plugin: PluginDefinition = {
|
||||
})),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'accessTokenUrl',
|
||||
type: "text",
|
||||
name: "accessTokenUrl",
|
||||
optional: true,
|
||||
label: 'Access Token URL',
|
||||
label: "Access Token URL",
|
||||
placeholder: accessTokenUrls[0],
|
||||
dynamic: hiddenIfNot(['authorization_code', 'password', 'client_credentials']),
|
||||
dynamic: hiddenIfNot(["authorization_code", "password", "client_credentials"]),
|
||||
completionOptions: accessTokenUrls.map((url) => ({
|
||||
label: url,
|
||||
value: url,
|
||||
})),
|
||||
},
|
||||
{
|
||||
type: 'banner',
|
||||
type: "banner",
|
||||
inputs: [
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'useExternalBrowser',
|
||||
label: 'Use External Browser',
|
||||
type: "checkbox",
|
||||
name: "useExternalBrowser",
|
||||
label: "Use External Browser",
|
||||
description:
|
||||
'Open authorization URL in your system browser instead of the embedded browser. ' +
|
||||
'Useful when the OAuth provider blocks embedded browsers or you need existing browser sessions.',
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
"Open authorization URL in your system browser instead of the embedded browser. " +
|
||||
"Useful when the OAuth provider blocks embedded browsers or you need existing browser sessions.",
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"]),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'redirectUri',
|
||||
label: 'Redirect URI (can be any valid URL)',
|
||||
placeholder: 'https://mysite.example.com/oauth/callback',
|
||||
type: "text",
|
||||
name: "redirectUri",
|
||||
label: "Redirect URI (can be any valid URL)",
|
||||
placeholder: "https://mysite.example.com/oauth/callback",
|
||||
description:
|
||||
'URI the OAuth provider redirects to after authorization. Yaak intercepts this automatically in its embedded browser so any valid URI will work.',
|
||||
"URI the OAuth provider redirects to after authorization. Yaak intercepts this automatically in its embedded browser so any valid URI will work.",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(
|
||||
['authorization_code', 'implicit'],
|
||||
["authorization_code", "implicit"],
|
||||
({ useExternalBrowser }) => !useExternalBrowser,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'h_stack',
|
||||
type: "h_stack",
|
||||
inputs: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'callbackType',
|
||||
label: 'Callback Type',
|
||||
type: "select",
|
||||
name: "callbackType",
|
||||
label: "Callback Type",
|
||||
description:
|
||||
'"Hosted Redirect" uses an external Yaak-hosted endpoint. "Localhost" starts a local server to receive the callback.',
|
||||
defaultValue: 'hosted',
|
||||
defaultValue: "hosted",
|
||||
options: [
|
||||
{ label: 'Hosted Redirect', value: 'hosted' },
|
||||
{ label: 'Localhost', value: 'localhost' },
|
||||
{ label: "Hosted Redirect", value: "hosted" },
|
||||
{ label: "Localhost", value: "localhost" },
|
||||
],
|
||||
dynamic: hiddenIfNot(
|
||||
['authorization_code', 'implicit'],
|
||||
["authorization_code", "implicit"],
|
||||
({ useExternalBrowser }) => !!useExternalBrowser,
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'callbackPort',
|
||||
label: 'Callback Port',
|
||||
type: "text",
|
||||
name: "callbackPort",
|
||||
label: "Callback Port",
|
||||
placeholder: `${DEFAULT_LOCALHOST_PORT}`,
|
||||
description:
|
||||
'Port for the local callback server. Defaults to ' +
|
||||
"Port for the local callback server. Defaults to " +
|
||||
DEFAULT_LOCALHOST_PORT +
|
||||
' if empty.',
|
||||
" if empty.",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(
|
||||
['authorization_code', 'implicit'],
|
||||
["authorization_code", "implicit"],
|
||||
({ useExternalBrowser }) => !!useExternalBrowser,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
type: "banner",
|
||||
color: "info",
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content: 'Redirect URI to Register',
|
||||
type: "markdown",
|
||||
content: "Redirect URI to Register",
|
||||
async dynamic(_ctx, { values }) {
|
||||
const grantType = String(values.grantType ?? defaultGrantType);
|
||||
const useExternalBrowser = !!values.useExternalBrowser;
|
||||
const callbackType = (stringArg(values, 'callbackType') ||
|
||||
'localhost') as CallbackType;
|
||||
const callbackType = (stringArg(values, "callbackType") ||
|
||||
"localhost") as CallbackType;
|
||||
|
||||
// Only show for authorization_code and implicit with external browser enabled
|
||||
if (
|
||||
!['authorization_code', 'implicit'].includes(grantType) ||
|
||||
!["authorization_code", "implicit"].includes(grantType) ||
|
||||
!useExternalBrowser
|
||||
) {
|
||||
return { hidden: true };
|
||||
}
|
||||
|
||||
// Compute the redirect URI based on callback type
|
||||
const port = intArg(values, 'callbackPort') || DEFAULT_LOCALHOST_PORT;
|
||||
const port = intArg(values, "callbackPort") || DEFAULT_LOCALHOST_PORT;
|
||||
let redirectUri: string;
|
||||
if (callbackType === 'hosted') {
|
||||
if (callbackType === "hosted") {
|
||||
redirectUri = buildHostedCallbackRedirectUri(port);
|
||||
} else {
|
||||
redirectUri = `http://127.0.0.1:${port}/callback`;
|
||||
@@ -350,149 +350,149 @@ export const plugin: PluginDefinition = {
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'state',
|
||||
label: 'State',
|
||||
type: "text",
|
||||
name: "state",
|
||||
label: "State",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"]),
|
||||
},
|
||||
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
||||
{ type: 'text', name: 'audience', label: 'Audience', optional: true },
|
||||
{ type: "text", name: "scope", label: "Scope", optional: true },
|
||||
{ type: "text", name: "audience", label: "Audience", optional: true },
|
||||
{
|
||||
type: 'select',
|
||||
name: 'tokenName',
|
||||
label: 'Token for authorization',
|
||||
type: "select",
|
||||
name: "tokenName",
|
||||
label: "Token for authorization",
|
||||
description:
|
||||
'Select which token to send in the "Authorization: Bearer" header. Most APIs expect ' +
|
||||
'access_token, but some (like OpenID Connect) require id_token.',
|
||||
defaultValue: 'access_token',
|
||||
"access_token, but some (like OpenID Connect) require id_token.",
|
||||
defaultValue: "access_token",
|
||||
options: [
|
||||
{ label: 'access_token', value: 'access_token' },
|
||||
{ label: 'id_token', value: 'id_token' },
|
||||
{ label: "access_token", value: "access_token" },
|
||||
{ label: "id_token", value: "id_token" },
|
||||
],
|
||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"]),
|
||||
},
|
||||
{
|
||||
type: 'banner',
|
||||
type: "banner",
|
||||
inputs: [
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'usePkce',
|
||||
label: 'Use PKCE',
|
||||
dynamic: hiddenIfNot(['authorization_code']),
|
||||
type: "checkbox",
|
||||
name: "usePkce",
|
||||
label: "Use PKCE",
|
||||
dynamic: hiddenIfNot(["authorization_code"]),
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'pkceChallengeMethod',
|
||||
label: 'Code Challenge Method',
|
||||
type: "select",
|
||||
name: "pkceChallengeMethod",
|
||||
label: "Code Challenge Method",
|
||||
options: [
|
||||
{ label: 'SHA-256', value: PKCE_SHA256 },
|
||||
{ label: 'Plain', value: PKCE_PLAIN },
|
||||
{ label: "SHA-256", value: PKCE_SHA256 },
|
||||
{ label: "Plain", value: PKCE_PLAIN },
|
||||
],
|
||||
defaultValue: DEFAULT_PKCE_METHOD,
|
||||
dynamic: hiddenIfNot(['authorization_code'], ({ usePkce }) => !!usePkce),
|
||||
dynamic: hiddenIfNot(["authorization_code"], ({ usePkce }) => !!usePkce),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'pkceCodeChallenge',
|
||||
label: 'Code Verifier',
|
||||
placeholder: 'Automatically generated when not set',
|
||||
type: "text",
|
||||
name: "pkceCodeChallenge",
|
||||
label: "Code Verifier",
|
||||
placeholder: "Automatically generated when not set",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['authorization_code'], ({ usePkce }) => !!usePkce),
|
||||
dynamic: hiddenIfNot(["authorization_code"], ({ usePkce }) => !!usePkce),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'h_stack',
|
||||
type: "h_stack",
|
||||
inputs: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: 'Username',
|
||||
type: "text",
|
||||
name: "username",
|
||||
label: "Username",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['password']),
|
||||
dynamic: hiddenIfNot(["password"]),
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
type: "text",
|
||||
name: "password",
|
||||
label: "Password",
|
||||
password: true,
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(['password']),
|
||||
dynamic: hiddenIfNot(["password"]),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'responseType',
|
||||
label: 'Response Type',
|
||||
defaultValue: 'token',
|
||||
type: "select",
|
||||
name: "responseType",
|
||||
label: "Response Type",
|
||||
defaultValue: "token",
|
||||
options: [
|
||||
{ label: 'Access Token', value: 'token' },
|
||||
{ label: 'ID Token', value: 'id_token' },
|
||||
{ label: 'ID and Access Token', value: 'id_token token' },
|
||||
{ label: "Access Token", value: "token" },
|
||||
{ label: "ID Token", value: "id_token" },
|
||||
{ label: "ID and Access Token", value: "id_token token" },
|
||||
],
|
||||
dynamic: hiddenIfNot(['implicit']),
|
||||
dynamic: hiddenIfNot(["implicit"]),
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
label: 'Advanced',
|
||||
type: "accordion",
|
||||
label: "Advanced",
|
||||
inputs: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'headerName',
|
||||
label: 'Header Name',
|
||||
defaultValue: 'Authorization',
|
||||
type: "text",
|
||||
name: "headerName",
|
||||
label: "Header Name",
|
||||
defaultValue: "Authorization",
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'headerPrefix',
|
||||
label: 'Header Prefix',
|
||||
type: "text",
|
||||
name: "headerPrefix",
|
||||
label: "Header Prefix",
|
||||
optional: true,
|
||||
defaultValue: 'Bearer',
|
||||
defaultValue: "Bearer",
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'credentials',
|
||||
label: 'Send Credentials',
|
||||
defaultValue: 'body',
|
||||
type: "select",
|
||||
name: "credentials",
|
||||
label: "Send Credentials",
|
||||
defaultValue: "body",
|
||||
options: [
|
||||
{ label: 'In Request Body', value: 'body' },
|
||||
{ label: 'As Basic Authentication', value: 'basic' },
|
||||
{ label: "In Request Body", value: "body" },
|
||||
{ label: "As Basic Authentication", value: "basic" },
|
||||
],
|
||||
dynamic: (_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) => ({
|
||||
hidden:
|
||||
values.grantType === 'client_credentials' &&
|
||||
values.clientCredentialsMethod === 'client_assertion',
|
||||
values.grantType === "client_credentials" &&
|
||||
values.clientCredentialsMethod === "client_assertion",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
label: 'Access Token Response',
|
||||
type: "accordion",
|
||||
label: "Access Token Response",
|
||||
inputs: [],
|
||||
async dynamic(ctx, { contextId, values }) {
|
||||
const tokenArgs: TokenStoreArgs = {
|
||||
contextId,
|
||||
authorizationUrl: stringArg(values, 'authorizationUrl'),
|
||||
accessTokenUrl: stringArg(values, 'accessTokenUrl'),
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
authorizationUrl: stringArg(values, "authorizationUrl"),
|
||||
accessTokenUrl: stringArg(values, "accessTokenUrl"),
|
||||
clientId: stringArg(values, "clientId"),
|
||||
};
|
||||
const token = await getToken(ctx, tokenArgs);
|
||||
if (token == null) {
|
||||
return { hidden: true };
|
||||
}
|
||||
return {
|
||||
label: 'Access Token Response',
|
||||
label: "Access Token Response",
|
||||
inputs: [
|
||||
{
|
||||
type: 'editor',
|
||||
name: 'response',
|
||||
type: "editor",
|
||||
name: "response",
|
||||
defaultValue: JSON.stringify(token.response, null, 2),
|
||||
hideLabel: true,
|
||||
readOnly: true,
|
||||
language: 'json',
|
||||
language: "json",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -500,101 +500,101 @@ export const plugin: PluginDefinition = {
|
||||
},
|
||||
],
|
||||
async onApply(ctx, { values, contextId }) {
|
||||
const headerPrefix = stringArg(values, 'headerPrefix');
|
||||
const grantType = stringArg(values, 'grantType') as GrantType;
|
||||
const credentialsInBody = values.credentials === 'body';
|
||||
const tokenName = values.tokenName === 'id_token' ? 'id_token' : 'access_token';
|
||||
const headerPrefix = stringArg(values, "headerPrefix");
|
||||
const grantType = stringArg(values, "grantType") as GrantType;
|
||||
const credentialsInBody = values.credentials === "body";
|
||||
const tokenName = values.tokenName === "id_token" ? "id_token" : "access_token";
|
||||
|
||||
// Build external browser options if enabled
|
||||
const useExternalBrowser = !!values.useExternalBrowser;
|
||||
const externalBrowserOptions = useExternalBrowser
|
||||
? {
|
||||
useExternalBrowser: true,
|
||||
callbackType: (stringArg(values, 'callbackType') || 'localhost') as CallbackType,
|
||||
callbackPort: intArg(values, 'callbackPort') ?? undefined,
|
||||
callbackType: (stringArg(values, "callbackType") || "localhost") as CallbackType,
|
||||
callbackPort: intArg(values, "callbackPort") ?? undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
let token: AccessToken;
|
||||
if (grantType === 'authorization_code') {
|
||||
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||
if (grantType === "authorization_code") {
|
||||
const authorizationUrl = stringArg(values, "authorizationUrl");
|
||||
const accessTokenUrl = stringArg(values, "accessTokenUrl");
|
||||
token = await getAuthorizationCode(ctx, contextId, {
|
||||
accessTokenUrl:
|
||||
accessTokenUrl === '' || accessTokenUrl.match(/^https?:\/\//)
|
||||
accessTokenUrl === "" || accessTokenUrl.match(/^https?:\/\//)
|
||||
? accessTokenUrl
|
||||
: `https://${accessTokenUrl}`,
|
||||
authorizationUrl:
|
||||
authorizationUrl === '' || authorizationUrl.match(/^https?:\/\//)
|
||||
authorizationUrl === "" || authorizationUrl.match(/^https?:\/\//)
|
||||
? authorizationUrl
|
||||
: `https://${authorizationUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
clientSecret: stringArg(values, 'clientSecret'),
|
||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
state: stringArgOrNull(values, 'state'),
|
||||
clientId: stringArg(values, "clientId"),
|
||||
clientSecret: stringArg(values, "clientSecret"),
|
||||
redirectUri: stringArgOrNull(values, "redirectUri"),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
state: stringArgOrNull(values, "state"),
|
||||
credentialsInBody,
|
||||
pkce: values.usePkce
|
||||
? {
|
||||
challengeMethod: stringArg(values, 'pkceChallengeMethod') || DEFAULT_PKCE_METHOD,
|
||||
codeVerifier: stringArg(values, 'pkceCodeVerifier') || genPkceCodeVerifier(),
|
||||
challengeMethod: stringArg(values, "pkceChallengeMethod") || DEFAULT_PKCE_METHOD,
|
||||
codeVerifier: stringArg(values, "pkceCodeVerifier") || genPkceCodeVerifier(),
|
||||
}
|
||||
: null,
|
||||
tokenName: tokenName,
|
||||
externalBrowser: externalBrowserOptions,
|
||||
});
|
||||
} else if (grantType === 'implicit') {
|
||||
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
||||
} else if (grantType === "implicit") {
|
||||
const authorizationUrl = stringArg(values, "authorizationUrl");
|
||||
token = await getImplicit(ctx, contextId, {
|
||||
authorizationUrl: authorizationUrl.match(/^https?:\/\//)
|
||||
? authorizationUrl
|
||||
: `https://${authorizationUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||
responseType: stringArg(values, 'responseType'),
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
state: stringArgOrNull(values, 'state'),
|
||||
clientId: stringArg(values, "clientId"),
|
||||
redirectUri: stringArgOrNull(values, "redirectUri"),
|
||||
responseType: stringArg(values, "responseType"),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
state: stringArgOrNull(values, "state"),
|
||||
tokenName: tokenName,
|
||||
externalBrowser: externalBrowserOptions,
|
||||
});
|
||||
} else if (grantType === 'client_credentials') {
|
||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||
} else if (grantType === "client_credentials") {
|
||||
const accessTokenUrl = stringArg(values, "accessTokenUrl");
|
||||
token = await getClientCredentials(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||
? accessTokenUrl
|
||||
: `https://${accessTokenUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
clientAssertionAlgorithm: stringArg(values, 'clientAssertionAlgorithm') as Algorithm,
|
||||
clientSecret: stringArg(values, 'clientSecret'),
|
||||
clientCredentialsMethod: stringArg(values, 'clientCredentialsMethod'),
|
||||
clientAssertionSecret: stringArg(values, 'clientAssertionSecret'),
|
||||
clientId: stringArg(values, "clientId"),
|
||||
clientAssertionAlgorithm: stringArg(values, "clientAssertionAlgorithm") as Algorithm,
|
||||
clientSecret: stringArg(values, "clientSecret"),
|
||||
clientCredentialsMethod: stringArg(values, "clientCredentialsMethod"),
|
||||
clientAssertionSecret: stringArg(values, "clientAssertionSecret"),
|
||||
clientAssertionSecretBase64: !!values.clientAssertionSecretBase64,
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
credentialsInBody,
|
||||
});
|
||||
} else if (grantType === 'password') {
|
||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||
} else if (grantType === "password") {
|
||||
const accessTokenUrl = stringArg(values, "accessTokenUrl");
|
||||
token = await getPassword(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||
? accessTokenUrl
|
||||
: `https://${accessTokenUrl}`,
|
||||
clientId: stringArg(values, 'clientId'),
|
||||
clientSecret: stringArg(values, 'clientSecret'),
|
||||
username: stringArg(values, 'username'),
|
||||
password: stringArg(values, 'password'),
|
||||
scope: stringArgOrNull(values, 'scope'),
|
||||
audience: stringArgOrNull(values, 'audience'),
|
||||
clientId: stringArg(values, "clientId"),
|
||||
clientSecret: stringArg(values, "clientSecret"),
|
||||
username: stringArg(values, "username"),
|
||||
password: stringArg(values, "password"),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
credentialsInBody,
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Invalid grant type ${grantType}`);
|
||||
throw new Error(`Invalid grant type ${String(grantType)}`);
|
||||
}
|
||||
|
||||
const headerName = stringArg(values, 'headerName') || 'Authorization';
|
||||
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
|
||||
const headerName = stringArg(values, "headerName") || "Authorization";
|
||||
const headerValue = `${headerPrefix} ${token.response[tokenName] ?? ""}`.trim();
|
||||
return { setHeaders: [{ name: headerName, value: headerValue }] };
|
||||
},
|
||||
},
|
||||
@@ -605,19 +605,19 @@ function stringArgOrNull(
|
||||
name: string,
|
||||
): string | null {
|
||||
const arg = values[name];
|
||||
if (arg == null || arg === '') return null;
|
||||
if (arg == null || arg === "") return null;
|
||||
return `${arg}`;
|
||||
}
|
||||
|
||||
function stringArg(values: Record<string, JsonPrimitive | undefined>, name: string): string {
|
||||
const arg = stringArgOrNull(values, name);
|
||||
if (!arg) return '';
|
||||
if (!arg) return "";
|
||||
return arg;
|
||||
}
|
||||
|
||||
function intArg(values: Record<string, JsonPrimitive | undefined>, name: string): number | null {
|
||||
const arg = values[name];
|
||||
if (arg == null || arg === '') return null;
|
||||
if (arg == null || arg === "") return null;
|
||||
const num = parseInt(`${arg}`, 10);
|
||||
return Number.isNaN(num) ? null : num;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user