Skip to content

Commit fb32783

Browse files
authored
Merge pull request #279 from contentstack/feat/DX-2206
feat: oauth support
2 parents 9d2bd30 + 13f2bef commit fb32783

12 files changed

+665
-38
lines changed

lib/contentstackClient.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Organization } from './organization/index'
66
import cloneDeep from 'lodash/cloneDeep'
77
import { User } from './user/index'
88
import error from './core/contentstackError'
9+
import OAuthHandler from './core/oauthHandler'
910

1011
export default function contentstackClient ({ http }) {
1112
/**
@@ -172,12 +173,44 @@ export default function contentstackClient ({ http }) {
172173
}, error)
173174
}
174175

176+
/**
177+
* @description The oauth call is used to sign in to your Contentstack account and obtain the accesstoken.
178+
* @memberof ContentstackClient
179+
* @func oauth
180+
* @param {Object} parameters - oauth parameters
181+
* @prop {string} parameters.appId - appId of the application
182+
* @prop {string} parameters.clientId - clientId of the application
183+
* @prop {string} parameters.clientId - clientId of the application
184+
* @prop {string} parameters.responseType - responseType
185+
* @prop {string} parameters.scope - scope
186+
* @prop {string} parameters.clientSecret - clientSecret of the application
187+
* @returns {OAuthHandler} Instance of OAuthHandler
188+
* @example
189+
* import * as contentstack from '@contentstack/management'
190+
* const client = contentstack.client()
191+
*
192+
* client.oauth({ appId: <appId>, clientId: <clientId>, redirectUri: <redirectUri>, clientSecret: <clientSecret>, responseType: <responseType>, scope: <scope> })
193+
* .then(() => console.log('Logged in successfully'))
194+
*
195+
*/
196+
function oauth(params = {}) {
197+
http.defaults.versioningStrategy = "path";
198+
const appId = params.appId || '6400aa06db64de001a31c8a9';
199+
const clientId = params.clientId || 'Ie0FEfTzlfAHL4xM';
200+
const redirectUri = params.redirectUri || 'http://localhost:8184';
201+
const responseType = params.responseType || 'code';
202+
const scope = params.scope;
203+
const clientSecret = params.clientSecret;
204+
return new OAuthHandler(http, appId, clientId, redirectUri, clientSecret,responseType, scope);
205+
}
206+
175207
return {
176208
login: login,
177209
logout: logout,
178210
getUser: getUser,
179211
stack: stack,
180212
organization: organization,
181-
axiosInstance: http
213+
axiosInstance: http,
214+
oauth,
182215
}
183216
}

lib/core/concurrency-queue.js

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Axios from 'axios'
2+
import OAuthHandler from './oauthHandler'
23
const defaultConfig = {
34
maxRequests: 5,
45
retryLimit: 5,
@@ -75,17 +76,17 @@ export function ConcurrencyQueue ({ axios, config }) {
7576
request.formdata = request.data
7677
request.data = transformFormData(request)
7778
}
78-
request.retryCount = request.retryCount || 0
79-
if (request.headers.authorization && request.headers.authorization !== undefined) {
80-
if (this.config.authorization && this.config.authorization !== undefined) {
81-
request.headers.authorization = this.config.authorization
82-
request.authorization = this.config.authorization
79+
if (axios?.oauth?.accessToken) {
80+
const isTokenExpired = axios.oauth.tokenExpiryTime && Date.now() > axios.oauth.tokenExpiryTime;
81+
if (isTokenExpired) {
82+
return refreshAccessToken().catch((error) => {
83+
throw new Error('Failed to refresh access token: ' + error.message);
84+
});
8385
}
84-
delete request.headers.authtoken
85-
} else if (request.headers.authtoken && request.headers.authtoken !== undefined && this.config.authtoken && this.config.authtoken !== undefined) {
86-
request.headers.authtoken = this.config.authtoken
87-
request.authtoken = this.config.authtoken
88-
}
86+
}
87+
88+
request.retryCount = request?.retryCount || 0
89+
setAuthorizationHeaders(request);
8990
if (request.cancelToken === undefined) {
9091
const source = Axios.CancelToken.source()
9192
request.cancelToken = source.token
@@ -108,6 +109,40 @@ export function ConcurrencyQueue ({ axios, config }) {
108109
})
109110
}
110111

112+
const setAuthorizationHeaders = (request) => {
113+
if (request.headers.authorization && request.headers.authorization !== undefined) {
114+
if (this.config.authorization && this.config.authorization !== undefined) {
115+
request.headers.authorization = this.config.authorization
116+
request.authorization = this.config.authorization
117+
}
118+
delete request.headers.authtoken
119+
} else if (request.headers.authtoken && request.headers.authtoken !== undefined && this.config.authtoken && this.config.authtoken !== undefined) {
120+
request.headers.authtoken = this.config.authtoken
121+
request.authtoken = this.config.authtoken
122+
} else if (axios?.oauth?.accessToken) {
123+
// If OAuth access token is available in axios instance
124+
request.headers.authorization = `Bearer ${axios.oauth.accessToken}`;
125+
request.authorization = `Bearer ${axios.oauth.accessToken}`;
126+
delete request.headers.authtoken
127+
}
128+
}
129+
130+
//Refresh Access Token
131+
const refreshAccessToken = async () => {
132+
try {
133+
await new OAuthHandler(axios).refreshAccessToken();
134+
this.paused = false; // Resume the request queue once the token is refreshed
135+
this.running.forEach(({ request, resolve, reject }) => {
136+
resolve(request); // Retry the queued requests
137+
});
138+
this.running = []; // Clear the running queue
139+
} catch (error) {
140+
this.paused = false; // Ensure we stop queueing requests on failure
141+
this.running.forEach(({ reject }) => reject(error)); // Reject all queued requests
142+
this.running = []; // Clear the running queue
143+
}
144+
};
145+
111146
const delay = (time, isRefreshToken = false) => {
112147
if (!this.paused) {
113148
this.paused = true

lib/core/contentstackHTTPClient.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,31 @@ export default function contentstackHttpClient (options) {
6565
config.basePath = `/${config.basePath.split('/').filter(Boolean).join('/')}`
6666
}
6767
const baseURL = config.endpoint || `${protocol}://${hostname}:${port}${config.basePath}/{api-version}`
68+
let uiHostName = hostname;
69+
let developerHubBaseUrl = hostname;
70+
71+
if (hostname.endsWith('io')) {
72+
uiHostName = hostname.replace('io', 'com');
73+
}
74+
75+
if (hostname.startsWith('api')) {
76+
uiHostName = uiHostName.replace('api', 'app');
77+
}
78+
const uiBaseUrl = config.endpoint || `${protocol}://${uiHostName}`;
79+
80+
developerHubBaseUrl = developerHubBaseUrl
81+
?.replace('api', 'developerhub-api')
82+
.replace(/^dev\d+/, 'dev') // Replaces any 'dev1', 'dev2', etc. with 'dev'
83+
.replace('io', 'com')
84+
.replace(/^http/, '') // Removing `http` if already present
85+
.replace(/^/, 'https://'); // Adds 'https://' at the start if not already there
86+
87+
// set ui host name
6888
const axiosOptions = {
6989
// Axios
7090
baseURL,
91+
uiBaseUrl,
92+
developerHubBaseUrl,
7193
...config,
7294
paramsSerializer: function (params) {
7395
var query = params.query

0 commit comments

Comments
 (0)