Skip to content

Commit

Permalink
Merge pull request #63 from cap-js/SDMEXT-630/-Nodejs]-Support-multi-…
Browse files Browse the repository at this point in the history
…tenancy

SDMEXT-630: [Nodejs] Support multi-tenancy
  • Loading branch information
rashmiangadi11 authored Aug 22, 2024
2 parents b65879d + 1addfc0 commit c2f3ca5
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 21 deletions.
24 changes: 16 additions & 8 deletions lib/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,35 @@ const {

async function fetchAccessToken(credentials, jwt) {
let decoded_token_jwt = decodeAccessToken(jwt);
let access_token = cache.get(decoded_token_jwt.email); // to check if token exists
let subdomain = cds.context.user?.tokenInfo?.getPayload()?.ext_attr?.zdn;
let cache_key = decoded_token_jwt.email+"_"+subdomain;
let access_token = cache.get(cache_key); // to check if token exists
if (access_token === undefined) {
access_token = await generateSDMBearerToken(credentials, jwt);
let user = decodeAccessToken(access_token).email;
cache.set(user, access_token, 11 * 3600); //expires after 11 hours
let subdomain = cds.context.user?.tokenInfo?.getPayload()?.ext_attr?.zdn;
let cache_key = user+"_"+subdomain;
cache.set(cache_key, access_token, 11 * 3600); //expires after 11 hours
} else {
let decoded_token = decodeAccessToken(access_token);
if (isTokenExpired(decoded_token.exp)) {
access_token = generateSDMBearerToken(credentials, jwt);
cache.del(decoded_token.email);
cache.set(decoded_token.email, access_token, 11 * 3600); //expires after 11 hours
let subdomain = cds.context.user?.tokenInfo?.getPayload()?.ext_attr?.zdn;
let cache_key = decoded_token.email+"_"+subdomain;
cache.del(cache_key);
cache.set(cache_key, access_token, 11 * 3600); //expires after 11 hours
}

}
return access_token;
}
async function generateSDMBearerToken(credentials, jwt) {
let subdomain = cds.context.user?.tokenInfo?.getPayload()?.ext_attr?.zdn;
return new Promise(function (resolve, reject) {
requests.requestUserToken(
jwt,
credentials.uaa,
null, null, null, null, (error, response) => {
null, null, subdomain, null, (error, response) => {
if (error) {
console.error(
`Response error while fetching access token ${response.statusCode}`
Expand Down Expand Up @@ -76,11 +83,12 @@ function decodeAccessToken(jwtEncoded) {
}

async function getClientCredentialsToken(credentials) {
const access_token = cache.get("SDM_ACCESS_TOKEN"); // to check if token exists
let subdomain = cds.context.user?.tokenInfo?.getPayload()?.ext_attr?.zdn;
const access_token = cache.get("SDM_ACCESS_TOKEN_"+subdomain); // to check if token exists
if (access_token === undefined) {
return new Promise(function (resolve, reject) {
requests.requestClientCredentialsToken(
null,
subdomain,
credentials.uaa,
null,
(error, response) => {
Expand All @@ -90,7 +98,7 @@ async function getClientCredentialsToken(credentials) {
);
reject(err);
} else {
cache.set("SDM_ACCESS_TOKEN", response, 11*3600); //expires after 11 hours
cache.set("SDM_ACCESS_TOKEN_"+subdomain, response, 11*3600); //expires after 11 hours
resolve(response);
}
}
Expand Down
88 changes: 75 additions & 13 deletions test/lib/util/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,17 @@ describe("util", () => {
xssec.requests.requestUserToken.mockImplementation(
(a, b, c, d, e, f, callback) => callback(null, dummyToken)
);

cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
ext_attr: {
zdn: 'subdomain' // simulate the subdomain extraction
}
})),
},
},
};
const credentials = { uaa: "uaa" };
const req = {
user: {
Expand All @@ -57,11 +67,11 @@ describe("util", () => {
},
},
};
const accessToken = await fetchAccessToken(credentials, req.user.tokenInfo.getTokenValue);
expect(NodeCache.prototype.get).toBeCalledWith("example@example.com");
const accessToken = await fetchAccessToken(credentials, req.user.tokenInfo.getTokenValue);
const expectedCacheKey = "example@example.com_subdomain";
expect(xssec.requests.requestUserToken).toBeCalled();
expect(NodeCache.prototype.set).toBeCalledWith(
"[email protected]",
expectedCacheKey,
dummyToken,
11 * 3600
);
Expand All @@ -77,9 +87,20 @@ describe("util", () => {
},
},
};
cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
ext_attr: {
zdn: 'subdomain' // simulate the subdomain extraction
}
})),
},
},
};
const credentials = { uaa: "uaa" };
const accessToken = await fetchAccessToken(credentials, req.user.tokenInfo.getTokenValue);
expect(NodeCache.prototype.get).toBeCalledWith("example@example.com");
expect(NodeCache.prototype.get).toBeCalledWith("example@example.com_subdomain");
expect(xssec.requests.requestUserToken).toBeCalled();
expect(accessToken).toBe(dummyToken);
});
Expand All @@ -90,7 +111,17 @@ describe("util", () => {
"email": "[email protected]",
"exp": 2537353178
};

cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
ext_attr: {
zdn: 'subdomain' // simulate the subdomain extraction
}
})),
},
},
};
// Please replace 'your_secret_key' with your own secret key
const secretKey = 'your_secret_key';

Expand All @@ -106,7 +137,7 @@ describe("util", () => {
};
const credentials = { uaa: "uaa" };
const accessToken = await fetchAccessToken(credentials, req.user.tokenInfo.getTokenValue);
expect(NodeCache.prototype.get).toBeCalledWith("example@example.com");
expect(NodeCache.prototype.get).toBeCalledWith("example@example.com_subdomain");
expect(xssec.requests.requestUserToken).not.toBeCalled();
expect(accessToken).toBe(dummyToken);
});
Expand All @@ -120,6 +151,17 @@ describe("util", () => {
(a, b, c, d, e, f, callback) =>
callback(new Error("test error"), { statusCode: 500 })
);
cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
ext_attr: {
zdn: 'subdomain' // simulate the subdomain extraction
}
})),
},
},
};
const req = {
user: {
tokenInfo: {
Expand All @@ -131,7 +173,7 @@ describe("util", () => {
try {
await fetchAccessToken(credentials, req.user.tokenInfo.getTokenValue);
} catch (err) {
expect(NodeCache.prototype.get).toBeCalledWith("example@example.com");
expect(NodeCache.prototype.get).toBeCalledWith("example@example.com_subdomain");
expect(xssec.requests.requestUserToken).toBeCalled();
expect(consoleErrorSpy).toBeCalledWith(
"Response error while fetching access token 500"
Expand All @@ -153,11 +195,21 @@ describe("util", () => {
it('returns cached token if available', async () => {
const cachedToken = 'mockedAccessToken';
NodeCache.prototype.get.mockImplementation(() => cachedToken);

cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
ext_attr: {
zdn: 'subdomain' // simulate the subdomain extraction
}
})),
},
},
};
const token = await getClientCredentialsToken({ uaa: 'mockedUaa' });

expect(token).toBe(cachedToken);
expect(NodeCache.prototype.get).toHaveBeenCalledWith('SDM_ACCESS_TOKEN');
expect(NodeCache.prototype.get).toHaveBeenCalledWith('SDM_ACCESS_TOKEN_subdomain');
expect(xssec.requests.requestClientCredentialsToken).not.toHaveBeenCalled();
});

Expand All @@ -168,13 +220,23 @@ describe("util", () => {
xssec.requests.requestClientCredentialsToken.mockImplementation((_, __, ___, callback) => {
callback(null, mockResponse);
});

cds.context = {
user: {
tokenInfo: {
getPayload: jest.fn(() => ({
ext_attr: {
zdn: 'subdomain' // simulate the subdomain extraction
}
})),
},
},
};
const token = await getClientCredentialsToken(credentials);

expect(token).toBe(mockResponse);
expect(NodeCache.prototype.set).toHaveBeenCalledWith('SDM_ACCESS_TOKEN', mockResponse, expect.any(Number));
expect(NodeCache.prototype.set).toHaveBeenCalledWith('SDM_ACCESS_TOKEN_subdomain', mockResponse, expect.any(Number));
expect(xssec.requests.requestClientCredentialsToken).toHaveBeenCalledWith(
null,
"subdomain",
credentials.uaa,
null,
expect.any(Function)
Expand Down

0 comments on commit c2f3ca5

Please sign in to comment.