mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Support id_token for OAuth 2.0
https://feedback.yaak.app/p/unable-to-use-idtoken-for-auth-in-authorization-code-oauth2
This commit is contained in:
@@ -282,15 +282,9 @@ export class PluginInstance {
|
|||||||
this.#importModule();
|
this.#importModule();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Plugin call threw exception', payload.type, err);
|
const error = `${err}`.replace(/^Error:\s*/g, '');
|
||||||
this.#sendPayload(
|
console.log('Plugin call threw exception', payload.type, '→', error);
|
||||||
windowContext,
|
this.#sendPayload(windowContext, { type: 'error_response', error }, replyId);
|
||||||
{
|
|
||||||
type: 'error_response',
|
|
||||||
error: `${err}`,
|
|
||||||
},
|
|
||||||
replyId,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { readFileSync } from 'node:fs';
|
|||||||
import { AccessTokenRawResponse } from './store';
|
import { AccessTokenRawResponse } from './store';
|
||||||
|
|
||||||
export async function getAccessToken(
|
export async function getAccessToken(
|
||||||
ctx: Context, {
|
ctx: Context,
|
||||||
|
{
|
||||||
accessTokenUrl,
|
accessTokenUrl,
|
||||||
scope,
|
scope,
|
||||||
audience,
|
audience,
|
||||||
@@ -21,17 +22,15 @@ export async function getAccessToken(
|
|||||||
audience: string | null;
|
audience: string | null;
|
||||||
credentialsInBody: boolean;
|
credentialsInBody: boolean;
|
||||||
params: HttpUrlParameter[];
|
params: HttpUrlParameter[];
|
||||||
}): Promise<AccessTokenRawResponse> {
|
},
|
||||||
|
): Promise<AccessTokenRawResponse> {
|
||||||
console.log('Getting access token', accessTokenUrl);
|
console.log('Getting access token', accessTokenUrl);
|
||||||
const httpRequest: Partial<HttpRequest> = {
|
const httpRequest: Partial<HttpRequest> = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: accessTokenUrl,
|
url: accessTokenUrl,
|
||||||
bodyType: 'application/x-www-form-urlencoded',
|
bodyType: 'application/x-www-form-urlencoded',
|
||||||
body: {
|
body: {
|
||||||
form: [
|
form: [{ name: 'grant_type', value: grantType }, ...params],
|
||||||
{ name: 'grant_type', value: grantType },
|
|
||||||
...params,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
headers: [
|
headers: [
|
||||||
{ name: 'User-Agent', value: 'yaak' },
|
{ name: 'User-Agent', value: 'yaak' },
|
||||||
@@ -56,7 +55,9 @@ export async function getAccessToken(
|
|||||||
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
|
const body = resp.bodyPath ? readFileSync(resp.bodyPath, 'utf8') : '';
|
||||||
|
|
||||||
if (resp.status < 200 || resp.status >= 300) {
|
if (resp.status < 200 || resp.status >= 300) {
|
||||||
throw new Error('Failed to fetch access token with status=' + resp.status + ' and body=' + body);
|
throw new Error(
|
||||||
|
'Failed to fetch access token with status=' + resp.status + ' and body=' + body,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export async function getAuthorizationCode(
|
|||||||
audience,
|
audience,
|
||||||
credentialsInBody,
|
credentialsInBody,
|
||||||
pkce,
|
pkce,
|
||||||
|
tokenName,
|
||||||
}: {
|
}: {
|
||||||
authorizationUrl: string;
|
authorizationUrl: string;
|
||||||
accessTokenUrl: string;
|
accessTokenUrl: string;
|
||||||
@@ -36,6 +37,7 @@ export async function getAuthorizationCode(
|
|||||||
challengeMethod: string | null;
|
challengeMethod: string | null;
|
||||||
codeVerifier: string | null;
|
codeVerifier: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
tokenName: 'access_token' | 'id_token';
|
||||||
},
|
},
|
||||||
): Promise<AccessToken> {
|
): Promise<AccessToken> {
|
||||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||||
@@ -59,7 +61,10 @@ export async function getAuthorizationCode(
|
|||||||
if (pkce) {
|
if (pkce) {
|
||||||
const verifier = pkce.codeVerifier || createPkceCodeVerifier();
|
const verifier = pkce.codeVerifier || createPkceCodeVerifier();
|
||||||
const challengeMethod = pkce.challengeMethod || DEFAULT_PKCE_METHOD;
|
const challengeMethod = pkce.challengeMethod || DEFAULT_PKCE_METHOD;
|
||||||
authorizationUrl.searchParams.set('code_challenge', createPkceCodeChallenge(verifier, challengeMethod));
|
authorizationUrl.searchParams.set(
|
||||||
|
'code_challenge',
|
||||||
|
createPkceCodeChallenge(verifier, challengeMethod),
|
||||||
|
);
|
||||||
authorizationUrl.searchParams.set('code_challenge_method', challengeMethod);
|
authorizationUrl.searchParams.set('code_challenge_method', challengeMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +112,7 @@ export async function getAuthorizationCode(
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resolve(await storeToken(ctx, contextId, response));
|
resolve(await storeToken(ctx, contextId, response, tokenName));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
@@ -127,14 +132,15 @@ function createPkceCodeChallenge(verifier: string, method: string) {
|
|||||||
|
|
||||||
const hash = encodeForPkce(createHash('sha256').update(verifier).digest());
|
const hash = encodeForPkce(createHash('sha256').update(verifier).digest());
|
||||||
return hash
|
return hash
|
||||||
.replace(/=/g, '') // Remove padding '='
|
.replace(/=/g, '') // Remove padding '='
|
||||||
.replace(/\+/g, '-') // Replace '+' with '-'
|
.replace(/\+/g, '-') // Replace '+' with '-'
|
||||||
.replace(/\//g, '_'); // Replace '/' with '_'
|
.replace(/\//g, '_'); // Replace '/' with '_'
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeForPkce(bytes: Buffer) {
|
function encodeForPkce(bytes: Buffer) {
|
||||||
return bytes.toString('base64')
|
return bytes
|
||||||
.replace(/=/g, '') // Remove padding '='
|
.toString('base64')
|
||||||
|
.replace(/=/g, '') // Remove padding '='
|
||||||
.replace(/\+/g, '-') // Replace '+' with '-'
|
.replace(/\+/g, '-') // Replace '+' with '-'
|
||||||
.replace(/\//g, '_'); // Replace '/' with '_'
|
.replace(/\//g, '_'); // Replace '/' with '_'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export function getImplicit(
|
|||||||
scope,
|
scope,
|
||||||
state,
|
state,
|
||||||
audience,
|
audience,
|
||||||
|
tokenName,
|
||||||
}: {
|
}: {
|
||||||
authorizationUrl: string;
|
authorizationUrl: string;
|
||||||
responseType: string;
|
responseType: string;
|
||||||
@@ -20,8 +21,9 @@ export function getImplicit(
|
|||||||
scope: string | null;
|
scope: string | null;
|
||||||
state: string | null;
|
state: string | null;
|
||||||
audience: string | null;
|
audience: string | null;
|
||||||
|
tokenName: 'access_token' | 'id_token';
|
||||||
},
|
},
|
||||||
) :Promise<AccessToken> {
|
): Promise<AccessToken> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const token = await getToken(ctx, contextId);
|
const token = await getToken(ctx, contextId);
|
||||||
if (token) {
|
if (token) {
|
||||||
@@ -38,7 +40,10 @@ export function getImplicit(
|
|||||||
if (state) authorizationUrl.searchParams.set('state', state);
|
if (state) authorizationUrl.searchParams.set('state', state);
|
||||||
if (audience) authorizationUrl.searchParams.set('audience', audience);
|
if (audience) authorizationUrl.searchParams.set('audience', audience);
|
||||||
if (responseType.includes('id_token')) {
|
if (responseType.includes('id_token')) {
|
||||||
authorizationUrl.searchParams.set('nonce', String(Math.floor(Math.random() * 9999999999999) + 1));
|
authorizationUrl.searchParams.set(
|
||||||
|
'nonce',
|
||||||
|
String(Math.floor(Math.random() * 9999999999999) + 1),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const authorizationUrlStr = authorizationUrl.toString();
|
const authorizationUrlStr = authorizationUrl.toString();
|
||||||
@@ -60,7 +65,7 @@ export function getImplicit(
|
|||||||
const hash = url.hash.slice(1);
|
const hash = url.hash.slice(1);
|
||||||
const params = new URLSearchParams(hash);
|
const params = new URLSearchParams(hash);
|
||||||
|
|
||||||
const accessToken = params.get('access_token');
|
const accessToken = params.get(tokenName);
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ import {
|
|||||||
JsonPrimitive,
|
JsonPrimitive,
|
||||||
PluginDefinition,
|
PluginDefinition,
|
||||||
} from '@yaakapp/api';
|
} from '@yaakapp/api';
|
||||||
import { DEFAULT_PKCE_METHOD, getAuthorizationCode, PKCE_PLAIN, PKCE_SHA256 } from './grants/authorizationCode';
|
import {
|
||||||
|
DEFAULT_PKCE_METHOD,
|
||||||
|
getAuthorizationCode,
|
||||||
|
PKCE_PLAIN,
|
||||||
|
PKCE_SHA256,
|
||||||
|
} from './grants/authorizationCode';
|
||||||
import { getClientCredentials } from './grants/clientCredentials';
|
import { getClientCredentials } from './grants/clientCredentials';
|
||||||
import { getImplicit } from './grants/implicit';
|
import { getImplicit } from './grants/implicit';
|
||||||
import { getPassword } from './grants/password';
|
import { getPassword } from './grants/password';
|
||||||
@@ -22,10 +27,13 @@ const grantTypes: FormInputSelectOption[] = [
|
|||||||
|
|
||||||
const defaultGrantType = grantTypes[0]!.value;
|
const defaultGrantType = grantTypes[0]!.value;
|
||||||
|
|
||||||
function hiddenIfNot(grantTypes: GrantType[], ...other: ((values: GetHttpAuthenticationConfigRequest['values']) => boolean)[]) {
|
function hiddenIfNot(
|
||||||
|
grantTypes: GrantType[],
|
||||||
|
...other: ((values: GetHttpAuthenticationConfigRequest['values']) => boolean)[]
|
||||||
|
) {
|
||||||
return (_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) => {
|
return (_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) => {
|
||||||
const hasGrantType = grantTypes.find(t => t === String(values.grantType ?? defaultGrantType));
|
const hasGrantType = grantTypes.find((t) => t === String(values.grantType ?? defaultGrantType));
|
||||||
const hasOtherBools = other.every(t => t(values));
|
const hasOtherBools = other.every((t) => t(values));
|
||||||
const show = hasGrantType && hasOtherBools;
|
const show = hasGrantType && hasOtherBools;
|
||||||
return { hidden: !show };
|
return { hidden: !show };
|
||||||
};
|
};
|
||||||
@@ -77,7 +85,11 @@ export const plugin: PluginDefinition = {
|
|||||||
await ctx.toast.show({ message: 'No token to copy', color: 'warning' });
|
await ctx.toast.show({ message: 'No token to copy', color: 'warning' });
|
||||||
} else {
|
} else {
|
||||||
await ctx.clipboard.copyText(token.response.access_token);
|
await ctx.clipboard.copyText(token.response.access_token);
|
||||||
await ctx.toast.show({ message: 'Token copied to clipboard', icon: 'copy', color: 'success' });
|
await ctx.toast.show({
|
||||||
|
message: 'Token copied to clipboard',
|
||||||
|
icon: 'copy',
|
||||||
|
color: 'success',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -130,7 +142,7 @@ export const plugin: PluginDefinition = {
|
|||||||
label: 'Authorization URL',
|
label: 'Authorization URL',
|
||||||
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||||
placeholder: authorizationUrls[0],
|
placeholder: authorizationUrls[0],
|
||||||
completionOptions: authorizationUrls.map(url => ({ label: url, value: url })),
|
completionOptions: authorizationUrls.map((url) => ({ label: url, value: url })),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -139,7 +151,7 @@ export const plugin: PluginDefinition = {
|
|||||||
label: 'Access Token URL',
|
label: 'Access Token URL',
|
||||||
placeholder: accessTokenUrls[0],
|
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 })),
|
completionOptions: accessTokenUrls.map((url) => ({ label: url, value: url })),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -161,6 +173,20 @@ export const plugin: PluginDefinition = {
|
|||||||
label: 'Audience',
|
label: 'Audience',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
options: [
|
||||||
|
{ label: 'access_token', value: 'access_token' },
|
||||||
|
{ label: 'id_token', value: 'id_token' },
|
||||||
|
],
|
||||||
|
dynamic: hiddenIfNot(['authorization_code', 'implicit']),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
name: 'usePkce',
|
name: 'usePkce',
|
||||||
@@ -171,7 +197,10 @@ export const plugin: PluginDefinition = {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'pkceChallengeMethod',
|
name: 'pkceChallengeMethod',
|
||||||
label: 'Code Challenge Method',
|
label: 'Code Challenge Method',
|
||||||
options: [{ label: 'SHA-256', value: PKCE_SHA256 }, { label: 'Plain', value: PKCE_PLAIN }],
|
options: [
|
||||||
|
{ label: 'SHA-256', value: PKCE_SHA256 },
|
||||||
|
{ label: 'Plain', value: PKCE_PLAIN },
|
||||||
|
],
|
||||||
defaultValue: DEFAULT_PKCE_METHOD,
|
defaultValue: DEFAULT_PKCE_METHOD,
|
||||||
dynamic: hiddenIfNot(['authorization_code'], ({ usePkce }) => !!usePkce),
|
dynamic: hiddenIfNot(['authorization_code'], ({ usePkce }) => !!usePkce),
|
||||||
},
|
},
|
||||||
@@ -215,9 +244,19 @@ export const plugin: PluginDefinition = {
|
|||||||
label: 'Advanced',
|
label: 'Advanced',
|
||||||
inputs: [
|
inputs: [
|
||||||
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
{ type: 'text', name: 'scope', label: 'Scope', optional: true },
|
||||||
{ type: 'text', name: 'headerPrefix', label: 'Header Prefix', optional: true, defaultValue: 'Bearer' },
|
|
||||||
{
|
{
|
||||||
type: 'select', name: 'credentials', label: 'Send Credentials', defaultValue: 'body', options: [
|
type: 'text',
|
||||||
|
name: 'headerPrefix',
|
||||||
|
label: 'Header Prefix',
|
||||||
|
optional: true,
|
||||||
|
defaultValue: 'Bearer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'credentials',
|
||||||
|
label: 'Send Credentials',
|
||||||
|
defaultValue: 'body',
|
||||||
|
options: [
|
||||||
{ label: 'In Request Body', value: 'body' },
|
{ label: 'In Request Body', value: 'body' },
|
||||||
{ label: 'As Basic Authentication', value: 'basic' },
|
{ label: 'As Basic Authentication', value: 'basic' },
|
||||||
],
|
],
|
||||||
@@ -257,8 +296,12 @@ export const plugin: PluginDefinition = {
|
|||||||
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
||||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||||
token = await getAuthorizationCode(ctx, contextId, {
|
token = await getAuthorizationCode(ctx, contextId, {
|
||||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//) ? accessTokenUrl : `https://${accessTokenUrl}`,
|
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||||
authorizationUrl: authorizationUrl.match(/^https?:\/\//) ? authorizationUrl : `https://${authorizationUrl}`,
|
? accessTokenUrl
|
||||||
|
: `https://${accessTokenUrl}`,
|
||||||
|
authorizationUrl: authorizationUrl.match(/^https?:\/\//)
|
||||||
|
? authorizationUrl
|
||||||
|
: `https://${authorizationUrl}`,
|
||||||
clientId: stringArg(values, 'clientId'),
|
clientId: stringArg(values, 'clientId'),
|
||||||
clientSecret: stringArg(values, 'clientSecret'),
|
clientSecret: stringArg(values, 'clientSecret'),
|
||||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||||
@@ -266,26 +309,34 @@ export const plugin: PluginDefinition = {
|
|||||||
audience: stringArgOrNull(values, 'audience'),
|
audience: stringArgOrNull(values, 'audience'),
|
||||||
state: stringArgOrNull(values, 'state'),
|
state: stringArgOrNull(values, 'state'),
|
||||||
credentialsInBody,
|
credentialsInBody,
|
||||||
pkce: values.usePkce ? {
|
pkce: values.usePkce
|
||||||
challengeMethod: stringArg(values, 'pkceChallengeMethod'),
|
? {
|
||||||
codeVerifier: stringArgOrNull(values, 'pkceCodeVerifier'),
|
challengeMethod: stringArg(values, 'pkceChallengeMethod'),
|
||||||
} : null,
|
codeVerifier: stringArgOrNull(values, 'pkceCodeVerifier'),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
tokenName: values.tokenName === 'id_token' ? 'id_token' : 'access_token',
|
||||||
});
|
});
|
||||||
} else if (grantType === 'implicit') {
|
} else if (grantType === 'implicit') {
|
||||||
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
const authorizationUrl = stringArg(values, 'authorizationUrl');
|
||||||
token = await getImplicit(ctx, contextId, {
|
token = await getImplicit(ctx, contextId, {
|
||||||
authorizationUrl: authorizationUrl.match(/^https?:\/\//) ? authorizationUrl : `https://${authorizationUrl}`,
|
authorizationUrl: authorizationUrl.match(/^https?:\/\//)
|
||||||
|
? authorizationUrl
|
||||||
|
: `https://${authorizationUrl}`,
|
||||||
clientId: stringArg(values, 'clientId'),
|
clientId: stringArg(values, 'clientId'),
|
||||||
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
redirectUri: stringArgOrNull(values, 'redirectUri'),
|
||||||
responseType: stringArg(values, 'responseType'),
|
responseType: stringArg(values, 'responseType'),
|
||||||
scope: stringArgOrNull(values, 'scope'),
|
scope: stringArgOrNull(values, 'scope'),
|
||||||
audience: stringArgOrNull(values, 'audience'),
|
audience: stringArgOrNull(values, 'audience'),
|
||||||
state: stringArgOrNull(values, 'state'),
|
state: stringArgOrNull(values, 'state'),
|
||||||
|
tokenName: values.tokenName === 'id_token' ? 'id_token' : 'access_token',
|
||||||
});
|
});
|
||||||
} else if (grantType === 'client_credentials') {
|
} else if (grantType === 'client_credentials') {
|
||||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||||
token = await getClientCredentials(ctx, contextId, {
|
token = await getClientCredentials(ctx, contextId, {
|
||||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//) ? accessTokenUrl : `https://${accessTokenUrl}`,
|
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||||
|
? accessTokenUrl
|
||||||
|
: `https://${accessTokenUrl}`,
|
||||||
clientId: stringArg(values, 'clientId'),
|
clientId: stringArg(values, 'clientId'),
|
||||||
clientSecret: stringArg(values, 'clientSecret'),
|
clientSecret: stringArg(values, 'clientSecret'),
|
||||||
scope: stringArgOrNull(values, 'scope'),
|
scope: stringArgOrNull(values, 'scope'),
|
||||||
@@ -295,7 +346,9 @@ export const plugin: PluginDefinition = {
|
|||||||
} else if (grantType === 'password') {
|
} else if (grantType === 'password') {
|
||||||
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
const accessTokenUrl = stringArg(values, 'accessTokenUrl');
|
||||||
token = await getPassword(ctx, contextId, {
|
token = await getPassword(ctx, contextId, {
|
||||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//) ? accessTokenUrl : `https://${accessTokenUrl}`,
|
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//)
|
||||||
|
? accessTokenUrl
|
||||||
|
: `https://${accessTokenUrl}`,
|
||||||
clientId: stringArg(values, 'clientId'),
|
clientId: stringArg(values, 'clientId'),
|
||||||
clientSecret: stringArg(values, 'clientSecret'),
|
clientSecret: stringArg(values, 'clientSecret'),
|
||||||
username: stringArg(values, 'username'),
|
username: stringArg(values, 'username'),
|
||||||
@@ -310,16 +363,21 @@ export const plugin: PluginDefinition = {
|
|||||||
|
|
||||||
const headerValue = `${headerPrefix} ${token.response.access_token}`.trim();
|
const headerValue = `${headerPrefix} ${token.response.access_token}`.trim();
|
||||||
return {
|
return {
|
||||||
setHeaders: [{
|
setHeaders: [
|
||||||
name: 'Authorization',
|
{
|
||||||
value: headerValue,
|
name: 'Authorization',
|
||||||
}],
|
value: headerValue,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function stringArgOrNull(values: Record<string, JsonPrimitive | undefined>, name: string): string | null {
|
function stringArgOrNull(
|
||||||
|
values: Record<string, JsonPrimitive | undefined>,
|
||||||
|
name: string,
|
||||||
|
): string | null {
|
||||||
const arg = values[name];
|
const arg = values[name];
|
||||||
if (arg == null || arg == '') return null;
|
if (arg == null || arg == '') return null;
|
||||||
return `${arg}`;
|
return `${arg}`;
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { Context } from '@yaakapp/api';
|
import { Context } from '@yaakapp/api';
|
||||||
|
|
||||||
export async function storeToken(ctx: Context, contextId: string, response: AccessTokenRawResponse) {
|
export async function storeToken(
|
||||||
if (!response.access_token) {
|
ctx: Context,
|
||||||
throw new Error(`Token not found in response`);
|
contextId: string,
|
||||||
|
response: AccessTokenRawResponse,
|
||||||
|
tokenName: 'access_token' | 'id_token' = 'access_token',
|
||||||
|
) {
|
||||||
|
if (!response[tokenName]) {
|
||||||
|
throw new Error(`${tokenName} not found in response ${Object.keys(response).join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const expiresAt = response.expires_in ? Date.now() + response.expires_in * 1000 : null;
|
const expiresAt = response.expires_in ? Date.now() + response.expires_in * 1000 : null;
|
||||||
@@ -41,12 +46,13 @@ function dataDirStoreKey(context_id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessToken {
|
export interface AccessToken {
|
||||||
response: AccessTokenRawResponse,
|
response: AccessTokenRawResponse;
|
||||||
expiresAt: number | null;
|
expiresAt: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessTokenRawResponse {
|
export interface AccessTokenRawResponse {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
|
id_token?: string;
|
||||||
token_type?: string;
|
token_type?: string;
|
||||||
expires_in?: number;
|
expires_in?: number;
|
||||||
refresh_token?: string;
|
refresh_token?: string;
|
||||||
|
|||||||
@@ -530,6 +530,7 @@ impl PluginManager {
|
|||||||
InternalEventPayload::EmptyResponse(_) => {
|
InternalEventPayload::EmptyResponse(_) => {
|
||||||
Err(PluginErr("Auth plugin returned empty".to_string()))
|
Err(PluginErr("Auth plugin returned empty".to_string()))
|
||||||
}
|
}
|
||||||
|
InternalEventPayload::ErrorResponse(e) => Err(PluginErr(e.error)),
|
||||||
e => Err(PluginErr(format!("Auth plugin returned invalid event {:?}", e))),
|
e => Err(PluginErr(format!("Auth plugin returned invalid event {:?}", e))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,6 +602,7 @@ impl PluginManager {
|
|||||||
InternalEventPayload::EmptyResponse(_) => {
|
InternalEventPayload::EmptyResponse(_) => {
|
||||||
Err(PluginErr("Auth plugin returned empty".to_string()))
|
Err(PluginErr("Auth plugin returned empty".to_string()))
|
||||||
}
|
}
|
||||||
|
InternalEventPayload::ErrorResponse(e) => Err(PluginErr(e.error)),
|
||||||
e => Err(PluginErr(format!("Auth plugin returned invalid event {:?}", e))),
|
e => Err(PluginErr(format!("Auth plugin returned invalid event {:?}", e))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ impl PluginRuntimeServerWebsocket {
|
|||||||
// Parse everything but the payload so we can catch errors on that, specifically
|
// Parse everything but the payload so we can catch errors on that, specifically
|
||||||
let payload = serde_json::from_value::<InternalEventPayload>(event.payload.clone())
|
let payload = serde_json::from_value::<InternalEventPayload>(event.payload.clone())
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
warn!("Plugin error from {}: {:?} {}", event.plugin_name, e, event.payload);
|
warn!("Plugin event parse error from {}: {:?} {}", event.plugin_name, e, event.payload);
|
||||||
InternalEventPayload::ErrorResponse(ErrorResponse {
|
InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||||
error: format!("Plugin error from {}: {e:?}", event.plugin_name),
|
error: format!("Plugin event parse error from {}: {e:?}", event.plugin_name),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user