Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDMEXT-630: [Nodejs] Support multi-tenancy #63

Merged
merged 2 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading