Files
Automata/packages/@n8n/client-oauth2/src/CodeFlow.ts
Giulio Andreini 42721dba80 feat(Twitter Node): Node overhaul (#4788)
* First node set up.

* Progress: all Resources and Operations updated

* Upsates to all resources.

* Updated tooltip for Tweet > Search > Tweet fields.

* Upodate to resource locator items in User > Search.

* Added e.g. to placeholders and minor copy tweaks.

* Fixed Operations sorting.

* Added a couple of operations back.

* Removed 'Authorized API Call'.

* Remove authorization header when empty

* Import pkce

* Add OAuth2 with new grant type to Twitter

* Add pkce logic auto assign authorization code if pkce not defined

* Add pkce to ui and interfaces

* Fix scopes for Oauth2 twitter

* Deubg + pass it through header

* Add debug console, add airtable cred

* Remove all console.logs, make PKCE in th body only when it exists

* Remove invalid character ~

* Remove more console.logs

* remove body inside query

* Remove useless grantype check

* Hide oauth2 twitter waiting for overhaul

* Remove redundant header removal

* Remove more console.logs

* Add V2 twitter

* Add V1

* Fix description V1, V2

* Fix description for V1

* Add Oauth2 request

* Add user lookup

* Add search username by ID

* Search tweet feat

* Wip create tweet

* Generic function for returning ID

* Add like and retweet feat

* Add delete tweet feat

* Fix Location tweets

* Fix type

* Feat List add members

* Add scopes for dm and list

* Add direct message feature

* Improve response data

* Fix regex

* Add unit test to Twitter v2

* Fix unit test

* Remove console.logs

* Remove more console.logs

* Handle @ in username

* Minor copy tweaks.

* Add return all logic

* Add error for api permission error

* Update message api error

* Add error for date error

* Add notice for TwitterOAuth2 api link

* Fix display names location

* fix List RLC

* Fix like endpoint

* Fix error message check

* fix(core): Fix OAuth2 callback for grantType=clientCredentials

* Improve fix for callback

* update pnpm

* Fix iso time for end time

* sync oauth2Credential

* remove unused codeVerifer in Server.ts

* Add location and attachments notice

* Add notice to oauth1

* Improve notice for twitter

* moved credentials notice to TwitterOAuth1Api.credentials.ts

---------

Co-authored-by: agobrech <ael.gobrecht@gmail.com>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
Co-authored-by: Marcus <marcus@n8n.io>
2023-06-28 12:19:25 +02:00

124 lines
3.6 KiB
TypeScript

import * as qs from 'querystring';
import type { ClientOAuth2, ClientOAuth2Options } from './ClientOAuth2';
import type { ClientOAuth2Token, ClientOAuth2TokenData } from './ClientOAuth2Token';
import { DEFAULT_HEADERS, DEFAULT_URL_BASE } from './constants';
import { auth, expects, getAuthError, getRequestOptions } from './utils';
interface CodeFlowBody {
code: string | string[];
grant_type: 'authorization_code';
redirect_uri?: string;
client_id?: string;
}
/**
* Support authorization code OAuth 2.0 grant.
*
* Reference: http://tools.ietf.org/html/rfc6749#section-4.1
*/
export class CodeFlow {
constructor(private client: ClientOAuth2) {}
/**
* Generate the uri for doing the first redirect.
*/
getUri(opts?: Partial<ClientOAuth2Options>): string {
const options: ClientOAuth2Options = { ...this.client.options, ...opts };
// Check the required parameters are set.
expects(options, 'clientId', 'authorizationUri');
const url = new URL(options.authorizationUri);
const queryParams = {
...options.query,
client_id: options.clientId,
redirect_uri: options.redirectUri,
response_type: 'code',
state: options.state,
...(options.scopes ? { scope: options.scopes.join(options.scopesSeparator ?? ' ') } : {}),
};
for (const [key, value] of Object.entries(queryParams)) {
if (value !== null && value !== undefined) {
url.searchParams.append(key, value);
}
}
return url.toString();
}
/**
* Get the code token from the redirected uri and make another request for
* the user access token.
*/
async getToken(
uri: string | URL,
opts?: Partial<ClientOAuth2Options>,
): Promise<ClientOAuth2Token> {
const options = { ...this.client.options, ...opts };
expects(options, 'clientId', 'accessTokenUri');
const url = uri instanceof URL ? uri : new URL(uri, DEFAULT_URL_BASE);
if (
typeof options.redirectUri === 'string' &&
typeof url.pathname === 'string' &&
url.pathname !== new URL(options.redirectUri, DEFAULT_URL_BASE).pathname
) {
throw new TypeError('Redirected path should match configured path, but got: ' + url.pathname);
}
if (!url.search?.substring(1)) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new TypeError(`Unable to process uri: ${uri.toString()}`);
}
const data =
typeof url.search === 'string' ? qs.parse(url.search.substring(1)) : url.search || {};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const error = getAuthError(data);
if (error) throw error;
if (options.state && data.state !== options.state) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new TypeError(`Invalid state: ${data.state}`);
}
// Check whether the response code is set.
if (!data.code) {
throw new TypeError('Missing code, unable to request token');
}
const headers = { ...DEFAULT_HEADERS };
const body: CodeFlowBody = {
code: data.code,
grant_type: 'authorization_code',
redirect_uri: options.redirectUri,
};
// `client_id`: REQUIRED, if the client is not authenticating with the
// authorization server as described in Section 3.2.1.
// Reference: https://tools.ietf.org/html/rfc6749#section-3.2.1
if (options.clientSecret) {
headers.Authorization = auth(options.clientId, options.clientSecret);
} else {
body.client_id = options.clientId;
}
const requestOptions = getRequestOptions(
{
url: options.accessTokenUri,
method: 'POST',
headers,
body,
},
options,
);
const responseData = await this.client.request<ClientOAuth2TokenData>(requestOptions);
return this.client.createToken(responseData);
}
}