152 lines
5.7 KiB
JavaScript
152 lines
5.7 KiB
JavaScript
"use strict";
|
|
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.TunnelAccessTokenProperties = void 0;
|
|
/**
|
|
* Supports parsing tunnel access token JWT properties to allow for some pre-validation
|
|
* and diagnostics.
|
|
*
|
|
* Applications generally should not attempt to interpret or rely on any token properties
|
|
* other than `expiration`, because the service may change or omit those claims in the future.
|
|
* Other claims are exposed here only for diagnostic purposes.
|
|
*/
|
|
class TunnelAccessTokenProperties {
|
|
constructor(clusterId, tunnelId, tunnelPorts, scopes, issuer, expiration) {
|
|
this.clusterId = clusterId;
|
|
this.tunnelId = tunnelId;
|
|
this.tunnelPorts = tunnelPorts;
|
|
this.scopes = scopes;
|
|
this.issuer = issuer;
|
|
this.expiration = expiration;
|
|
}
|
|
toString() {
|
|
let s = '';
|
|
if (this.tunnelId) {
|
|
s += 'tunnel=';
|
|
s += this.tunnelId;
|
|
if (this.clusterId) {
|
|
s += '.';
|
|
s += this.clusterId;
|
|
}
|
|
}
|
|
if (this.tunnelPorts && this.tunnelPorts.length > 0) {
|
|
if (s.length > 0)
|
|
s += ', ';
|
|
if (this.tunnelPorts.length === 1) {
|
|
s += `port=${this.tunnelPorts[0]}`;
|
|
}
|
|
else {
|
|
s += `ports=[${this.tunnelPorts.join(', ')}]`;
|
|
}
|
|
}
|
|
if (this.scopes) {
|
|
if (s.length > 0)
|
|
s += ', ';
|
|
s += `scopes=[${this.scopes.join(', ')}]`;
|
|
}
|
|
if (this.issuer) {
|
|
if (s.length > 0)
|
|
s += ', ';
|
|
s += 'issuer=';
|
|
s += this.issuer;
|
|
}
|
|
if (this.expiration) {
|
|
if (s.length > 0)
|
|
s += ', ';
|
|
s += `expiration=${this.expiration.toString().replace('.000Z', 'Z')}`;
|
|
}
|
|
return s;
|
|
}
|
|
/**
|
|
* Checks if the tunnel access token expiration claim is in the past.
|
|
*
|
|
* (Does not throw if the token is an invalid format.)
|
|
*/
|
|
static validateTokenExpiration(token) {
|
|
const t = TunnelAccessTokenProperties.tryParse(token);
|
|
if (t === null || t === void 0 ? void 0 : t.expiration) {
|
|
if (t.expiration < new Date()) {
|
|
throw new Error('The access token is expired: ' + t);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Attempts to parse a tunnel access token (JWT). This does NOT validate the token
|
|
* signature or any claims.
|
|
*/
|
|
static tryParse(token) {
|
|
if (typeof token !== 'string')
|
|
throw new TypeError('Token string expected.');
|
|
// JWTs are encoded in 3 parts: header, body, and signature.
|
|
const tokenParts = token.split('.');
|
|
if (tokenParts.length !== 3) {
|
|
return null;
|
|
}
|
|
const tokenBodyJson = TunnelAccessTokenProperties.base64UrlDecode(tokenParts[1]);
|
|
if (!tokenBodyJson) {
|
|
return null;
|
|
}
|
|
try {
|
|
const tokenJson = JSON.parse(tokenBodyJson);
|
|
const clusterId = tokenJson.clusterId;
|
|
const tunnelId = tokenJson.tunnelId;
|
|
const ports = tokenJson.tunnelPorts;
|
|
const scp = tokenJson.scp;
|
|
const iss = tokenJson.iss;
|
|
const exp = tokenJson.exp;
|
|
return new TunnelAccessTokenProperties(clusterId, tunnelId, typeof ports === 'number' ? [ports] : ports, scp === null || scp === void 0 ? void 0 : scp.split(' '), iss, typeof exp === 'number' ? new Date(exp * 1000) : undefined);
|
|
}
|
|
catch (_a) {
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Gets the tunnal access token trace string.
|
|
* 'none' if null or undefined, parsed token info if can be parsed, or 'token' if cannot be parsed.
|
|
*/
|
|
static getTokenTrace(token) {
|
|
var _a, _b;
|
|
return !token ? 'none' : (_b = (_a = TunnelAccessTokenProperties.tryParse(token)) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : 'token';
|
|
}
|
|
/**
|
|
* Gets a tunnel access token that matches any of the provided access token scopes.
|
|
* Validates token expiration if the token is found and throws an error if it's expired.
|
|
* @param tunnel The tunnel to get the access tokens from.
|
|
* @param accessTokenScopes What scopes the token needs to have.
|
|
* @returns Tunnel access token if found; otherwise, undefined.
|
|
*/
|
|
static getTunnelAccessToken(tunnel, accessTokenScopes) {
|
|
if (!(tunnel === null || tunnel === void 0 ? void 0 : tunnel.accessTokens) || !accessTokenScopes) {
|
|
return;
|
|
}
|
|
if (!Array.isArray(accessTokenScopes)) {
|
|
accessTokenScopes = [accessTokenScopes];
|
|
}
|
|
for (const scope of accessTokenScopes) {
|
|
for (const [key, accessToken] of Object.entries(tunnel.accessTokens)) {
|
|
// Each key may be either a single scope or space-delimited list of scopes.
|
|
if (accessToken && key.split(' ').includes(scope)) {
|
|
TunnelAccessTokenProperties.validateTokenExpiration(accessToken);
|
|
return accessToken;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static base64UrlDecode(encodedString) {
|
|
// Convert from base64url encoding to base64 encoding: replace chars and add padding.
|
|
encodedString = encodedString.replace('-', '+');
|
|
while (encodedString.length % 4 !== 0) {
|
|
encodedString += '=';
|
|
}
|
|
try {
|
|
const result = atob(encodedString);
|
|
return result;
|
|
}
|
|
catch (_a) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
exports.TunnelAccessTokenProperties = TunnelAccessTokenProperties;
|
|
//# sourceMappingURL=tunnelAccessTokenProperties.js.map
|