-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathtoken-accessor.ts
137 lines (123 loc) · 4.88 KB
/
token-accessor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { errorWithCause } from '@sap-cloud-sdk/util';
import { DecodedJWT, decodeJwt } from '../util';
import { CachingOptions } from './cache';
import { clientCredentialsTokenCache } from './client-credentials-token-cache';
import {
extractClientCredentials,
getXsuaaServiceCredentials,
resolveService
} from './environment-accessor';
import { Service } from './environment-accessor-types';
import { ResilienceOptions } from './resilience-options';
import { replaceSubdomain } from './subdomain-replacer';
import {
clientCredentialsGrant,
refreshTokenGrant,
userTokenGrant
} from './xsuaa-service';
import { UserTokenResponse } from './xsuaa-service-types';
/**
* Returns an access token that can be used to call the given service. The token is fetched via a client credentials grant with the credentials of the given service.
* If multiple instances of the provided service exist, the first instance will be selected.
* When a JWT is passed, the tenant of the JWT will be used when performing the grant.
* When no JWT is passed, the grant will be performed using the provider tenant.
*
* Throws an error if there is no instance of the given service type or the XSUAA service, or if the request to the XSUAA service fails.
*
* @param service - The type of the service or an instance of [[Service]].
* @param options - Options to influence caching and resilience behavior (see [[CachingOptions]] and [[ResilienceOptions]], respectively) and a JWT. By default, caching and usage of a circuit breaker are enabled.
* @returns Access token.
*/
export async function serviceToken(
service: string | Service,
options?: CachingOptions &
ResilienceOptions & {
userJwt?: string | DecodedJWT;
}
): Promise<string> {
const resolvedService = resolveService(service);
const opts = {
useCache: true,
enableCircuitBreaker: true,
...(options || {}) // Tsc complains otherwise
};
const xsuaa = multiTenantXsuaaCredentials(opts.userJwt);
const serviceCreds = extractClientCredentials(resolvedService.credentials);
if (opts.useCache) {
const cachedToken = clientCredentialsTokenCache.getGrantTokenFromCache(
xsuaa.url,
serviceCreds
);
if (cachedToken) {
return Promise.resolve(cachedToken.access_token);
}
}
return clientCredentialsGrant(xsuaa, serviceCreds, opts)
.then(resp => {
if (opts.useCache) {
clientCredentialsTokenCache.cacheRetrievedToken(
xsuaa.url,
serviceCreds,
resp
);
}
return resp.access_token;
})
.catch(error => {
throw errorWithCause(
`Fetching an access token for service "${resolvedService.label}" failed!`,
error
);
});
}
/**
* Returns a user approved access token that can be used to call the given service on behalf of the given user. The token is fetched via user token + refresh token grant.
* This can be necessary for scenarios in which a token for a service is required, but the service needs
* to know about the user on whose behalf the request is performed (for example to let the destination
* service perform principal propagation with SAP S/4HANA Cloud).
*
* Throws an error if there is no instance of the given service type or the XSUAA service, or if the request to the XSUAA service fails.
*
* @param userJwt - The JWT of the user for whom the access token should be fetched.
* @param service - The type of the service or an instance of [[Service]].
* @param options - Options to influence resilience behavior (see [[ResilienceOptions]]). By default, usage of a circuit breaker is enabled.
* @returns A user approved access token.
*/
export async function userApprovedServiceToken(
userJwt: string,
service: string | Service,
options?: ResilienceOptions
): Promise<string> {
const resolvedService = resolveService(service);
const opts: ResilienceOptions = {
enableCircuitBreaker: true,
...options
};
const xsuaa = multiTenantXsuaaCredentials(userJwt);
const serviceCreds = extractClientCredentials(resolvedService.credentials);
return userTokenGrant(xsuaa.url, userJwt, serviceCreds.username, opts)
.then(userToken =>
refreshTokenGrant(xsuaa, serviceCreds, userToken.refresh_token, opts)
)
.then((refreshToken: UserTokenResponse) => refreshToken.access_token)
.catch(error => {
throw errorWithCause(
`Fetching a user approved access token for service "${resolvedService.label}" failed!`,
error
);
});
}
function multiTenantXsuaaCredentials(userJwt?: string | DecodedJWT) {
const xsuaa = getXsuaaServiceCredentials(userJwt);
if (userJwt) {
const decodedJwt =
typeof userJwt === 'string' ? decodeJwt(userJwt) : userJwt;
if (!decodedJwt.iss) {
throw Error(
'Property "iss" is missing from the provided user token! This shouldn\'t happen.'
);
}
xsuaa.url = replaceSubdomain(decodedJwt.iss, xsuaa.url);
}
return xsuaa;
}