From bfb9b2bf756a01c82b9d32b50fafe9221aee3989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Thu, 16 Mar 2023 11:22:24 +0100 Subject: [PATCH 01/11] feat(core): Replace client-oauth2 with an in-repo package --- packages/@n8n_io/client-oauth2/.eslintrc.js | 14 ++ packages/@n8n_io/client-oauth2/package.json | 25 +++ .../@n8n_io/client-oauth2/src/ClientOAuth2.ts | 116 +++++++++++++ .../client-oauth2/src/ClientOAuth2Token.ts | 102 +++++++++++ .../@n8n_io/client-oauth2/src/CodeFlow.ts | 117 +++++++++++++ .../client-oauth2/src/CredentialsFlow.ts | 54 ++++++ .../@n8n_io/client-oauth2/src/constants.ts | 63 +++++++ packages/@n8n_io/client-oauth2/src/index.ts | 2 + packages/@n8n_io/client-oauth2/src/types.ts | 2 + packages/@n8n_io/client-oauth2/src/utils.ts | 83 +++++++++ .../@n8n_io/client-oauth2/tsconfig.build.json | 12 ++ packages/@n8n_io/client-oauth2/tsconfig.json | 15 ++ packages/cli/package.json | 2 +- .../src/credentials/oauth2Credential.api.ts | 13 +- packages/cli/tsconfig.json | 3 +- packages/core/package.json | 2 +- packages/core/src/NodeExecuteFunctions.ts | 23 ++- packages/core/src/OAuth2Helper.ts | 10 +- packages/core/tsconfig.json | 5 +- pnpm-lock.yaml | 159 +++--------------- 20 files changed, 665 insertions(+), 157 deletions(-) create mode 100644 packages/@n8n_io/client-oauth2/.eslintrc.js create mode 100644 packages/@n8n_io/client-oauth2/package.json create mode 100644 packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts create mode 100644 packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts create mode 100644 packages/@n8n_io/client-oauth2/src/CodeFlow.ts create mode 100644 packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts create mode 100644 packages/@n8n_io/client-oauth2/src/constants.ts create mode 100644 packages/@n8n_io/client-oauth2/src/index.ts create mode 100644 packages/@n8n_io/client-oauth2/src/types.ts create mode 100644 packages/@n8n_io/client-oauth2/src/utils.ts create mode 100644 packages/@n8n_io/client-oauth2/tsconfig.build.json create mode 100644 packages/@n8n_io/client-oauth2/tsconfig.json diff --git a/packages/@n8n_io/client-oauth2/.eslintrc.js b/packages/@n8n_io/client-oauth2/.eslintrc.js new file mode 100644 index 0000000000000..fe7469cf3c7af --- /dev/null +++ b/packages/@n8n_io/client-oauth2/.eslintrc.js @@ -0,0 +1,14 @@ +const { sharedOptions } = require('@n8n_io/eslint-config/shared'); + +/** + * @type {import('@types/eslint').ESLint.ConfigData} + */ +module.exports = { + extends: ['@n8n_io/eslint-config/base'], + + ...sharedOptions(__dirname), + + rules: { + '@typescript-eslint/consistent-type-imports': 'error', + }, +}; diff --git a/packages/@n8n_io/client-oauth2/package.json b/packages/@n8n_io/client-oauth2/package.json new file mode 100644 index 0000000000000..3770129534a0f --- /dev/null +++ b/packages/@n8n_io/client-oauth2/package.json @@ -0,0 +1,25 @@ +{ + "name": "@n8n_io/client-oauth2", + "version": "1.0.4", + "scripts": { + "clean": "rimraf dist .turbo", + "dev": "pnpm watch", + "typecheck": "tsc", + "build": "tsc -p tsconfig.build.json", + "format": "prettier --write . --ignore-path ../../../.prettierignore", + "lint": "eslint --quiet .", + "lintfix": "eslint . --fix", + "watch": "tsc -p tsconfig.build.json --watch", + "test": "jest", + "test:dev": "jest --watch" + }, + "main": "dist/index.js", + "module": "src/index.ts", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "dependencies": { + "axios": "^0.21.1" + } +} diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts b/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts new file mode 100644 index 0000000000000..6b9a1db281483 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts @@ -0,0 +1,116 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/restrict-plus-operands */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as qs from 'querystring'; +import axios from 'axios'; +import { getAuthError } from './utils'; +import type { ClientOAuth2TokenData } from './ClientOAuth2Token'; +import { ClientOAuth2Token } from './ClientOAuth2Token'; +import { CodeFlow } from './CodeFlow'; +import { CredentialsFlow } from './CredentialsFlow'; +import type { Headers, Query } from './types'; + +export interface ClientOAuth2RequestObject { + url: string; + method: 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT'; + body?: Record; + query?: Query; + headers?: Headers; +} + +export interface ClientOAuth2Options { + clientId: string; + clientSecret: string; + accessTokenUri: string; + authorizationUri?: string; + redirectUri?: string; + scopes?: string[]; + authorizationGrants?: string[]; + state?: string; + body?: Record; + query?: Query; + headers?: Headers; +} + +class ResponseError extends Error { + constructor(readonly status: number, readonly body: object, readonly code = 'ESTATUS') { + super(`HTTP status ${status}`); + } +} + +/** + * Construct an object that can handle the multiple OAuth 2.0 flows. + */ +export class ClientOAuth2 { + code: CodeFlow; + + credentials: CredentialsFlow; + + constructor(readonly options: ClientOAuth2Options) { + this.code = new CodeFlow(this); + this.credentials = new CredentialsFlow(this); + } + + /** + * Create a new token from existing data. + */ + createToken( + access: string, + refresh: string, + type?: string, + data?: ClientOAuth2TokenData, + ): ClientOAuth2Token { + return new ClientOAuth2Token(this, { + ...data, + access_token: access, + refresh_token: refresh, + ...(typeof type === 'string' ? { token_type: type } : type), + }); + } + + /** + * Attempt to parse response body as JSON, fall back to parsing as a query string. + */ + private parseResponseBody(body: string): T { + try { + return JSON.parse(body); + } catch (e) { + return qs.parse(body) as T; + } + } + + /** + * Using the built-in request method, we'll automatically attempt to parse + * the response. + */ + async request(options: ClientOAuth2RequestObject): Promise { + let url = options.url; + const query = qs.stringify(options.query); + + if (query) { + url += (url.indexOf('?') === -1 ? '?' : '&') + query; + } + + const response = await axios.request({ + url, + method: options.method, + data: qs.stringify(options.body), + headers: options.headers, + transformResponse: (res) => res, + }); + + const body = this.parseResponseBody(response.data); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const authErr = getAuthError(body); + if (authErr) throw authErr; + + if (response.status < 200 || response.status >= 399) + throw new ResponseError(response.status, response.data); + + return body; + } +} diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts new file mode 100644 index 0000000000000..d2a26b134c336 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts @@ -0,0 +1,102 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { ClientOAuth2, ClientOAuth2Options, ClientOAuth2RequestObject } from './ClientOAuth2'; +import { auth, requestOptions } from './utils'; +import { DEFAULT_HEADERS } from './constants'; + +export interface ClientOAuth2TokenData extends Record { + token_type?: string | undefined; + access_token: string; + refresh_token: string; + expires_in?: string; + scope?: string | undefined; +} +/** + * General purpose client token generator. + */ +export class ClientOAuth2Token { + readonly tokenType?: string; + + readonly accessToken: string; + + readonly refreshToken: string; + + private expires: Date; + + constructor(readonly client: ClientOAuth2, readonly data: ClientOAuth2TokenData) { + this.tokenType = data.token_type?.toLowerCase(); + this.accessToken = data.access_token; + this.refreshToken = data.refresh_token; + + this.expires = new Date(); + this.expires.setSeconds(this.expires.getSeconds() + Number(data.expires_in)); + } + + /** + * Sign a standardized request object with user authentication information. + */ + sign(requestObject: ClientOAuth2RequestObject): ClientOAuth2RequestObject { + if (!this.accessToken) { + throw new Error('Unable to sign without access token'); + } + + requestObject.headers = requestObject.headers ?? {}; + + if (this.tokenType === 'bearer') { + requestObject.headers.Authorization = 'Bearer ' + this.accessToken; + } else { + const parts = requestObject.url.split('#'); + const token = 'access_token=' + this.accessToken; + const url = parts[0].replace(/[?&]access_token=[^&#]/, ''); + const fragment = parts[1] ? '#' + parts[1] : ''; + + // Prepend the correct query string parameter to the url. + requestObject.url = url + (url.indexOf('?') > -1 ? '&' : '?') + token + fragment; + + // Attempt to avoid storing the url in proxies, since the access token + // is exposed in the query parameters. + requestObject.headers.Pragma = 'no-store'; + requestObject.headers['Cache-Control'] = 'no-store'; + } + + return requestObject; + } + + /** + * Refresh a user access token with the supplied token. + */ + async refresh(opts?: ClientOAuth2Options): Promise { + const { clientId, clientSecret, accessTokenUri } = { ...this.client.options, ...opts }; + + if (!this.refreshToken) throw new Error('No refresh token'); + + const config = requestOptions( + { + url: accessTokenUri, + method: 'POST', + headers: { + ...DEFAULT_HEADERS, + Authorization: auth(clientId, clientSecret), + }, + body: { + refresh_token: this.refreshToken, + grant_type: 'refresh_token', + }, + }, + {}, + ); + + const data = await this.client.request(config); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return this.client.createToken({ ...this.data, ...data }); + } + + /** + * Check whether the token has expired. + */ + expired(): boolean { + return Date.now() > this.expires.getTime(); + } +} diff --git a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts new file mode 100644 index 0000000000000..2509947a16e7d --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts @@ -0,0 +1,117 @@ +import * as qs from 'querystring'; +import type { ClientOAuth2, ClientOAuth2Options } from './ClientOAuth2'; +import type { ClientOAuth2Token } from './ClientOAuth2Token'; +import { DEFAULT_HEADERS, DEFAULT_URL_BASE } from './constants'; +import { auth, expects, getAuthError, requestOptions, sanitizeScope } 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?: ClientOAuth2Options): string { + const options = { ...this.client.options, ...opts }; + + // Check the required parameters are set. + expects(options, 'clientId', 'authorizationUri'); + + const query: Record = { + client_id: options.clientId, + redirect_uri: options.redirectUri, + response_type: 'code', + state: options.state, + }; + if (options.scopes !== undefined) { + query.scope = sanitizeScope(options.scopes); + } + + const sep = options.authorizationUri!.includes('?') ? '&' : '?'; + return options.authorizationUri! + sep + qs.stringify({ ...query, ...options.query }); + } + + /** + * Get the code token from the redirected uri and make another request for + * the user access token. + */ + async getToken(uri?: string | URL, opts?: ClientOAuth2Options): Promise { + 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 responseData = await this.client.request( + requestOptions( + { + url: options.accessTokenUri, + method: 'POST', + headers, + body, + }, + options, + ), + ); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return this.client.createToken(responseData); + } +} diff --git a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts new file mode 100644 index 0000000000000..2dc4b1a06f89b --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts @@ -0,0 +1,54 @@ +import type { ClientOAuth2, ClientOAuth2Options } from './ClientOAuth2'; +import type { ClientOAuth2Token } from './ClientOAuth2Token'; +import { DEFAULT_HEADERS } from './constants'; +import { auth, expects, requestOptions, sanitizeScope } from './utils'; + +interface CredentialsFlowBody { + grant_type: 'client_credentials'; + scope?: string; +} + +/** + * Support client credentials OAuth 2.0 grant. + * + * Reference: http://tools.ietf.org/html/rfc6749#section-4.4 + */ +export class CredentialsFlow { + constructor(private client: ClientOAuth2) {} + + /** + * Request an access token using the client credentials. + */ + async getToken(opts?: ClientOAuth2Options): Promise { + const options = { ...this.client.options, ...opts }; + + expects(options, 'clientId', 'clientSecret', 'accessTokenUri'); + + const body: CredentialsFlowBody = { + grant_type: 'client_credentials', + }; + + if (options.scopes !== undefined) { + body.scope = sanitizeScope(options.scopes); + } + + const data = await this.client.request( + requestOptions( + { + url: options.accessTokenUri, + method: 'POST', + headers: { + ...DEFAULT_HEADERS, + // eslint-disable-next-line @typescript-eslint/naming-convention + Authorization: auth(options.clientId, options.clientSecret), + }, + body, + }, + options, + ), + ); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return this.client.createToken(data); + } +} diff --git a/packages/@n8n_io/client-oauth2/src/constants.ts b/packages/@n8n_io/client-oauth2/src/constants.ts new file mode 100644 index 0000000000000..e4895aa470a15 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/constants.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { Headers } from './types'; + +export const DEFAULT_URL_BASE = 'https://example.org/'; + +/** + * Default headers for executing OAuth 2.0 flows. + */ +export const DEFAULT_HEADERS: Headers = { + Accept: 'application/json, application/x-www-form-urlencoded', + 'Content-Type': 'application/x-www-form-urlencoded', +}; + +/** + * Format error response types to regular strings for displaying to clients. + * + * Reference: http://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ +export const ERROR_RESPONSES: Record = { + invalid_request: [ + 'The request is missing a required parameter, includes an', + 'invalid parameter value, includes a parameter more than', + 'once, or is otherwise malformed.', + ].join(' '), + invalid_client: [ + 'Client authentication failed (e.g., unknown client, no', + 'client authentication included, or unsupported', + 'authentication method).', + ].join(' '), + invalid_grant: [ + 'The provided authorization grant (e.g., authorization', + 'code, resource owner credentials) or refresh token is', + 'invalid, expired, revoked, does not match the redirection', + 'URI used in the authorization request, or was issued to', + 'another client.', + ].join(' '), + unauthorized_client: [ + 'The client is not authorized to request an authorization', + 'code using this method.', + ].join(' '), + unsupported_grant_type: [ + 'The authorization grant type is not supported by the', + 'authorization server.', + ].join(' '), + access_denied: ['The resource owner or authorization server denied the request.'].join(' '), + unsupported_response_type: [ + 'The authorization server does not support obtaining', + 'an authorization code using this method.', + ].join(' '), + invalid_scope: ['The requested scope is invalid, unknown, or malformed.'].join(' '), + server_error: [ + 'The authorization server encountered an unexpected', + 'condition that prevented it from fulfilling the request.', + '(This error code is needed because a 500 Internal Server', + 'Error HTTP status code cannot be returned to the client', + 'via an HTTP redirect.)', + ].join(' '), + temporarily_unavailable: [ + 'The authorization server is currently unable to handle', + 'the request due to a temporary overloading or maintenance', + 'of the server.', + ].join(' '), +}; diff --git a/packages/@n8n_io/client-oauth2/src/index.ts b/packages/@n8n_io/client-oauth2/src/index.ts new file mode 100644 index 0000000000000..376c10f1eec51 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/index.ts @@ -0,0 +1,2 @@ +export { ClientOAuth2, ClientOAuth2Options, ClientOAuth2RequestObject } from './ClientOAuth2'; +export { ClientOAuth2Token, ClientOAuth2TokenData } from './ClientOAuth2Token'; diff --git a/packages/@n8n_io/client-oauth2/src/types.ts b/packages/@n8n_io/client-oauth2/src/types.ts new file mode 100644 index 0000000000000..906efcc43e0fd --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/types.ts @@ -0,0 +1,2 @@ +export type Headers = Record; +export type Query = Record; diff --git a/packages/@n8n_io/client-oauth2/src/utils.ts b/packages/@n8n_io/client-oauth2/src/utils.ts new file mode 100644 index 0000000000000..f1e51dc1c3578 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/src/utils.ts @@ -0,0 +1,83 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/restrict-plus-operands */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { ClientOAuth2RequestObject } from './ClientOAuth2'; +import { ERROR_RESPONSES } from './constants'; + +/** + * Check if properties exist on an object and throw when they aren't. + */ +export function expects(obj: any, ...args: any[]) { + for (let i = 1; i < args.length; i++) { + const prop = args[i]; + if (obj[prop] === null) { + throw new TypeError('Expected "' + prop + '" to exist'); + } + } +} + +class AuthError extends Error { + constructor(message: string, readonly body: any, readonly code = 'EAUTH') { + super(message); + } +} + +/** + * Pull an authentication error from the response data. + */ +export function getAuthError(body: { + error: string; + error_description?: string; +}): Error | undefined { + const message: string | undefined = + ERROR_RESPONSES[body.error] ?? body.error_description ?? body.error; + + if (message) { + return new AuthError(message, body); + } + + return undefined; +} + +/** + * Ensure a value is a string. + */ +function toString(str: string | null | undefined) { + return str === null ? '' : String(str); +} + +/** + * Sanitize the scopes option to be a string. + */ +export function sanitizeScope(scopes: string[] | string): string { + return Array.isArray(scopes) ? scopes.join(' ') : toString(scopes); +} + +/** + * Create basic auth header. + */ +export function auth(username: string, password: string): string { + return 'Basic ' + Buffer.from(toString(username) + ':' + toString(password)).toString('base64'); +} + +/** + * Merge request options from an options object. + */ +export function requestOptions( + { url, method, body, query, headers }: ClientOAuth2RequestObject, + options: any, +): ClientOAuth2RequestObject { + const rOptions = { + url, + method, + body: { ...body, ...options.body }, + query: { ...query, ...options.query }, + headers: { ...headers, ...options.headers }, + }; + // if request authorization was overridden delete it from header + if (rOptions.headers.Authorization === '') { + delete rOptions.headers.Authorization; + } + return rOptions; +} diff --git a/packages/@n8n_io/client-oauth2/tsconfig.build.json b/packages/@n8n_io/client-oauth2/tsconfig.build.json new file mode 100644 index 0000000000000..c8f44354c7ac4 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "types": ["node"], + "noEmit": false, + "tsBuildInfoFile": "dist/build.tsbuildinfo" + }, + "include": ["src/**/*.ts"], + "exclude": ["test/**"] +} diff --git a/packages/@n8n_io/client-oauth2/tsconfig.json b/packages/@n8n_io/client-oauth2/tsconfig.json new file mode 100644 index 0000000000000..a693815582818 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "types": ["node", "jest"], + "composite": true, + "noEmit": true, + "baseUrl": "src", + "paths": { + "@/*": ["./*"] + }, + "tsBuildInfoFile": "dist/typecheck.tsbuildinfo" + }, + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 39112605e2851..0e92b1cc24cbe 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -117,6 +117,7 @@ }, "dependencies": { "@n8n_io/license-sdk": "~2.3.0", + "@n8n_io/client-oauth2": "workspace:*", "@oclif/command": "^1.8.16", "@oclif/core": "^1.16.4", "@oclif/errors": "^1.3.6", @@ -133,7 +134,6 @@ "change-case": "^4.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", - "client-oauth2": "^4.2.5", "compression": "^1.7.4", "connect-history-api-fallback": "^1.6.0", "convict": "^6.2.4", diff --git a/packages/cli/src/credentials/oauth2Credential.api.ts b/packages/cli/src/credentials/oauth2Credential.api.ts index a8fd84cef7967..a6e475048fb53 100644 --- a/packages/cli/src/credentials/oauth2Credential.api.ts +++ b/packages/cli/src/credentials/oauth2Credential.api.ts @@ -1,4 +1,5 @@ -import ClientOAuth2 from 'client-oauth2'; +import type { ClientOAuth2Options } from '@n8n_io/client-oauth2'; +import { ClientOAuth2 } from '@n8n_io/client-oauth2'; import Csrf from 'csrf'; import express from 'express'; import get from 'lodash.get'; @@ -119,7 +120,7 @@ oauth2CredentialController.get( }; const stateEncodedStr = Buffer.from(JSON.stringify(state)).toString('base64'); - const oAuthOptions: ClientOAuth2.Options = { + const oAuthOptions: ClientOAuth2Options = { clientId: get(oauthCredentials, 'clientId') as string, clientSecret: get(oauthCredentials, 'clientSecret', '') as string, accessTokenUri: get(oauthCredentials, 'accessTokenUrl', '') as string, @@ -251,11 +252,11 @@ oauth2CredentialController.get( return renderCallbackError(res, errorMessage); } - let options = {}; + let options: Partial = {}; - const oAuth2Parameters = { + const oAuth2Parameters: ClientOAuth2Options = { clientId: get(oauthCredentials, 'clientId') as string, - clientSecret: get(oauthCredentials, 'clientSecret', '') as string | undefined, + clientSecret: get(oauthCredentials, 'clientSecret', '') as string, accessTokenUri: get(oauthCredentials, 'accessTokenUrl', '') as string, authorizationUri: get(oauthCredentials, 'authUrl', '') as string, redirectUri: `${getInstanceBaseUrl()}/${restEndpoint}/oauth2-credential/callback`, @@ -269,6 +270,7 @@ oauth2CredentialController.get( client_secret: get(oauthCredentials, 'clientSecret', '') as string, }, }; + // @ts-ignore delete oAuth2Parameters.clientSecret; } @@ -280,6 +282,7 @@ oauth2CredentialController.get( const oauthToken = await oAuthObj.code.getToken( `${oAuth2Parameters.redirectUri}?${queryParameters}`, + // @ts-ignore options, ); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 5326caf458dca..7b52926d17cb8 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -21,6 +21,7 @@ "include": ["src/**/*.ts", "test/**/*.ts", "src/sso/saml/saml-schema-metadata-2.0.xsd"], "references": [ { "path": "../workflow/tsconfig.build.json" }, - { "path": "../core/tsconfig.build.json" } + { "path": "../core/tsconfig.build.json" }, + { "path": "../@n8n_io/client-oauth2/tsconfig.build.json" } ] } diff --git a/packages/core/package.json b/packages/core/package.json index 699e12149c62b..f95da7364ea95 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -46,7 +46,7 @@ }, "dependencies": { "axios": "^0.21.1", - "client-oauth2": "^4.2.5", + "@n8n_io/client-oauth2": "workspace:*", "concat-stream": "^2.0.0", "cron": "~1.7.2", "crypto-js": "~4.1.1", diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index a7efac11586a2..3ce3187ae911b 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -82,7 +82,12 @@ import { IncomingMessage } from 'http'; import { stringify } from 'qs'; import type { Token } from 'oauth-1.0a'; import clientOAuth1 from 'oauth-1.0a'; -import clientOAuth2 from 'client-oauth2'; +import type { + ClientOAuth2Options, + ClientOAuth2RequestObject, + ClientOAuth2TokenData, +} from '@n8n_io/client-oauth2'; +import { ClientOAuth2 } from '@n8n_io/client-oauth2'; import crypto, { createHmac } from 'crypto'; import get from 'lodash.get'; import type { Request, Response } from 'express'; @@ -1081,14 +1086,14 @@ export async function requestOAuth2( throw new Error('OAuth credentials not connected!'); } - const oAuthClient = new clientOAuth2({ + const oAuthClient = new ClientOAuth2({ clientId: credentials.clientId as string, clientSecret: credentials.clientSecret as string, accessTokenUri: credentials.accessTokenUrl as string, scopes: (credentials.scope as string).split(' '), }); - let oauthTokenData = credentials.oauthTokenData as clientOAuth2.Data; + let oauthTokenData = credentials.oauthTokenData as ClientOAuth2TokenData; // if it's the first time using the credentials, get the access token and save it into the DB. if ( @@ -1117,6 +1122,8 @@ export async function requestOAuth2( } const token = oAuthClient.createToken( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore get(oauthTokenData, oAuth2Options?.property as string) || oauthTokenData.accessToken, oauthTokenData.refreshToken, oAuth2Options?.tokenType || oauthTokenData.tokenType, @@ -1124,7 +1131,7 @@ export async function requestOAuth2( ); // Signs the request by adding authorization headers or query parameters depending // on the token-type used. - const newRequestOptions = token.sign(requestOptions as clientOAuth2.RequestObject); + const newRequestOptions = token.sign(requestOptions as ClientOAuth2RequestObject); const newRequestHeaders = (newRequestOptions.headers = newRequestOptions.headers ?? {}); // If keep bearer is false remove the it from the authorization header if (oAuth2Options?.keepBearer === false && typeof newRequestHeaders.Authorization === 'string') { @@ -1164,7 +1171,7 @@ export async function requestOAuth2( if (OAuth2GrantType.clientCredentials === credentials.grantType) { newToken = await getClientCredentialsToken(token.client, credentials); } else { - newToken = await token.refresh(tokenRefreshOptions); + newToken = await token.refresh(tokenRefreshOptions as unknown as ClientOAuth2Options); } Logger.debug( @@ -1184,7 +1191,7 @@ export async function requestOAuth2( credentialsType, credentials, ); - const refreshedRequestOption = newToken.sign(requestOptions as clientOAuth2.RequestObject); + const refreshedRequestOption = newToken.sign(requestOptions as ClientOAuth2RequestObject); if (oAuth2Options?.keyToIncludeInAccessTokenHeader) { Object.assign(newRequestHeaders, { @@ -1243,7 +1250,7 @@ export async function requestOAuth2( if (OAuth2GrantType.clientCredentials === credentials.grantType) { newToken = await getClientCredentialsToken(token.client, credentials); } else { - newToken = await token.refresh(tokenRefreshOptions); + newToken = await token.refresh(tokenRefreshOptions as unknown as ClientOAuth2Options); } Logger.debug( `OAuth2 token for "${credentialsType}" used by node "${node.name}" has been renewed.`, @@ -1271,7 +1278,7 @@ export async function requestOAuth2( ); // Make the request again with the new token - const newRequestOptions = newToken.sign(requestOptions as clientOAuth2.RequestObject); + const newRequestOptions = newToken.sign(requestOptions as ClientOAuth2RequestObject); newRequestOptions.headers = newRequestOptions.headers ?? {}; if (oAuth2Options?.keyToIncludeInAccessTokenHeader) { diff --git a/packages/core/src/OAuth2Helper.ts b/packages/core/src/OAuth2Helper.ts index a66763505c501..b41349b26dc63 100644 --- a/packages/core/src/OAuth2Helper.ts +++ b/packages/core/src/OAuth2Helper.ts @@ -1,15 +1,15 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import type { ICredentialDataDecryptedObject } from 'n8n-workflow'; -import type clientOAuth2 from 'client-oauth2'; +import type { ClientOAuth2, ClientOAuth2Options, ClientOAuth2Token } from '@n8n_io/client-oauth2'; export const getClientCredentialsToken = async ( - oAuth2Client: clientOAuth2, + oAuth2Client: ClientOAuth2, credentials: ICredentialDataDecryptedObject, -): Promise => { +): Promise => { const options = {}; if (credentials.authentication === 'body') { Object.assign(options, { headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention Authorization: '', }, body: { @@ -18,5 +18,5 @@ export const getClientCredentialsToken = async ( }, }); } - return oAuth2Client.credentials.getToken(options); + return oAuth2Client.credentials.getToken(options as ClientOAuth2Options); }; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 69536da44842a..55f880dfcdf1e 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -14,5 +14,8 @@ "useUnknownInCatchVariables": false }, "include": ["src/**/*.ts", "test/**/*.ts"], - "references": [{ "path": "../workflow/tsconfig.build.json" }] + "references": [ + { "path": "../workflow/tsconfig.build.json" }, + { "path": "../@n8n_io/client-oauth2/tsconfig.build.json" } + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76a7095196788..a6c85a4e5693c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,12 @@ importers: specifier: ^5.0.3 version: 5.0.3 + packages/@n8n_io/client-oauth2: + dependencies: + axios: + specifier: ^0.21.1 + version: 0.21.4(debug@4.3.2) + packages/@n8n_io/eslint-config: devDependencies: '@types/eslint': @@ -167,6 +173,9 @@ importers: packages/cli: dependencies: + '@n8n_io/client-oauth2': + specifier: workspace:* + version: link:../@n8n_io/client-oauth2 '@n8n_io/license-sdk': specifier: ~2.3.0 version: 2.3.0 @@ -218,9 +227,6 @@ importers: class-validator: specifier: ^0.14.0 version: 0.14.0 - client-oauth2: - specifier: ^4.2.5 - version: 4.3.3 compression: specifier: ^1.7.4 version: 1.7.4 @@ -606,12 +612,12 @@ importers: packages/core: dependencies: + '@n8n_io/client-oauth2': + specifier: workspace:* + version: link:../@n8n_io/client-oauth2 axios: specifier: ^0.21.1 version: 0.21.4(debug@4.3.2) - client-oauth2: - specifier: ^4.2.5 - version: 4.3.3 concat-stream: specifier: ^2.0.0 version: 2.0.0 @@ -6054,10 +6060,6 @@ packages: - supports-color dev: true - /@servie/events@1.0.0: - resolution: {integrity: sha512-sBSO19KzdrJCM3gdx6eIxV8M9Gxfgg6iDQmH5TIAGaUu+X9VDdsINXJOnoiZ1Kx3TrHdH4bt5UVglkjsEGBcvw==} - dev: false - /@sideway/address@4.1.4: resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} dependencies: @@ -8255,10 +8257,6 @@ packages: resolution: {integrity: sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==} dev: true - /@types/tough-cookie@2.3.8: - resolution: {integrity: sha512-7axfYN8SW9pWg78NgenHasSproWQee5rzyPVLC9HpaQSDgNArsnKJD88EaMfi4Pl48AyciO3agYCFqpHS1gLpg==} - dev: false - /@types/tough-cookie@4.0.2: resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} dev: true @@ -10240,10 +10238,6 @@ packages: streamsearch: 1.1.0 dev: false - /byte-length@1.0.2: - resolution: {integrity: sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==} - dev: false - /bytes@3.0.0: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} engines: {node: '>= 0.8'} @@ -10694,14 +10688,6 @@ packages: glob: 7.2.3 dev: true - /client-oauth2@4.3.3: - resolution: {integrity: sha512-k8AvUYJon0vv75ufoVo4nALYb/qwFFicO3I0+39C6xEdflqVtr+f9cy+0ZxAduoVSTfhP5DX2tY2XICAd5hy6Q==} - engines: {node: '>=4.2.0'} - dependencies: - popsicle: 12.1.0 - safe-buffer: 5.2.1 - dev: false - /cliui@3.2.0: resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==} dependencies: @@ -11652,6 +11638,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 5.5.0 + dev: true /debug@3.2.7(supports-color@8.1.1): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -11663,7 +11650,6 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 - dev: true /debug@4.3.2: resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} @@ -12574,7 +12560,7 @@ packages: /eslint-import-resolver-node@0.3.7: resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.11.0 resolve: 1.22.1 transitivePeerDependencies: @@ -12627,7 +12613,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.59.0(eslint@8.39.0)(typescript@5.0.3) - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) eslint: 8.39.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.0)(eslint-plugin-import@2.27.5)(eslint@8.39.0) @@ -12658,7 +12644,7 @@ packages: array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.39.0 eslint-import-resolver-node: 0.3.7 @@ -13541,7 +13527,7 @@ packages: debug: optional: true dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) dev: false /follow-redirects@1.15.2(debug@4.3.2): @@ -14132,7 +14118,7 @@ packages: array-parallel: 0.1.3 array-series: 0.1.5 cross-spawn: 4.0.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: false @@ -14248,6 +14234,7 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -14769,11 +14756,6 @@ packages: - supports-color dev: false - /ip-regex@2.1.0: - resolution: {integrity: sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==} - engines: {node: '>=4'} - dev: false - /ip@1.1.8: resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} dev: false @@ -17021,14 +17003,9 @@ packages: dependencies: semver: 6.3.0 - /make-error-cause@2.3.0: - resolution: {integrity: sha512-etgt+n4LlOkGSJbBTV9VROHA5R7ekIPS4vfh+bCAoJgRrJWdqJCBbpS3osRJ/HrT7R68MzMiY3L3sDJ/Fd8aBg==} - dependencies: - make-error: 1.3.6 - dev: false - /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true /make-fetch-happen@9.1.0: resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} @@ -18641,7 +18618,7 @@ packages: resolution: {integrity: sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==} engines: {node: '>=6.8.1'} dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) node-ensure: 0.0.0 transitivePeerDependencies: - supports-color @@ -18847,70 +18824,6 @@ packages: '@babel/runtime': 7.20.7 dev: true - /popsicle-content-encoding@1.0.0(servie@4.3.3): - resolution: {integrity: sha512-4Df+vTfM8wCCJVTzPujiI6eOl3SiWQkcZg0AMrOkD1enMXsF3glIkFUZGvour1Sj7jOWCsNSEhBxpbbhclHhzw==} - peerDependencies: - servie: ^4.0.0 - dependencies: - servie: 4.3.3 - dev: false - - /popsicle-cookie-jar@1.0.0(servie@4.3.3): - resolution: {integrity: sha512-vrlOGvNVELko0+J8NpGC5lHWDGrk8LQJq9nwAMIVEVBfN1Lib3BLxAaLRGDTuUnvl45j5N9dT2H85PULz6IjjQ==} - peerDependencies: - servie: ^4.0.0 - dependencies: - '@types/tough-cookie': 2.3.8 - servie: 4.3.3 - tough-cookie: 3.0.1 - dev: false - - /popsicle-redirects@1.1.1(servie@4.3.3): - resolution: {integrity: sha512-mC2HrKjdTAWDalOjGxlXw9j6Qxrz/Yd2ui6bPxpi2IQDYWpF4gUAMxbA8EpSWJhLi0PuWKDwTHHPrUPGutAoIA==} - peerDependencies: - servie: ^4.1.0 - dependencies: - servie: 4.3.3 - dev: false - - /popsicle-transport-http@1.2.1(servie@4.3.3): - resolution: {integrity: sha512-i5r3IGHkGiBDm1oPFvOfEeSGWR0lQJcsdTqwvvDjXqcTHYJJi4iSi3ecXIttDiTBoBtRAFAE9nF91fspQr63FQ==} - peerDependencies: - servie: ^4.2.0 - dependencies: - make-error-cause: 2.3.0 - servie: 4.3.3 - dev: false - - /popsicle-transport-xhr@2.0.0(servie@4.3.3): - resolution: {integrity: sha512-5Sbud4Widngf1dodJE5cjEYXkzEUIl8CzyYRYR57t6vpy9a9KPGQX6KBKdPjmBZlR5A06pOBXuJnVr23l27rtA==} - peerDependencies: - servie: ^4.2.0 - dependencies: - servie: 4.3.3 - dev: false - - /popsicle-user-agent@1.0.0(servie@4.3.3): - resolution: {integrity: sha512-epKaq3TTfTzXcxBxjpoKYMcTTcAX8Rykus6QZu77XNhJuRHSRxMd+JJrbX/3PFI0opFGSN0BabbAYCbGxbu0mA==} - peerDependencies: - servie: ^4.0.0 - dependencies: - servie: 4.3.3 - dev: false - - /popsicle@12.1.0: - resolution: {integrity: sha512-muNC/cIrWhfR6HqqhHazkxjob3eyECBe8uZYSQ/N5vixNAgssacVleerXnE8Are5fspR0a+d2qWaBR1g7RYlmw==} - dependencies: - popsicle-content-encoding: 1.0.0(servie@4.3.3) - popsicle-cookie-jar: 1.0.0(servie@4.3.3) - popsicle-redirects: 1.1.1(servie@4.3.3) - popsicle-transport-http: 1.2.1(servie@4.3.3) - popsicle-transport-xhr: 2.0.0(servie@4.3.3) - popsicle-user-agent: 1.0.0(servie@4.3.3) - servie: 4.3.3 - throwback: 4.1.0 - dev: false - /posix-character-classes@0.1.1: resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} engines: {node: '>=0.10.0'} @@ -20127,7 +20040,7 @@ packages: /rhea@1.0.24: resolution: {integrity: sha512-PEl62U2EhxCO5wMUZ2/bCBcXAVKN9AdMSNQOrp3+R5b77TEaOSiy16MQ0sIOmzj/iqsgIAgPs1mt3FYfu1vIXA==} dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: false @@ -20520,14 +20433,6 @@ packages: transitivePeerDependencies: - supports-color - /servie@4.3.3: - resolution: {integrity: sha512-b0IrY3b1gVMsWvJppCf19g1p3JSnS0hQi6xu4Hi40CIhf0Lx8pQHcvBL+xunShpmOiQzg1NOia812NAWdSaShw==} - dependencies: - '@servie/events': 1.0.0 - byte-length: 1.0.2 - ts-expect: 1.3.0 - dev: false - /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -20747,7 +20652,7 @@ packages: bignumber.js: 2.4.0 binascii: 0.0.2 browser-request: 0.3.3 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) expand-tilde: 2.0.2 extend: 3.0.2 generic-pool: 3.9.0 @@ -21399,6 +21304,7 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 + dev: true /supports-color@6.1.0: resolution: {integrity: sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==} @@ -21684,10 +21590,6 @@ packages: /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - /throwback@4.1.0: - resolution: {integrity: sha512-dLFe8bU8SeH0xeqeKL7BNo8XoPC/o91nz9/ooeplZPiso+DZukhoyZcSz9TFnUNScm+cA9qjU1m1853M6sPOng==} - dev: false - /time-stamp@1.1.0: resolution: {integrity: sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==} engines: {node: '>=0.10.0'} @@ -21849,15 +21751,6 @@ packages: psl: 1.9.0 punycode: 2.2.0 - /tough-cookie@3.0.1: - resolution: {integrity: sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==} - engines: {node: '>=6'} - dependencies: - ip-regex: 2.1.0 - psl: 1.9.0 - punycode: 2.2.0 - dev: false - /tough-cookie@4.1.2: resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==} engines: {node: '>=6'} @@ -21910,10 +21803,6 @@ packages: typescript: 5.0.3 dev: true - /ts-expect@1.3.0: - resolution: {integrity: sha512-e4g0EJtAjk64xgnFPD6kTBUtpnMVzDrMb12N1YZV0VvSlhnVT3SGxiYTLdGy8Q5cYHOIC/FAHmZ10eGrAguicQ==} - dev: false - /ts-jest@29.1.0(@babel/core@7.21.8)(jest@29.5.0)(typescript@5.0.3): resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} From 8ab2e4c5da7500d90ee5873aa43acb9d4e1de580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 21 Apr 2023 15:07:40 +0200 Subject: [PATCH 02/11] fix some issues --- packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts | 9 +-------- .../@n8n_io/client-oauth2/src/ClientOAuth2Token.ts | 6 ++---- packages/@n8n_io/client-oauth2/src/CodeFlow.ts | 7 +++---- .../@n8n_io/client-oauth2/src/CredentialsFlow.ts | 8 +++----- packages/core/src/NodeExecuteFunctions.ts | 13 ++++++++----- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts b/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts index 6b9a1db281483..8900666a84159 100644 --- a/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts +++ b/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts @@ -56,16 +56,9 @@ export class ClientOAuth2 { /** * Create a new token from existing data. */ - createToken( - access: string, - refresh: string, - type?: string, - data?: ClientOAuth2TokenData, - ): ClientOAuth2Token { + createToken(data: ClientOAuth2TokenData, type?: string): ClientOAuth2Token { return new ClientOAuth2Token(this, { ...data, - access_token: access, - refresh_token: refresh, ...(typeof type === 'string' ? { token_type: type } : type), }); } diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts index d2a26b134c336..9f39bd1ef0f81 100644 --- a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts +++ b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts @@ -87,10 +87,8 @@ export class ClientOAuth2Token { {}, ); - const data = await this.client.request(config); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.client.createToken({ ...this.data, ...data }); + const responseData = await this.client.request(config); + return this.client.createToken({ ...this.data, ...responseData }); } /** diff --git a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts index 2509947a16e7d..c0f09f03dfb19 100644 --- a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts +++ b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts @@ -1,6 +1,6 @@ import * as qs from 'querystring'; import type { ClientOAuth2, ClientOAuth2Options } from './ClientOAuth2'; -import type { ClientOAuth2Token } from './ClientOAuth2Token'; +import type { ClientOAuth2Token, ClientOAuth2TokenData } from './ClientOAuth2Token'; import { DEFAULT_HEADERS, DEFAULT_URL_BASE } from './constants'; import { auth, expects, getAuthError, requestOptions, sanitizeScope } from './utils'; @@ -99,7 +99,7 @@ export class CodeFlow { body.client_id = options.clientId; } - const responseData = await this.client.request( + const responseData = await this.client.request( requestOptions( { url: options.accessTokenUri, @@ -110,8 +110,7 @@ export class CodeFlow { options, ), ); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + return this.client.createToken(responseData); } } diff --git a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts index 2dc4b1a06f89b..cd2ae70991172 100644 --- a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts +++ b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts @@ -1,5 +1,5 @@ import type { ClientOAuth2, ClientOAuth2Options } from './ClientOAuth2'; -import type { ClientOAuth2Token } from './ClientOAuth2Token'; +import type { ClientOAuth2Token, ClientOAuth2TokenData } from './ClientOAuth2Token'; import { DEFAULT_HEADERS } from './constants'; import { auth, expects, requestOptions, sanitizeScope } from './utils'; @@ -32,7 +32,7 @@ export class CredentialsFlow { body.scope = sanitizeScope(options.scopes); } - const data = await this.client.request( + const responseData = await this.client.request( requestOptions( { url: options.accessTokenUri, @@ -47,8 +47,6 @@ export class CredentialsFlow { options, ), ); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.client.createToken(data); + return this.client.createToken(responseData); } } diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 3ce3187ae911b..17eeda0e0e63e 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -1121,13 +1121,16 @@ export async function requestOAuth2( oauthTokenData = data; } + const accessToken = + get(oauthTokenData, oAuth2Options?.property as string) || oauthTokenData.accessToken; + const refreshToken = oauthTokenData.refreshToken; const token = oAuthClient.createToken( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - get(oauthTokenData, oAuth2Options?.property as string) || oauthTokenData.accessToken, - oauthTokenData.refreshToken, + { + ...oauthTokenData, + ...(accessToken ? { access_token: accessToken } : {}), + ...(refreshToken ? { refresh_token: refreshToken } : {}), + }, oAuth2Options?.tokenType || oauthTokenData.tokenType, - oauthTokenData, ); // Signs the request by adding authorization headers or query parameters depending // on the token-type used. From 3ffc3ae8a2e2b3084108e0213e70e78b01c4c475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 21 Apr 2023 16:07:21 +0200 Subject: [PATCH 03/11] delete some duplicated code --- packages/core/src/NodeExecuteFunctions.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 17eeda0e0e63e..92df9e12ae22f 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -1207,6 +1207,11 @@ export async function requestOAuth2( throw error; }); } + const tokenExpiredStatusCode = + oAuth2Options?.tokenExpiredStatusCode === undefined + ? 401 + : oAuth2Options?.tokenExpiredStatusCode; + return this.helpers .request(newRequestOptions) .then((response) => { @@ -1214,21 +1219,14 @@ export async function requestOAuth2( if ( requestOptions.resolveWithFullResponse === true && requestOptions.simple === false && - response.statusCode === - (oAuth2Options?.tokenExpiredStatusCode === undefined - ? 401 - : oAuth2Options?.tokenExpiredStatusCode) + response.statusCode === tokenExpiredStatusCode ) { throw response; } return response; }) .catch(async (error: IResponseError) => { - const statusCodeReturned = - oAuth2Options?.tokenExpiredStatusCode === undefined - ? 401 - : oAuth2Options?.tokenExpiredStatusCode; - if (error.statusCode === statusCodeReturned) { + if (error.statusCode === tokenExpiredStatusCode) { // Token is probably not valid anymore. So try refresh it. const tokenRefreshOptions: IDataObject = {}; if (oAuth2Options?.includeCredentialsOnRefreshOnBody) { From 11ad7ea20817700d5d0d82ffbc16e15b6e282fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 10 May 2023 22:11:57 +0200 Subject: [PATCH 04/11] add tests --- packages/@n8n_io/client-oauth2/jest.config.js | 2 + .../client-oauth2/src/ClientOAuth2Token.ts | 8 +- .../@n8n_io/client-oauth2/src/CodeFlow.ts | 21 +- .../client-oauth2/src/CredentialsFlow.ts | 28 +-- packages/@n8n_io/client-oauth2/src/utils.ts | 2 +- .../client-oauth2/test/CodeFlow.test.ts | 186 ++++++++++++++++++ .../test/CredentialsFlow.test.ts | 116 +++++++++++ packages/@n8n_io/client-oauth2/test/config.ts | 15 ++ 8 files changed, 348 insertions(+), 30 deletions(-) create mode 100644 packages/@n8n_io/client-oauth2/jest.config.js create mode 100644 packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts create mode 100644 packages/@n8n_io/client-oauth2/test/CredentialsFlow.test.ts create mode 100644 packages/@n8n_io/client-oauth2/test/config.ts diff --git a/packages/@n8n_io/client-oauth2/jest.config.js b/packages/@n8n_io/client-oauth2/jest.config.js new file mode 100644 index 0000000000000..d6c48554a79a4 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/jest.config.js @@ -0,0 +1,2 @@ +/** @type {import('jest').Config} */ +module.exports = require('../../../jest.config'); diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts index 9f39bd1ef0f81..05b1353ca49ac 100644 --- a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts +++ b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/naming-convention */ import type { ClientOAuth2, ClientOAuth2Options, ClientOAuth2RequestObject } from './ClientOAuth2'; -import { auth, requestOptions } from './utils'; +import { auth, getRequestOptions } from './utils'; import { DEFAULT_HEADERS } from './constants'; export interface ClientOAuth2TokenData extends Record { @@ -25,7 +25,7 @@ export class ClientOAuth2Token { private expires: Date; constructor(readonly client: ClientOAuth2, readonly data: ClientOAuth2TokenData) { - this.tokenType = data.token_type?.toLowerCase(); + this.tokenType = data.token_type?.toLowerCase() ?? 'bearer'; this.accessToken = data.access_token; this.refreshToken = data.refresh_token; @@ -71,7 +71,7 @@ export class ClientOAuth2Token { if (!this.refreshToken) throw new Error('No refresh token'); - const config = requestOptions( + const requestOptions = getRequestOptions( { url: accessTokenUri, method: 'POST', @@ -87,7 +87,7 @@ export class ClientOAuth2Token { {}, ); - const responseData = await this.client.request(config); + const responseData = await this.client.request(requestOptions); return this.client.createToken({ ...this.data, ...responseData }); } diff --git a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts index c0f09f03dfb19..7db8719de1ef9 100644 --- a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts +++ b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts @@ -2,7 +2,7 @@ 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, requestOptions, sanitizeScope } from './utils'; +import { auth, expects, getAuthError, getRequestOptions, sanitizeScope } from './utils'; interface CodeFlowBody { code: string | string[]; @@ -99,18 +99,17 @@ export class CodeFlow { body.client_id = options.clientId; } - const responseData = await this.client.request( - requestOptions( - { - url: options.accessTokenUri, - method: 'POST', - headers, - body, - }, - options, - ), + const requestOptions = getRequestOptions( + { + url: options.accessTokenUri, + method: 'POST', + headers, + body, + }, + options, ); + const responseData = await this.client.request(requestOptions); return this.client.createToken(responseData); } } diff --git a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts index cd2ae70991172..6106daae32b76 100644 --- a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts +++ b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts @@ -1,7 +1,7 @@ import type { ClientOAuth2, ClientOAuth2Options } from './ClientOAuth2'; import type { ClientOAuth2Token, ClientOAuth2TokenData } from './ClientOAuth2Token'; import { DEFAULT_HEADERS } from './constants'; -import { auth, expects, requestOptions, sanitizeScope } from './utils'; +import { auth, expects, getRequestOptions, sanitizeScope } from './utils'; interface CredentialsFlowBody { grant_type: 'client_credentials'; @@ -32,21 +32,21 @@ export class CredentialsFlow { body.scope = sanitizeScope(options.scopes); } - const responseData = await this.client.request( - requestOptions( - { - url: options.accessTokenUri, - method: 'POST', - headers: { - ...DEFAULT_HEADERS, - // eslint-disable-next-line @typescript-eslint/naming-convention - Authorization: auth(options.clientId, options.clientSecret), - }, - body, + const requestOptions = getRequestOptions( + { + url: options.accessTokenUri, + method: 'POST', + headers: { + ...DEFAULT_HEADERS, + // eslint-disable-next-line @typescript-eslint/naming-convention + Authorization: auth(options.clientId, options.clientSecret), }, - options, - ), + body, + }, + options, ); + + const responseData = await this.client.request(requestOptions); return this.client.createToken(responseData); } } diff --git a/packages/@n8n_io/client-oauth2/src/utils.ts b/packages/@n8n_io/client-oauth2/src/utils.ts index f1e51dc1c3578..406fcb947574c 100644 --- a/packages/@n8n_io/client-oauth2/src/utils.ts +++ b/packages/@n8n_io/client-oauth2/src/utils.ts @@ -64,7 +64,7 @@ export function auth(username: string, password: string): string { /** * Merge request options from an options object. */ -export function requestOptions( +export function getRequestOptions( { url, method, body, query, headers }: ClientOAuth2RequestObject, options: any, ): ClientOAuth2RequestObject { diff --git a/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts b/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts new file mode 100644 index 0000000000000..ff90ef247d0b8 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts @@ -0,0 +1,186 @@ +import nock from 'nock'; +import { ClientOAuth2, ClientOAuth2Token } from '../src'; +import * as config from './config'; + +describe('CodeFlow', () => { + beforeAll(async () => { + nock.disableNetConnect(); + }); + + afterAll(() => { + nock.restore(); + }); + + const uri = `/auth/callback?code=${config.code}&state=${config.state}`; + + const githubAuth = new ClientOAuth2({ + clientId: config.clientId, + clientSecret: config.clientSecret, + accessTokenUri: config.accessTokenUri, + authorizationUri: config.authorizationUri, + authorizationGrants: ['code'], + redirectUri: config.redirectUri, + scopes: ['notifications'], + }); + + describe('#getUri', () => { + it('should return a valid uri', () => { + expect(githubAuth.code.getUri()).toEqual( + `${config.authorizationUri}?client_id=abc&` + + 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + 'response_type=code&state=&scope=notifications', + ); + }); + + describe('when scopes are undefined', () => { + it('should not include scope in the uri', () => { + const authWithoutScopes = new ClientOAuth2({ + clientId: config.clientId, + clientSecret: config.clientSecret, + accessTokenUri: config.accessTokenUri, + authorizationUri: config.authorizationUri, + authorizationGrants: ['code'], + redirectUri: config.redirectUri, + }); + expect(authWithoutScopes.code.getUri()).toEqual( + `${config.authorizationUri}?client_id=abc&` + + 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + 'response_type=code&state=', + ); + }); + }); + + it('should include empty scopes array as an empty string', () => { + const authWithEmptyScopes = new ClientOAuth2({ + clientId: config.clientId, + clientSecret: config.clientSecret, + accessTokenUri: config.accessTokenUri, + authorizationUri: config.authorizationUri, + authorizationGrants: ['code'], + redirectUri: config.redirectUri, + scopes: [], + }); + expect(authWithEmptyScopes.code.getUri()).toEqual( + `${config.authorizationUri}?client_id=abc&` + + 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + 'response_type=code&state=&scope=', + ); + }); + + it('should include empty scopes string as an empty string', () => { + const authWithEmptyScopes = new ClientOAuth2({ + clientId: config.clientId, + clientSecret: config.clientSecret, + accessTokenUri: config.accessTokenUri, + authorizationUri: config.authorizationUri, + authorizationGrants: ['code'], + redirectUri: config.redirectUri, + scopes: [], + }); + expect(authWithEmptyScopes.code.getUri()).toEqual( + `${config.authorizationUri}?client_id=abc&` + + 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + 'response_type=code&state=&scope=', + ); + }); + + describe('when authorizationUri contains query parameters', () => { + it('should preserve query string parameters', () => { + const authWithParams = new ClientOAuth2({ + clientId: config.clientId, + clientSecret: config.clientSecret, + accessTokenUri: config.accessTokenUri, + authorizationUri: `${config.authorizationUri}?bar=qux`, + authorizationGrants: ['code'], + redirectUri: config.redirectUri, + scopes: ['notifications'], + }); + expect(authWithParams.code.getUri()).toEqual( + `${config.authorizationUri}?bar=qux&client_id=abc&` + + 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + 'response_type=code&state=&scope=notifications', + ); + }); + }); + }); + + describe('#getToken', () => { + const mockTokenCall = () => + nock(config.baseUrl) + .post( + '/login/oauth/access_token', + ({ code, grant_type, redirect_uri }) => + code === config.code && + grant_type === 'authorization_code' && + redirect_uri === config.redirectUri, + ) + .once() + .reply(200, { + access_token: config.accessToken, + refresh_token: config.refreshToken, + }); + + it('should request the token', async () => { + mockTokenCall(); + const user = await githubAuth.code.getToken(uri); + + expect(user).toBeInstanceOf(ClientOAuth2Token); + expect(user.accessToken).toEqual(config.accessToken); + expect(user.tokenType).toEqual('bearer'); + }); + + it('should reject with auth errors', async () => { + let errored = false; + + try { + await githubAuth.code.getToken(`${config.redirectUri}?error=invalid_request`); + } catch (err) { + errored = true; + + expect(err.code).toEqual('EAUTH'); + expect(err.body.error).toEqual('invalid_request'); + } + expect(errored).toEqual(true); + }); + + describe('#sign', () => { + it('should be able to sign a standard request object', async () => { + mockTokenCall(); + const token = await githubAuth.code.getToken(uri); + const requestOptions = token.sign({ + method: 'GET', + url: 'http://api.github.com/user', + }); + expect(requestOptions.headers?.Authorization).toEqual(`Bearer ${config.accessToken}`); + }); + }); + + describe('#refresh', () => { + const mockRefreshCall = () => + nock(config.baseUrl) + .post( + '/login/oauth/access_token', + ({ refresh_token, grant_type }) => + refresh_token === config.refreshToken && grant_type === 'refresh_token', + ) + .once() + .reply(200, { + access_token: config.refreshedAccessToken, + refresh_token: config.refreshedRefreshToken, + }); + + it('should make a request to get a new access token', async () => { + mockTokenCall(); + const token = await githubAuth.code.getToken(uri, { state: config.state }); + expect(token.refreshToken).toEqual(config.refreshToken); + + mockRefreshCall(); + const token1 = await token.refresh(); + expect(token1).toBeInstanceOf(ClientOAuth2Token); + expect(token1.accessToken).toEqual(config.refreshedAccessToken); + expect(token1.refreshToken).toEqual(config.refreshedRefreshToken); + expect(token1.tokenType).toEqual('bearer'); + }); + }); + }); +}); diff --git a/packages/@n8n_io/client-oauth2/test/CredentialsFlow.test.ts b/packages/@n8n_io/client-oauth2/test/CredentialsFlow.test.ts new file mode 100644 index 0000000000000..7aee647b251b2 --- /dev/null +++ b/packages/@n8n_io/client-oauth2/test/CredentialsFlow.test.ts @@ -0,0 +1,116 @@ +import nock from 'nock'; +import { ClientOAuth2, ClientOAuth2Token } from '../src'; +import * as config from './config'; + +describe('CredentialsFlow', () => { + beforeAll(async () => { + nock.disableNetConnect(); + }); + + afterAll(() => { + nock.restore(); + }); + + describe('#getToken', () => { + const createAuthClient = (scopes?: string[]) => + new ClientOAuth2({ + clientId: config.clientId, + clientSecret: config.clientSecret, + accessTokenUri: config.accessTokenUri, + authorizationGrants: ['credentials'], + scopes, + }); + + const mockTokenCall = (requestedScope?: string) => + nock(config.baseUrl) + .post( + '/login/oauth/access_token', + ({ scope, grant_type }) => + scope === requestedScope && grant_type === 'client_credentials', + ) + .once() + .reply(200, { + access_token: config.accessToken, + refresh_token: config.refreshToken, + scope: requestedScope, + }); + + it('should request the token', async () => { + const authClient = createAuthClient(['notifications']); + mockTokenCall('notifications'); + + const user = await authClient.credentials.getToken(); + + expect(user).toBeInstanceOf(ClientOAuth2Token); + expect(user.accessToken).toEqual(config.accessToken); + expect(user.tokenType).toEqual('bearer'); + expect(user.data.scope).toEqual('notifications'); + }); + + it('when scopes are undefined, it should not send scopes to an auth server', async () => { + const authClient = createAuthClient(); + mockTokenCall(); + + const user = await authClient.credentials.getToken(); + expect(user).toBeInstanceOf(ClientOAuth2Token); + expect(user.accessToken).toEqual(config.accessToken); + expect(user.tokenType).toEqual('bearer'); + expect(user.data.scope).toEqual(undefined); + }); + + it('when scopes is an empty array, it should send empty scope string to an auth server', async () => { + const authClient = createAuthClient([]); + mockTokenCall(''); + + const user = await authClient.credentials.getToken(); + expect(user).toBeInstanceOf(ClientOAuth2Token); + expect(user.accessToken).toEqual(config.accessToken); + expect(user.tokenType).toEqual('bearer'); + expect(user.data.scope).toEqual(''); + }); + + describe('#sign', () => { + it('should be able to sign a standard request object', async () => { + const authClient = createAuthClient(['notifications']); + mockTokenCall('notifications'); + + const token = await authClient.credentials.getToken(); + const requestOptions = token.sign({ + method: 'GET', + url: `${config.baseUrl}/test`, + }); + + expect(requestOptions.headers?.Authorization).toEqual(`Bearer ${config.accessToken}`); + }); + }); + + describe('#refresh', () => { + const mockRefreshCall = () => + nock(config.baseUrl) + .post( + '/login/oauth/access_token', + ({ refresh_token, grant_type }) => + refresh_token === config.refreshToken && grant_type === 'refresh_token', + ) + .once() + .reply(200, { + access_token: config.refreshedAccessToken, + refresh_token: config.refreshedRefreshToken, + }); + + it('should make a request to get a new access token', async () => { + const authClient = createAuthClient(['notifications']); + mockTokenCall('notifications'); + + const token = await authClient.credentials.getToken(); + expect(token.accessToken).toEqual(config.accessToken); + + mockRefreshCall(); + const token1 = await token.refresh(); + expect(token1).toBeInstanceOf(ClientOAuth2Token); + expect(token1.accessToken).toEqual(config.refreshedAccessToken); + expect(token1.tokenType).toEqual('bearer'); + }); + }); + }); +}); diff --git a/packages/@n8n_io/client-oauth2/test/config.ts b/packages/@n8n_io/client-oauth2/test/config.ts new file mode 100644 index 0000000000000..a6dd28a3ef95d --- /dev/null +++ b/packages/@n8n_io/client-oauth2/test/config.ts @@ -0,0 +1,15 @@ +export const baseUrl = 'https://mock.auth.service'; +export const accessTokenUri = baseUrl + '/login/oauth/access_token'; +export const authorizationUri = baseUrl + '/login/oauth/authorize'; +export const redirectUri = 'http://example.com/auth/callback'; + +export const accessToken = '4430eb1615fb6127cbf828a8e403'; +export const refreshToken = 'def456token'; +export const refreshedAccessToken = 'f456okeendt'; +export const refreshedRefreshToken = 'f4f6577c0f3af456okeendt'; + +export const clientId = 'abc'; +export const clientSecret = '123'; + +export const code = 'fbe55d970377e0686746'; +export const state = '7076840850058943'; From 33bf7056f0a910f9bd52a6bdcb5176c2d41d221f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 10 May 2023 22:14:58 +0200 Subject: [PATCH 05/11] collect new package's coverage --- .github/workflows/ci-master.yml | 2 +- .github/workflows/ci-pull-requests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index f7a0f8f21aa5e..17f55e4c158eb 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -38,7 +38,7 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: - files: packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml + files: packages/@n8n_io/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml - name: Lint env: diff --git a/.github/workflows/ci-pull-requests.yml b/.github/workflows/ci-pull-requests.yml index e6d4cdabd741c..fa26de687e1d4 100644 --- a/.github/workflows/ci-pull-requests.yml +++ b/.github/workflows/ci-pull-requests.yml @@ -70,7 +70,7 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: - files: packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml + files: packages/@n8n_io/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml lint: name: Lint changes From efb7d3c83b28e81a1a46518fc4ce375a39c6ec55 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 11 May 2023 12:17:07 +0200 Subject: [PATCH 06/11] getToken with Partial to fix type error --- packages/@n8n_io/client-oauth2/src/CodeFlow.ts | 5 ++++- packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts index 7db8719de1ef9..c320f411fcc67 100644 --- a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts +++ b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts @@ -46,7 +46,10 @@ export class CodeFlow { * Get the code token from the redirected uri and make another request for * the user access token. */ - async getToken(uri?: string | URL, opts?: ClientOAuth2Options): Promise { + async getToken( + uri?: string | URL, + opts?: Partial, + ): Promise { const options = { ...this.client.options, ...opts }; expects(options, 'clientId', 'accessTokenUri'); diff --git a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts index 6106daae32b76..3bc7c2ac3aca3 100644 --- a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts +++ b/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts @@ -19,7 +19,7 @@ export class CredentialsFlow { /** * Request an access token using the client credentials. */ - async getToken(opts?: ClientOAuth2Options): Promise { + async getToken(opts?: Partial): Promise { const options = { ...this.client.options, ...opts }; expects(options, 'clientId', 'clientSecret', 'accessTokenUri'); From cabe34e64d5350fa76f6a8dc186bf76b93b2c6cb Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 11 May 2023 12:19:27 +0200 Subject: [PATCH 07/11] minor test improvements --- packages/@n8n_io/client-oauth2/src/utils.ts | 2 +- .../client-oauth2/test/CodeFlow.test.ts | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/@n8n_io/client-oauth2/src/utils.ts b/packages/@n8n_io/client-oauth2/src/utils.ts index 406fcb947574c..e3433e5cf1c99 100644 --- a/packages/@n8n_io/client-oauth2/src/utils.ts +++ b/packages/@n8n_io/client-oauth2/src/utils.ts @@ -17,7 +17,7 @@ export function expects(obj: any, ...args: any[]) { } } -class AuthError extends Error { +export class AuthError extends Error { constructor(message: string, readonly body: any, readonly code = 'EAUTH') { super(message); } diff --git a/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts b/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts index ff90ef247d0b8..7b62e5ba141ca 100644 --- a/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts +++ b/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts @@ -1,6 +1,7 @@ import nock from 'nock'; import { ClientOAuth2, ClientOAuth2Token } from '../src'; import * as config from './config'; +import { AuthError } from '@/utils'; describe('CodeFlow', () => { beforeAll(async () => { @@ -27,7 +28,7 @@ describe('CodeFlow', () => { it('should return a valid uri', () => { expect(githubAuth.code.getUri()).toEqual( `${config.authorizationUri}?client_id=abc&` + - 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + `redirect_uri=${encodeURIComponent(config.redirectUri)}&` + 'response_type=code&state=&scope=notifications', ); }); @@ -44,7 +45,7 @@ describe('CodeFlow', () => { }); expect(authWithoutScopes.code.getUri()).toEqual( `${config.authorizationUri}?client_id=abc&` + - 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + `redirect_uri=${encodeURIComponent(config.redirectUri)}&` + 'response_type=code&state=', ); }); @@ -62,7 +63,7 @@ describe('CodeFlow', () => { }); expect(authWithEmptyScopes.code.getUri()).toEqual( `${config.authorizationUri}?client_id=abc&` + - 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + `redirect_uri=${encodeURIComponent(config.redirectUri)}&` + 'response_type=code&state=&scope=', ); }); @@ -79,7 +80,7 @@ describe('CodeFlow', () => { }); expect(authWithEmptyScopes.code.getUri()).toEqual( `${config.authorizationUri}?client_id=abc&` + - 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + `redirect_uri=${encodeURIComponent(config.redirectUri)}&` + 'response_type=code&state=&scope=', ); }); @@ -97,7 +98,7 @@ describe('CodeFlow', () => { }); expect(authWithParams.code.getUri()).toEqual( `${config.authorizationUri}?bar=qux&client_id=abc&` + - 'redirect_uri=http%3A%2F%2Fexample.com%2Fauth%2Fcallback&' + + `redirect_uri=${encodeURIComponent(config.redirectUri)}&` + 'response_type=code&state=&scope=notifications', ); }); @@ -136,9 +137,11 @@ describe('CodeFlow', () => { await githubAuth.code.getToken(`${config.redirectUri}?error=invalid_request`); } catch (err) { errored = true; - - expect(err.code).toEqual('EAUTH'); - expect(err.body.error).toEqual('invalid_request'); + expect(err).toBeInstanceOf(AuthError); + if (err instanceof AuthError) { + expect(err.code).toEqual('EAUTH'); + expect(err.body.error).toEqual('invalid_request'); + } } expect(errored).toEqual(true); }); From 702ac4db6301f6e55a09d15178a1f1b59cb0a072 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 11 May 2023 12:39:41 +0200 Subject: [PATCH 08/11] fixing no-non-null-assertion --- packages/@n8n_io/client-oauth2/src/CodeFlow.ts | 13 ++++++++----- .../cli/src/credentials/oauth2Credential.api.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts index c320f411fcc67..f25feb574c33c 100644 --- a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts +++ b/packages/@n8n_io/client-oauth2/src/CodeFlow.ts @@ -38,8 +38,11 @@ export class CodeFlow { query.scope = sanitizeScope(options.scopes); } - const sep = options.authorizationUri!.includes('?') ? '&' : '?'; - return options.authorizationUri! + sep + qs.stringify({ ...query, ...options.query }); + if (options.authorizationUri) { + const sep = options.authorizationUri.includes('?') ? '&' : '?'; + return options.authorizationUri + sep + qs.stringify({ ...query, ...options.query }); + } + throw new TypeError('Missing authorization uri, unable to get redirect uri'); } /** @@ -47,14 +50,14 @@ export class CodeFlow { * the user access token. */ async getToken( - uri?: string | URL, + uri: string | URL, opts?: Partial, ): Promise { const options = { ...this.client.options, ...opts }; expects(options, 'clientId', 'accessTokenUri'); - const url = uri instanceof URL ? uri : new URL(uri!, DEFAULT_URL_BASE); + const url = uri instanceof URL ? uri : new URL(uri, DEFAULT_URL_BASE); if ( typeof options.redirectUri === 'string' && typeof url.pathname === 'string' && @@ -65,7 +68,7 @@ export class CodeFlow { if (!url.search?.substring(1)) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - throw new TypeError(`Unable to process uri: ${uri!.toString()}`); + throw new TypeError(`Unable to process uri: ${uri.toString()}`); } const data = diff --git a/packages/cli/src/credentials/oauth2Credential.api.ts b/packages/cli/src/credentials/oauth2Credential.api.ts index a6e475048fb53..35a37dbb56886 100644 --- a/packages/cli/src/credentials/oauth2Credential.api.ts +++ b/packages/cli/src/credentials/oauth2Credential.api.ts @@ -281,7 +281,7 @@ oauth2CredentialController.get( const queryParameters = req.originalUrl.split('?').splice(1, 1).join(''); const oauthToken = await oAuthObj.code.getToken( - `${oAuth2Parameters.redirectUri}?${queryParameters}`, + `${oAuth2Parameters.redirectUri as string}?${queryParameters}`, // @ts-ignore options, ); From c3b469421a6e891ce6f478562de2e6613f650b55 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 11 May 2023 17:06:59 +0200 Subject: [PATCH 09/11] fixing refresh missing opts argument (Hubspot wasn't working) Original client-oauth2 implementation: https://github.com/mulesoft-labs/js-client-oauth2/blob/68d2a71565641d321148798d7df9d78a3182e584/src/client-oauth2.js#LL386C15-L386C15 --- packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts index 05b1353ca49ac..1a4c754441c6a 100644 --- a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts +++ b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts @@ -84,7 +84,7 @@ export class ClientOAuth2Token { grant_type: 'refresh_token', }, }, - {}, + opts, ); const responseData = await this.client.request(requestOptions); From bd8b379dcdb4d71fefa89e48e53c319ea1289051 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 11 May 2023 18:02:13 +0200 Subject: [PATCH 10/11] fixing refresh missing options argument according to original implementation --- packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts index 1a4c754441c6a..5c749a9009074 100644 --- a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts +++ b/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts @@ -67,24 +67,24 @@ export class ClientOAuth2Token { * Refresh a user access token with the supplied token. */ async refresh(opts?: ClientOAuth2Options): Promise { - const { clientId, clientSecret, accessTokenUri } = { ...this.client.options, ...opts }; + const options = { ...this.client.options, ...opts }; if (!this.refreshToken) throw new Error('No refresh token'); const requestOptions = getRequestOptions( { - url: accessTokenUri, + url: options.accessTokenUri, method: 'POST', headers: { ...DEFAULT_HEADERS, - Authorization: auth(clientId, clientSecret), + Authorization: auth(options.clientId, options.clientSecret), }, body: { refresh_token: this.refreshToken, grant_type: 'refresh_token', }, }, - opts, + options, ); const responseData = await this.client.request(requestOptions); From ad5a510099c2fcd4ec09cc0dd0fa08a1d2181afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Tue, 16 May 2023 15:48:40 +0200 Subject: [PATCH 11/11] rename `@n8n_io/client-oauth2` to `@n8n/client-oauth2` --- .github/workflows/ci-master.yml | 2 +- .github/workflows/ci-pull-requests.yml | 2 +- packages/{@n8n_io => @n8n}/client-oauth2/.eslintrc.js | 0 .../{@n8n_io => @n8n}/client-oauth2/jest.config.js | 0 packages/{@n8n_io => @n8n}/client-oauth2/package.json | 4 ++-- .../client-oauth2/src/ClientOAuth2.ts | 0 .../client-oauth2/src/ClientOAuth2Token.ts | 0 .../{@n8n_io => @n8n}/client-oauth2/src/CodeFlow.ts | 0 .../client-oauth2/src/CredentialsFlow.ts | 0 .../{@n8n_io => @n8n}/client-oauth2/src/constants.ts | 0 packages/{@n8n_io => @n8n}/client-oauth2/src/index.ts | 0 packages/{@n8n_io => @n8n}/client-oauth2/src/types.ts | 0 packages/{@n8n_io => @n8n}/client-oauth2/src/utils.ts | 0 .../client-oauth2/test/CodeFlow.test.ts | 0 .../client-oauth2/test/CredentialsFlow.test.ts | 0 .../{@n8n_io => @n8n}/client-oauth2/test/config.ts | 0 .../client-oauth2/tsconfig.build.json | 0 packages/{@n8n_io => @n8n}/client-oauth2/tsconfig.json | 0 packages/cli/package.json | 2 +- packages/cli/src/credentials/oauth2Credential.api.ts | 4 ++-- packages/cli/tsconfig.json | 2 +- packages/core/package.json | 2 +- packages/core/src/NodeExecuteFunctions.ts | 4 ++-- packages/core/src/OAuth2Helper.ts | 2 +- packages/core/tsconfig.json | 2 +- pnpm-lock.yaml | 10 +++++----- pnpm-workspace.yaml | 1 + 27 files changed, 19 insertions(+), 18 deletions(-) rename packages/{@n8n_io => @n8n}/client-oauth2/.eslintrc.js (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/jest.config.js (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/package.json (90%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/ClientOAuth2.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/ClientOAuth2Token.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/CodeFlow.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/CredentialsFlow.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/constants.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/index.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/types.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/src/utils.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/test/CodeFlow.test.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/test/CredentialsFlow.test.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/test/config.ts (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/tsconfig.build.json (100%) rename packages/{@n8n_io => @n8n}/client-oauth2/tsconfig.json (100%) diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index 17f55e4c158eb..53ea34eb91ae8 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -38,7 +38,7 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: - files: packages/@n8n_io/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml + files: packages/@n8n/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml - name: Lint env: diff --git a/.github/workflows/ci-pull-requests.yml b/.github/workflows/ci-pull-requests.yml index fa26de687e1d4..01e913b010580 100644 --- a/.github/workflows/ci-pull-requests.yml +++ b/.github/workflows/ci-pull-requests.yml @@ -70,7 +70,7 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: - files: packages/@n8n_io/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml + files: packages/@n8n/client-oauth2/coverage/cobertura-coverage.xml,packages/cli/coverage/cobertura-coverage.xml,packages/core/coverage/cobertura-coverage.xml,packages/design-system/coverage/cobertura-coverage.xml,packages/editor-ui/coverage/cobertura-coverage.xml,packages/nodes-base/coverage/cobertura-coverage.xml,packages/workflow/coverage/cobertura-coverage.xml lint: name: Lint changes diff --git a/packages/@n8n_io/client-oauth2/.eslintrc.js b/packages/@n8n/client-oauth2/.eslintrc.js similarity index 100% rename from packages/@n8n_io/client-oauth2/.eslintrc.js rename to packages/@n8n/client-oauth2/.eslintrc.js diff --git a/packages/@n8n_io/client-oauth2/jest.config.js b/packages/@n8n/client-oauth2/jest.config.js similarity index 100% rename from packages/@n8n_io/client-oauth2/jest.config.js rename to packages/@n8n/client-oauth2/jest.config.js diff --git a/packages/@n8n_io/client-oauth2/package.json b/packages/@n8n/client-oauth2/package.json similarity index 90% rename from packages/@n8n_io/client-oauth2/package.json rename to packages/@n8n/client-oauth2/package.json index 3770129534a0f..6acda89b76742 100644 --- a/packages/@n8n_io/client-oauth2/package.json +++ b/packages/@n8n/client-oauth2/package.json @@ -1,6 +1,6 @@ { - "name": "@n8n_io/client-oauth2", - "version": "1.0.4", + "name": "@n8n/client-oauth2", + "version": "0.1.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts b/packages/@n8n/client-oauth2/src/ClientOAuth2.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/ClientOAuth2.ts rename to packages/@n8n/client-oauth2/src/ClientOAuth2.ts diff --git a/packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts b/packages/@n8n/client-oauth2/src/ClientOAuth2Token.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/ClientOAuth2Token.ts rename to packages/@n8n/client-oauth2/src/ClientOAuth2Token.ts diff --git a/packages/@n8n_io/client-oauth2/src/CodeFlow.ts b/packages/@n8n/client-oauth2/src/CodeFlow.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/CodeFlow.ts rename to packages/@n8n/client-oauth2/src/CodeFlow.ts diff --git a/packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts b/packages/@n8n/client-oauth2/src/CredentialsFlow.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/CredentialsFlow.ts rename to packages/@n8n/client-oauth2/src/CredentialsFlow.ts diff --git a/packages/@n8n_io/client-oauth2/src/constants.ts b/packages/@n8n/client-oauth2/src/constants.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/constants.ts rename to packages/@n8n/client-oauth2/src/constants.ts diff --git a/packages/@n8n_io/client-oauth2/src/index.ts b/packages/@n8n/client-oauth2/src/index.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/index.ts rename to packages/@n8n/client-oauth2/src/index.ts diff --git a/packages/@n8n_io/client-oauth2/src/types.ts b/packages/@n8n/client-oauth2/src/types.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/types.ts rename to packages/@n8n/client-oauth2/src/types.ts diff --git a/packages/@n8n_io/client-oauth2/src/utils.ts b/packages/@n8n/client-oauth2/src/utils.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/src/utils.ts rename to packages/@n8n/client-oauth2/src/utils.ts diff --git a/packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts b/packages/@n8n/client-oauth2/test/CodeFlow.test.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/test/CodeFlow.test.ts rename to packages/@n8n/client-oauth2/test/CodeFlow.test.ts diff --git a/packages/@n8n_io/client-oauth2/test/CredentialsFlow.test.ts b/packages/@n8n/client-oauth2/test/CredentialsFlow.test.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/test/CredentialsFlow.test.ts rename to packages/@n8n/client-oauth2/test/CredentialsFlow.test.ts diff --git a/packages/@n8n_io/client-oauth2/test/config.ts b/packages/@n8n/client-oauth2/test/config.ts similarity index 100% rename from packages/@n8n_io/client-oauth2/test/config.ts rename to packages/@n8n/client-oauth2/test/config.ts diff --git a/packages/@n8n_io/client-oauth2/tsconfig.build.json b/packages/@n8n/client-oauth2/tsconfig.build.json similarity index 100% rename from packages/@n8n_io/client-oauth2/tsconfig.build.json rename to packages/@n8n/client-oauth2/tsconfig.build.json diff --git a/packages/@n8n_io/client-oauth2/tsconfig.json b/packages/@n8n/client-oauth2/tsconfig.json similarity index 100% rename from packages/@n8n_io/client-oauth2/tsconfig.json rename to packages/@n8n/client-oauth2/tsconfig.json diff --git a/packages/cli/package.json b/packages/cli/package.json index 6c701065d70e5..c679d5ae23376 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -117,7 +117,7 @@ }, "dependencies": { "@n8n_io/license-sdk": "~2.4.0", - "@n8n_io/client-oauth2": "workspace:*", + "@n8n/client-oauth2": "workspace:*", "@oclif/command": "^1.8.16", "@oclif/core": "^1.16.4", "@oclif/errors": "^1.3.6", diff --git a/packages/cli/src/credentials/oauth2Credential.api.ts b/packages/cli/src/credentials/oauth2Credential.api.ts index 3337771af1420..b394dfefb523b 100644 --- a/packages/cli/src/credentials/oauth2Credential.api.ts +++ b/packages/cli/src/credentials/oauth2Credential.api.ts @@ -1,5 +1,5 @@ -import type { ClientOAuth2Options } from '@n8n_io/client-oauth2'; -import { ClientOAuth2 } from '@n8n_io/client-oauth2'; +import type { ClientOAuth2Options } from '@n8n/client-oauth2'; +import { ClientOAuth2 } from '@n8n/client-oauth2'; import Csrf from 'csrf'; import express from 'express'; import get from 'lodash.get'; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 7b52926d17cb8..461b082baa4c1 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -22,6 +22,6 @@ "references": [ { "path": "../workflow/tsconfig.build.json" }, { "path": "../core/tsconfig.build.json" }, - { "path": "../@n8n_io/client-oauth2/tsconfig.build.json" } + { "path": "../@n8n/client-oauth2/tsconfig.build.json" } ] } diff --git a/packages/core/package.json b/packages/core/package.json index 91a8ad5f642c7..af6d0b2a9b15d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -46,7 +46,7 @@ }, "dependencies": { "axios": "^0.21.1", - "@n8n_io/client-oauth2": "workspace:*", + "@n8n/client-oauth2": "workspace:*", "concat-stream": "^2.0.0", "cron": "~1.7.2", "crypto-js": "~4.1.1", diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 55f01d8fce364..6bd3f5433b08a 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -86,8 +86,8 @@ import type { ClientOAuth2Options, ClientOAuth2RequestObject, ClientOAuth2TokenData, -} from '@n8n_io/client-oauth2'; -import { ClientOAuth2 } from '@n8n_io/client-oauth2'; +} from '@n8n/client-oauth2'; +import { ClientOAuth2 } from '@n8n/client-oauth2'; import crypto, { createHmac } from 'crypto'; import get from 'lodash.get'; import type { Request, Response } from 'express'; diff --git a/packages/core/src/OAuth2Helper.ts b/packages/core/src/OAuth2Helper.ts index b41349b26dc63..dacb8d44a0c58 100644 --- a/packages/core/src/OAuth2Helper.ts +++ b/packages/core/src/OAuth2Helper.ts @@ -1,5 +1,5 @@ import type { ICredentialDataDecryptedObject } from 'n8n-workflow'; -import type { ClientOAuth2, ClientOAuth2Options, ClientOAuth2Token } from '@n8n_io/client-oauth2'; +import type { ClientOAuth2, ClientOAuth2Options, ClientOAuth2Token } from '@n8n/client-oauth2'; export const getClientCredentialsToken = async ( oAuth2Client: ClientOAuth2, diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 55f880dfcdf1e..ed61b927dd395 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -16,6 +16,6 @@ "include": ["src/**/*.ts", "test/**/*.ts"], "references": [ { "path": "../workflow/tsconfig.build.json" }, - { "path": "../@n8n_io/client-oauth2/tsconfig.build.json" } + { "path": "../@n8n/client-oauth2/tsconfig.build.json" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e312e37e8c75f..6f122dc473b14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,7 +137,7 @@ importers: specifier: ^1.0.24 version: 1.0.24(typescript@5.0.3) - packages/@n8n_io/client-oauth2: + packages/@n8n/client-oauth2: dependencies: axios: specifier: ^0.21.1 @@ -193,9 +193,9 @@ importers: packages/cli: dependencies: - '@n8n_io/client-oauth2': + '@n8n/client-oauth2': specifier: workspace:* - version: link:../@n8n_io/client-oauth2 + version: link:../@n8n/client-oauth2 '@n8n_io/license-sdk': specifier: ~2.4.0 version: 2.4.0 @@ -632,9 +632,9 @@ importers: packages/core: dependencies: - '@n8n_io/client-oauth2': + '@n8n/client-oauth2': specifier: workspace:* - version: link:../@n8n_io/client-oauth2 + version: link:../@n8n/client-oauth2 axios: specifier: ^0.21.1 version: 0.21.4(debug@4.3.2) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4a89a5858c0a8..ce18506b3820a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - packages/* + - packages/@n8n/* - packages/@n8n_io/*