@acrolinx/sdk
Advanced tools
Comparing version 1.2.0-dev.10 to 1.2.0-dev.11
@@ -27,9 +27,3 @@ import { DocumentId } from './document-descriptor'; | ||
AppSignatureRejected = "appSignatureRejected", | ||
LicenseLimitExceeded = "licenseLimitExceeded", | ||
AuthorizationPending = "authorization_pending", | ||
InvalidClient = "invalid_client", | ||
RealmNotExist = "Realm does not exist", | ||
InvalidGrant = "invalid_grant", | ||
UnsuppotedGrantType = "unsupported_grant_type", | ||
InavlidRequest = "invalid_request" | ||
LicenseLimitExceeded = "licenseLimitExceeded" | ||
} | ||
@@ -36,0 +30,0 @@ export interface AcrolinxErrorProps { |
@@ -63,8 +63,2 @@ "use strict"; | ||
ErrorType["LicenseLimitExceeded"] = "licenseLimitExceeded"; | ||
ErrorType["AuthorizationPending"] = "authorization_pending"; | ||
ErrorType["InvalidClient"] = "invalid_client"; | ||
ErrorType["RealmNotExist"] = "Realm does not exist"; | ||
ErrorType["InvalidGrant"] = "invalid_grant"; | ||
ErrorType["UnsuppotedGrantType"] = "unsupported_grant_type"; | ||
ErrorType["InavlidRequest"] = "invalid_request"; | ||
})(ErrorType || (exports.ErrorType = ErrorType = {})); | ||
@@ -119,13 +113,2 @@ var AcrolinxError = /** @class */ (function (_super) { | ||
} | ||
else if (jsonBody && | ||
typeof jsonBody.error === 'string' && | ||
new RegExp(/realms\/.+?\/protocol\/openid-connect/).test(req.url)) { | ||
return new AcrolinxError({ | ||
detail: jsonBody.error_description || 'Unknown Auth Error', | ||
status: res.status, | ||
httpRequest: req, | ||
title: jsonBody.error, | ||
type: jsonBody.error, | ||
}); | ||
} | ||
else { | ||
@@ -132,0 +115,0 @@ return new AcrolinxError({ |
@@ -14,3 +14,2 @@ import { AddonId, AppAccessToken, AppAccessTokenResult, AppAccessTokenValidationResult } from './addons'; | ||
import { isSigninLinksResult, PollMoreResult, SignInInteractiveOptions, SigninLinksResult, SigninOptions, SigninPollResult, SigninResult, SigninSuccessData, SigninSuccessResult } from './signin'; | ||
import { DeviceAuthResponse, DeviceSignInSuccessResponse, DeviceSignInOptions, DeviceSignInOptionsInteractive } from './signin-device-grant'; | ||
import { User } from './user'; | ||
@@ -52,3 +51,2 @@ export * from './dictionary'; | ||
additionalFetchProperties?: any; | ||
additionalHeaders?: StringMap; | ||
fetch?: typeof fetch; | ||
@@ -89,11 +87,3 @@ /** | ||
signInWithSSO(genericToken: string, username: string): Promise<SigninSuccessResult>; | ||
signInWithHeaders(): Promise<SigninSuccessResult>; | ||
singInInteractive(opts: SignInInteractiveOptions): Promise<SigninSuccessData>; | ||
private fetchLoginInfo; | ||
private fetchDeviceGrantUserAction; | ||
deviceAuthSignIn(opts: DeviceSignInOptions): Promise<DeviceAuthResponse | DeviceSignInSuccessResponse>; | ||
private verifyTokenIsValid; | ||
deviceAuthSignInInteractive(opts: DeviceSignInOptionsInteractive): Promise<DeviceSignInSuccessResponse>; | ||
private fetchTokenKeyCloak; | ||
pollDeviceSignInCompletion(clientId: string, verificationResult: DeviceAuthResponse): Promise<DeviceSignInSuccessResponse>; | ||
signin(options?: SigninOptions): Promise<SigninResult>; | ||
@@ -100,0 +90,0 @@ pollForSignin(signinLinks: SigninLinksResult, lastPollResult?: PollMoreResult): Promise<SigninPollResult>; |
@@ -94,7 +94,5 @@ "use strict"; | ||
Object.defineProperty(exports, "isSigninLinksResult", { enumerable: true, get: function () { return signin_1.isSigninLinksResult; } }); | ||
var signin_device_grant_1 = require("./signin-device-grant"); | ||
var fetch_1 = require("./utils/fetch"); | ||
var logging = require("./utils/logging"); | ||
var mixed_utils_1 = require("./utils/mixed-utils"); | ||
var jwt_decode_1 = require("jwt-decode"); | ||
__exportStar(require("./dictionary"), exports); | ||
@@ -161,34 +159,2 @@ __exportStar(require("./extraction"), exports); | ||
}; | ||
AcrolinxEndpoint.prototype.signInWithHeaders = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var authHeader, jwtToken, decodedToken, username, password; | ||
var _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
if (!this.props.additionalHeaders) { | ||
throw new errors_1.AcrolinxError({ | ||
type: errors_1.ErrorType.SSO, | ||
title: 'Headers missing', | ||
detail: 'Additional headers must be provided', | ||
}); | ||
} | ||
authHeader = this.props.additionalHeaders['Authorization']; | ||
if (!authHeader) { | ||
throw new errors_1.AcrolinxError({ | ||
type: errors_1.ErrorType.SSO, | ||
title: 'Headers missing', | ||
detail: 'Additional headers must be provided', | ||
}); | ||
} | ||
jwtToken = (_a = authHeader.split(' ')) === null || _a === void 0 ? void 0 : _a[1]; | ||
decodedToken = (0, jwt_decode_1.jwtDecode)(jwtToken); | ||
username = decodedToken.preferred_username; | ||
password = decodedToken.genericPassword; | ||
return [4 /*yield*/, this.signInWithSSO(password, username)]; | ||
case 1: return [2 /*return*/, _b.sent()]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.singInInteractive = function (opts) { | ||
@@ -211,166 +177,2 @@ return __awaiter(this, void 0, void 0, function () { | ||
}; | ||
AcrolinxEndpoint.prototype.fetchLoginInfo = function () { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, (0, fetch_1.fetchJson)((0, fetch_1.getUrlOfPath)(this.props, '/int-service/api/v1/discovery'), this.props, { | ||
method: 'GET', | ||
})]; | ||
case 1: return [2 /*return*/, _a.sent()]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.fetchDeviceGrantUserAction = function (deviceGrantUrl, clientId) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, (0, fetch_1.fetchJson)(deviceGrantUrl, this.props, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: new URLSearchParams({ | ||
client_id: clientId, | ||
}), | ||
})]; | ||
case 1: return [2 /*return*/, _a.sent()]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.deviceAuthSignIn = function (opts) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var tenantId, intSeriveDiscovery, clientId, tokenValidationResult, deviceAuthResponse; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
tenantId = (0, signin_device_grant_1.getTenantId)(this.props.acrolinxUrl, opts); | ||
return [4 /*yield*/, this.fetchLoginInfo()]; | ||
case 1: | ||
intSeriveDiscovery = _a.sent(); | ||
clientId = (0, signin_device_grant_1.getClientId)(opts); | ||
return [4 /*yield*/, this.verifyTokenIsValid((0, signin_device_grant_1.generateTokenUrl)(intSeriveDiscovery, tenantId), clientId, opts.refreshToken)]; | ||
case 2: | ||
tokenValidationResult = _a.sent(); | ||
if (tokenValidationResult) { | ||
return [2 /*return*/, tokenValidationResult]; | ||
} | ||
return [4 /*yield*/, this.fetchDeviceGrantUserAction((0, signin_device_grant_1.generateDeviceAuthUrl)(intSeriveDiscovery, tenantId), clientId)]; | ||
case 3: | ||
deviceAuthResponse = _a.sent(); | ||
return [2 /*return*/, (0, signin_device_grant_1.tidyKeyCloakDeviceAuthResponse)((0, signin_device_grant_1.generateTokenUrl)(intSeriveDiscovery, tenantId), deviceAuthResponse)]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.verifyTokenIsValid = function (url, clientId, refreshToken) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var response, error_1, acrolinxError; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 2, , 3]); | ||
return [4 /*yield*/, this.fetchTokenKeyCloak(url, new URLSearchParams({ | ||
grant_type: 'refresh_token', | ||
client_id: clientId, | ||
refresh_token: refreshToken !== null && refreshToken !== void 0 ? refreshToken : '', | ||
}))]; | ||
case 1: | ||
response = _a.sent(); | ||
return [2 /*return*/, (0, signin_device_grant_1.tidyKeyCloakSuccessResponse)(response)]; | ||
case 2: | ||
error_1 = _a.sent(); | ||
acrolinxError = error_1; | ||
if (acrolinxError.type === errors_1.ErrorType.InvalidGrant) { | ||
console.log('Refresh token invalid, fetching new device code'); | ||
return [2 /*return*/, undefined]; | ||
} | ||
else { | ||
throw error_1; | ||
} | ||
return [3 /*break*/, 3]; | ||
case 3: return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.deviceAuthSignInInteractive = function (opts) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var result, clientId; | ||
var _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: return [4 /*yield*/, this.deviceAuthSignIn(opts)]; | ||
case 1: | ||
result = _b.sent(); | ||
clientId = (_a = opts.clientId) !== null && _a !== void 0 ? _a : 'device-sign-in'; | ||
if ((0, signin_device_grant_1.isSignInDeviceGrantSuccess)(result)) { | ||
return [2 /*return*/, result]; | ||
} | ||
opts.onDeviceGrantUserAction(result); | ||
return [4 /*yield*/, this.pollDeviceSignInCompletion(clientId, result)]; | ||
case 2: return [2 /*return*/, _b.sent()]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.fetchTokenKeyCloak = function (url, urlSearchParams) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, (0, fetch_1.fetchJson)(url, this.props, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: urlSearchParams, | ||
})]; | ||
case 1: return [2 /*return*/, _a.sent()]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.pollDeviceSignInCompletion = function (clientId, verificationResult) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var startTime, response, error_2, acrolinxError; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
startTime = Date.now(); | ||
_a.label = 1; | ||
case 1: | ||
if (!(Date.now() < startTime + verificationResult.expiresInSeconds * 1000)) return [3 /*break*/, 7]; | ||
return [4 /*yield*/, (0, mixed_utils_1.waitMs)(verificationResult.pollingIntervalInSeconds * 1000)]; | ||
case 2: | ||
_a.sent(); | ||
_a.label = 3; | ||
case 3: | ||
_a.trys.push([3, 5, , 6]); | ||
return [4 /*yield*/, this.fetchTokenKeyCloak(verificationResult.pollingUrl, new URLSearchParams({ | ||
grant_type: 'urn:ietf:params:oauth:grant-type:device_code', | ||
device_code: verificationResult.deviceCode, | ||
client_id: clientId, | ||
}))]; | ||
case 4: | ||
response = _a.sent(); | ||
return [2 /*return*/, (0, signin_device_grant_1.tidyKeyCloakSuccessResponse)(response)]; | ||
case 5: | ||
error_2 = _a.sent(); | ||
acrolinxError = error_2; | ||
if (acrolinxError.type === errors_1.ErrorType.AuthorizationPending) { | ||
console.log(acrolinxError.detail); | ||
return [3 /*break*/, 1]; | ||
} | ||
throw error_2; | ||
case 6: return [3 /*break*/, 1]; | ||
case 7: throw new errors_1.AcrolinxError({ | ||
type: errors_1.ErrorType.SigninTimedOut, | ||
title: 'Interactive device grant sign-in time out', | ||
detail: "Interactive device grant sign-in has timed out by client (".concat(Date.now() - startTime, " > ").concat(verificationResult.expiresInSeconds, " ms)."), | ||
}); | ||
} | ||
}); | ||
}); | ||
}; | ||
AcrolinxEndpoint.prototype.signin = function () { | ||
@@ -565,3 +367,3 @@ return __awaiter(this, arguments, void 0, function (options) { | ||
return [2 /*return*/, (0, fetch_1.fetchJson)(url, this.props, { | ||
headers: __assign(__assign(__assign({}, (0, headers_1.getCommonHeaders)(this.props, accessToken, opts.serviceType)), opts.headers), this.props.additionalHeaders), | ||
headers: __assign(__assign({}, (0, headers_1.getCommonHeaders)(this.props, accessToken, opts.serviceType)), opts.headers), | ||
})]; | ||
@@ -578,3 +380,3 @@ }); | ||
return [2 /*return*/, (0, fetch_1.fetchWithProps)(url, this.props, { | ||
headers: __assign(__assign(__assign({}, (0, headers_1.getCommonHeaders)(this.props, accessToken, opts.serviceType)), opts.headers), this.props.additionalHeaders), | ||
headers: __assign(__assign({}, (0, headers_1.getCommonHeaders)(this.props, accessToken, opts.serviceType)), opts.headers), | ||
}).then(function (res) { return (0, fetch_1.handleExpectedTextResponse)(httpRequest, res); }, function (error) { return (0, errors_1.wrapFetchError)(httpRequest, error); })]; | ||
@@ -698,3 +500,3 @@ }); | ||
return [2 /*return*/, (0, fetch_1.fetchJson)(url, this.props, { | ||
headers: __assign(__assign(__assign({}, (0, headers_1.getCommonHeaders)(this.props, accessToken, opts.serviceType)), opts.headers), this.props.additionalHeaders), | ||
headers: __assign(__assign({}, (0, headers_1.getCommonHeaders)(this.props, accessToken, opts.serviceType)), opts.headers), | ||
method: 'DELETE', | ||
@@ -701,0 +503,0 @@ })]; |
@@ -201,3 +201,3 @@ "use strict"; | ||
body: JSON.stringify(body), | ||
headers: __assign(__assign(__assign({}, (0, headers_1.getCommonHeaders)(props, accessToken, serviceType)), headers), props.additionalHeaders), | ||
headers: __assign(__assign({}, (0, headers_1.getCommonHeaders)(props, accessToken, serviceType)), headers), | ||
method: method, | ||
@@ -204,0 +204,0 @@ })]; |
@@ -45,9 +45,6 @@ "use strict"; | ||
dotenv.config(); | ||
var ACROLINX_ONE_SERVER_URL = process.env.ACROLINX_ONE_SERVER_URL || ''; | ||
var KEYCLOAK_TENANT_ID = process.env.KEYCLOAK_TENANT_ID; | ||
var KEYCLOAK_CLIENT_ID = process.env.KEYCLOAK_CLIENT_ID || ''; | ||
var KEYCLOAK_REFRESH_TOKEN = process.env.KEYCLOAK_REFRESH_TOKEN; | ||
var KEYCLOAK_ACCESS_TOKEN = process.env.KEYCLOAK_ACCESS_TOKEN; | ||
var TEST_SERVER_URL = process.env.TEST_SERVER_URL || ''; /* Add here your own test server URL */ | ||
var ACROLINX_API_TOKEN = process.env.ACROLINX_API_TOKEN || ''; | ||
exports.ACROLINX_DEV_SIGNATURE = process.env.ACROLINX_DEV_SIGNATURE; | ||
function createEndpoint(acrolinxUrl, headers) { | ||
function createEndpoint(acrolinxUrl) { | ||
return new src_1.AcrolinxEndpoint({ | ||
@@ -60,3 +57,2 @@ acrolinxUrl: acrolinxUrl, | ||
}, | ||
additionalHeaders: headers, | ||
}); | ||
@@ -66,127 +62,14 @@ } | ||
var endpoint; | ||
var verifyDeviceGrantUserActionInfo = function (deviceGrantUserAction) { | ||
expect(deviceGrantUserAction.verificationUrl).toBeDefined(); | ||
expect(deviceGrantUserAction.verificationUrlComplete).toBeDefined(); | ||
expect(deviceGrantUserAction.userCode).toBeDefined(); | ||
expect(deviceGrantUserAction.pollingUrl).toBeDefined(); | ||
expect(deviceGrantUserAction.deviceCode).toBeDefined(); | ||
expect(deviceGrantUserAction.expiresInSeconds).toBeDefined(); | ||
expect(deviceGrantUserAction.pollingIntervalInSeconds).toBeDefined(); | ||
}; | ||
var verifySignInDeviceGrantSuccess = function (signInDeviceGrant) { | ||
expect(signInDeviceGrant.accessToken).toBeDefined(); | ||
expect(signInDeviceGrant.refreshToken).toBeDefined(); | ||
expect(signInDeviceGrant.refreshToken).not.toEqual(KEYCLOAK_REFRESH_TOKEN); | ||
expect(signInDeviceGrant.accessTokenExpiryInSeconds).toBeDefined(); | ||
expect(signInDeviceGrant.refreshTokenExpiryInSeconds).toBeDefined(); | ||
expect(signInDeviceGrant.scope).toBeDefined(); | ||
expect(signInDeviceGrant.sessionState).toBeDefined(); | ||
expect(signInDeviceGrant.tokenType).toBeDefined(); | ||
expect(signInDeviceGrant.tokenType.toLowerCase()).toEqual('bearer'); | ||
}; | ||
beforeEach(function () { | ||
endpoint = createEndpoint(ACROLINX_ONE_SERVER_URL); | ||
endpoint = createEndpoint(TEST_SERVER_URL); | ||
}); | ||
describe('Sign in with device grant', function () { | ||
it('get device verification url', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var deviceGrantUserAction; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
})]; | ||
case 1: | ||
deviceGrantUserAction = (_a.sent()); | ||
console.log(deviceGrantUserAction); | ||
verifyDeviceGrantUserActionInfo(deviceGrantUserAction); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }); | ||
// This test requires valid refresh token | ||
it.skip('validate refresh token', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var signInDeviceGrant; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
refreshToken: KEYCLOAK_REFRESH_TOKEN, | ||
})]; | ||
case 1: | ||
signInDeviceGrant = (_a.sent()); | ||
console.log(signInDeviceGrant); | ||
verifySignInDeviceGrantSuccess(signInDeviceGrant); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }); | ||
it('invalid refresh token triggers new device grant', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var deviceGrantUserAction; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
refreshToken: 'invalid', | ||
})]; | ||
case 1: | ||
deviceGrantUserAction = (_a.sent()); | ||
console.log(deviceGrantUserAction); | ||
verifyDeviceGrantUserActionInfo(deviceGrantUserAction); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }); | ||
it('poll for sign in result', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var deviceGrantUserAction; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
})]; | ||
case 1: | ||
deviceGrantUserAction = (_a.sent()); | ||
console.log(deviceGrantUserAction); | ||
verifyDeviceGrantUserActionInfo(deviceGrantUserAction); | ||
deviceGrantUserAction.expiresInSeconds = 10; | ||
return [4 /*yield*/, expect(endpoint.pollDeviceSignInCompletion(KEYCLOAK_CLIENT_ID, deviceGrantUserAction)).rejects.toThrow()]; | ||
case 2: | ||
_a.sent(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }, 30000); | ||
// This test requires valid keycloak access token | ||
it.skip('sign-in with auth header', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var headers, ep, result; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
headers = { | ||
Authorization: "Bearer ".concat(KEYCLOAK_ACCESS_TOKEN), | ||
}; | ||
ep = createEndpoint(ACROLINX_ONE_SERVER_URL, headers); | ||
return [4 /*yield*/, ep.signInWithHeaders()]; | ||
case 1: | ||
result = _a.sent(); | ||
expect(result).toBeDefined(); | ||
expect(result.data.accessToken).toBeDefined(); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
}); }); | ||
}); | ||
describe.skip('AI Service Integration Tests', function () { | ||
// This tests requires valid keycloak access token | ||
it('getAiFeatures returns whether certain AI features are enabled', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var ep, aiService, aiFeatures; | ||
var aiService, aiFeatures; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
ep = createEndpoint(ACROLINX_ONE_SERVER_URL); | ||
aiService = new ai_service_1.AIService(ep); | ||
return [4 /*yield*/, aiService.getAiFeatures(KEYCLOAK_ACCESS_TOKEN)]; | ||
aiService = new ai_service_1.AIService(endpoint); | ||
return [4 /*yield*/, aiService.getAiFeatures(ACROLINX_API_TOKEN)]; | ||
case 1: | ||
@@ -201,12 +84,8 @@ aiFeatures = _a.sent(); | ||
it('check if the ai service is activated', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var headers, ep, aiService, aiResult; | ||
var aiService, aiResult; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
headers = { | ||
Authorization: "Bearer ".concat(KEYCLOAK_ACCESS_TOKEN), | ||
}; | ||
ep = createEndpoint(ACROLINX_ONE_SERVER_URL, headers); | ||
aiService = new ai_service_1.AIService(ep); | ||
return [4 /*yield*/, aiService.getAIEnabled(KEYCLOAK_ACCESS_TOKEN)]; | ||
aiService = new ai_service_1.AIService(endpoint); | ||
return [4 /*yield*/, aiService.getAIEnabled(ACROLINX_API_TOKEN)]; | ||
case 1: | ||
@@ -222,11 +101,7 @@ aiResult = _a.sent(); | ||
it('get a chat completion from the ai service', function () { return __awaiter(void 0, void 0, void 0, function () { | ||
var headers, ep, aiService, aiResult; | ||
var aiService, aiResult; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
headers = { | ||
Authorization: "Bearer ".concat(KEYCLOAK_ACCESS_TOKEN), | ||
}; | ||
ep = createEndpoint(ACROLINX_ONE_SERVER_URL, headers); | ||
aiService = new ai_service_1.AIService(ep); | ||
aiService = new ai_service_1.AIService(endpoint); | ||
return [4 /*yield*/, aiService.getAIChatCompletion({ | ||
@@ -239,3 +114,3 @@ issue: { | ||
targetUuid: '123e4567-e89b-12d3-a456-426614174000', | ||
}, KEYCLOAK_ACCESS_TOKEN)]; | ||
}, ACROLINX_API_TOKEN)]; | ||
case 1: | ||
@@ -242,0 +117,0 @@ aiResult = _a.sent(); |
{ | ||
"name": "@acrolinx/sdk", | ||
"version": "1.2.0-dev.10", | ||
"version": "1.2.0-dev.11", | ||
"description": "Acrolinx JavaScript SDK for the Acrolinx API", | ||
@@ -51,5 +51,5 @@ "license": "Apache-2.0", | ||
"@types/lodash": "^4.17.0", | ||
"@types/node": "^20.11.28", | ||
"@typescript-eslint/eslint-plugin": "^7.2.0", | ||
"@typescript-eslint/parser": "^7.2.0", | ||
"@types/node": "^20.12.7", | ||
"@typescript-eslint/eslint-plugin": "^7.6.0", | ||
"@typescript-eslint/parser": "^7.6.0", | ||
"ajv": "^8.12.0", | ||
@@ -63,3 +63,3 @@ "co-body": "^6.1.0", | ||
"eslint-plugin-prettier": "^5.1.3", | ||
"eslint-plugin-sonarjs": "^0.24.0", | ||
"eslint-plugin-sonarjs": "^0.25.1", | ||
"fetch-mock": "^9.11.0", | ||
@@ -72,9 +72,6 @@ "jest": "^29.7.0", | ||
"ts-jest": "^29.1.2", | ||
"typedoc": "0.25.12", | ||
"typescript": "5.4.2", | ||
"typedoc": "0.25.13", | ||
"typescript": "5.4.4", | ||
"typescript-json-schema": "^0.63.0" | ||
}, | ||
"dependencies": { | ||
"jwt-decode": "^4.0.0" | ||
} | ||
} |
@@ -48,8 +48,2 @@ /* | ||
LicenseLimitExceeded = 'licenseLimitExceeded', | ||
AuthorizationPending = 'authorization_pending', | ||
InvalidClient = 'invalid_client', | ||
RealmNotExist = 'Realm does not exist', | ||
InvalidGrant = 'invalid_grant', | ||
UnsuppotedGrantType = 'unsupported_grant_type', | ||
InavlidRequest = 'invalid_request', | ||
} | ||
@@ -154,14 +148,2 @@ | ||
}); | ||
} else if ( | ||
jsonBody && | ||
typeof jsonBody.error === 'string' && | ||
new RegExp(/realms\/.+?\/protocol\/openid-connect/).test(req.url) | ||
) { | ||
return new AcrolinxError({ | ||
detail: jsonBody.error_description || 'Unknown Auth Error', | ||
status: res.status, | ||
httpRequest: req, | ||
title: jsonBody.error, | ||
type: jsonBody.error as ErrorType, | ||
}); | ||
} else { | ||
@@ -168,0 +150,0 @@ return new AcrolinxError({ |
177
src/index.ts
@@ -87,19 +87,3 @@ /* | ||
} from './signin'; | ||
import { | ||
DeviceAuthResponse, | ||
DeviceAuthResponseRaw, | ||
IntServiceDiscovery, | ||
DeviceSignInSuccessResponse, | ||
DeviceSignInOptions, | ||
DeviceSignInOptionsInteractive, | ||
DeviceSignInSuccessResponseRaw, | ||
generateDeviceAuthUrl, | ||
generateTokenUrl, | ||
getClientId, | ||
getTenantId, | ||
isSignInDeviceGrantSuccess, | ||
tidyKeyCloakSuccessResponse, | ||
tidyKeyCloakDeviceAuthResponse, | ||
JWTAcrolinxPayload, | ||
} from './signin-device-grant'; | ||
import { User } from './user'; | ||
@@ -109,3 +93,2 @@ import { fetchJson, fetchWithProps, getUrlOfPath, handleExpectedTextResponse, post, put } from './utils/fetch'; | ||
import { waitMs } from './utils/mixed-utils'; | ||
import { jwtDecode } from 'jwt-decode'; | ||
@@ -181,3 +164,2 @@ export * from './dictionary'; | ||
additionalFetchProperties?: any; | ||
additionalHeaders?: StringMap; | ||
fetch?: typeof fetch; | ||
@@ -253,28 +235,2 @@ | ||
public async signInWithHeaders(): Promise<SigninSuccessResult> { | ||
if (!this.props.additionalHeaders) { | ||
throw new AcrolinxError({ | ||
type: ErrorType.SSO, | ||
title: 'Headers missing', | ||
detail: 'Additional headers must be provided', | ||
}); | ||
} | ||
const authHeader = this.props.additionalHeaders['Authorization']; | ||
if (!authHeader) { | ||
throw new AcrolinxError({ | ||
type: ErrorType.SSO, | ||
title: 'Headers missing', | ||
detail: 'Additional headers must be provided', | ||
}); | ||
} | ||
const jwtToken = authHeader.split(' ')?.[1]; | ||
const decodedToken = jwtDecode<JWTAcrolinxPayload>(jwtToken); | ||
const username = decodedToken.preferred_username; | ||
const password = decodedToken.genericPassword; | ||
return await this.signInWithSSO(password, username); | ||
} | ||
public async singInInteractive(opts: SignInInteractiveOptions): Promise<SigninSuccessData> { | ||
@@ -292,130 +248,2 @@ const signinResult = await this.signin({ accessToken: opts.accessToken }); | ||
private async fetchLoginInfo(): Promise<IntServiceDiscovery> { | ||
return await fetchJson(getUrlOfPath(this.props, '/int-service/api/v1/discovery'), this.props, { | ||
method: 'GET', | ||
}); | ||
} | ||
private async fetchDeviceGrantUserAction(deviceGrantUrl: string, clientId: string): Promise<DeviceAuthResponseRaw> { | ||
return await fetchJson(deviceGrantUrl, this.props, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: new URLSearchParams({ | ||
client_id: clientId, | ||
}), | ||
}); | ||
} | ||
public async deviceAuthSignIn(opts: DeviceSignInOptions): Promise<DeviceAuthResponse | DeviceSignInSuccessResponse> { | ||
const tenantId = getTenantId(this.props.acrolinxUrl, opts); | ||
const intSeriveDiscovery: IntServiceDiscovery = await this.fetchLoginInfo(); | ||
const clientId = getClientId(opts); | ||
const tokenValidationResult = await this.verifyTokenIsValid( | ||
generateTokenUrl(intSeriveDiscovery, tenantId), | ||
clientId, | ||
opts.refreshToken, | ||
); | ||
if (tokenValidationResult) { | ||
return tokenValidationResult; | ||
} | ||
const deviceAuthResponse = await this.fetchDeviceGrantUserAction( | ||
generateDeviceAuthUrl(intSeriveDiscovery, tenantId), | ||
clientId, | ||
); | ||
return tidyKeyCloakDeviceAuthResponse(generateTokenUrl(intSeriveDiscovery, tenantId), deviceAuthResponse); | ||
} | ||
private async verifyTokenIsValid( | ||
url: string, | ||
clientId: string, | ||
refreshToken: string | undefined, | ||
): Promise<DeviceSignInSuccessResponse | undefined> { | ||
try { | ||
const response = await this.fetchTokenKeyCloak( | ||
url, | ||
new URLSearchParams({ | ||
grant_type: 'refresh_token', | ||
client_id: clientId, | ||
refresh_token: refreshToken ?? '', | ||
}), | ||
); | ||
return tidyKeyCloakSuccessResponse(response); | ||
} catch (error: unknown) { | ||
const acrolinxError = error as AcrolinxError; | ||
if ((acrolinxError.type as ErrorType) === ErrorType.InvalidGrant) { | ||
console.log('Refresh token invalid, fetching new device code'); | ||
return undefined; | ||
} else { | ||
throw error; | ||
} | ||
} | ||
} | ||
public async deviceAuthSignInInteractive(opts: DeviceSignInOptionsInteractive): Promise<DeviceSignInSuccessResponse> { | ||
const result = await this.deviceAuthSignIn(opts); | ||
const clientId = opts.clientId ?? 'device-sign-in'; | ||
if (isSignInDeviceGrantSuccess(result)) { | ||
return result; | ||
} | ||
opts.onDeviceGrantUserAction(result); | ||
return await this.pollDeviceSignInCompletion(clientId, result); | ||
} | ||
private async fetchTokenKeyCloak( | ||
url: string, | ||
urlSearchParams: URLSearchParams, | ||
): Promise<DeviceSignInSuccessResponseRaw> { | ||
return await fetchJson(url, this.props, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: urlSearchParams, | ||
}); | ||
} | ||
public async pollDeviceSignInCompletion( | ||
clientId: string, | ||
verificationResult: DeviceAuthResponse, | ||
): Promise<DeviceSignInSuccessResponse> { | ||
const startTime = Date.now(); | ||
while (Date.now() < startTime + verificationResult.expiresInSeconds * 1000) { | ||
await waitMs(verificationResult.pollingIntervalInSeconds * 1000); | ||
try { | ||
const response: DeviceSignInSuccessResponseRaw = await this.fetchTokenKeyCloak( | ||
verificationResult.pollingUrl, | ||
new URLSearchParams({ | ||
grant_type: 'urn:ietf:params:oauth:grant-type:device_code', | ||
device_code: verificationResult.deviceCode, | ||
client_id: clientId, | ||
}), | ||
); | ||
return tidyKeyCloakSuccessResponse(response); | ||
} catch (error: unknown) { | ||
const acrolinxError = error as AcrolinxError; | ||
if ((acrolinxError.type as ErrorType) === ErrorType.AuthorizationPending) { | ||
console.log(acrolinxError.detail); | ||
continue; | ||
} | ||
throw error; | ||
} | ||
} | ||
throw new AcrolinxError({ | ||
type: ErrorType.SigninTimedOut, | ||
title: 'Interactive device grant sign-in time out', | ||
detail: `Interactive device grant sign-in has timed out by client (${Date.now() - startTime} > ${ | ||
verificationResult.expiresInSeconds | ||
} ms).`, | ||
}); | ||
} | ||
public async signin(options: SigninOptions = {}): Promise<SigninResult> { | ||
@@ -621,3 +449,2 @@ return post<SigninResult>('/api/v1/auth/sign-ins', {}, getSigninRequestHeaders(options), this.props); | ||
...opts.headers, | ||
...this.props.additionalHeaders, | ||
}, | ||
@@ -637,3 +464,2 @@ }); | ||
...opts.headers, | ||
...this.props.additionalHeaders, | ||
}, | ||
@@ -755,3 +581,2 @@ }).then( | ||
...opts.headers, | ||
...this.props.additionalHeaders, | ||
}, | ||
@@ -758,0 +583,0 @@ method: 'DELETE', |
@@ -124,3 +124,3 @@ /* | ||
body: JSON.stringify(body), | ||
headers: { ...getCommonHeaders(props, accessToken, serviceType), ...headers, ...props.additionalHeaders }, | ||
headers: { ...getCommonHeaders(props, accessToken, serviceType), ...headers }, | ||
method, | ||
@@ -127,0 +127,0 @@ }); |
@@ -1,3 +0,2 @@ | ||
import { DeviceAuthResponse, DeviceSignInSuccessResponse } from '../../src/signin-device-grant'; | ||
import { AcrolinxEndpoint, CommonIssue, DEVELOPMENT_SIGNATURE, StringMap } from '../../src'; | ||
import { AcrolinxEndpoint, CommonIssue, DEVELOPMENT_SIGNATURE } from '../../src'; | ||
import * as dotenv from 'dotenv'; | ||
@@ -9,10 +8,8 @@ import 'cross-fetch/polyfill'; | ||
const ACROLINX_ONE_SERVER_URL = process.env.ACROLINX_ONE_SERVER_URL || ''; | ||
const KEYCLOAK_TENANT_ID = process.env.KEYCLOAK_TENANT_ID; | ||
const KEYCLOAK_CLIENT_ID = process.env.KEYCLOAK_CLIENT_ID || ''; | ||
const KEYCLOAK_REFRESH_TOKEN = process.env.KEYCLOAK_REFRESH_TOKEN; | ||
const KEYCLOAK_ACCESS_TOKEN = process.env.KEYCLOAK_ACCESS_TOKEN; | ||
const TEST_SERVER_URL = process.env.TEST_SERVER_URL || ''; /* Add here your own test server URL */ | ||
const ACROLINX_API_TOKEN = process.env.ACROLINX_API_TOKEN || ''; | ||
export const ACROLINX_DEV_SIGNATURE = process.env.ACROLINX_DEV_SIGNATURE; | ||
function createEndpoint(acrolinxUrl: string, headers?: StringMap) { | ||
function createEndpoint(acrolinxUrl: string) { | ||
return new AcrolinxEndpoint({ | ||
@@ -25,3 +22,2 @@ acrolinxUrl, | ||
}, | ||
additionalHeaders: headers, | ||
}); | ||
@@ -33,87 +29,6 @@ } | ||
const verifyDeviceGrantUserActionInfo = (deviceGrantUserAction: DeviceAuthResponse) => { | ||
expect(deviceGrantUserAction.verificationUrl).toBeDefined(); | ||
expect(deviceGrantUserAction.verificationUrlComplete).toBeDefined(); | ||
expect(deviceGrantUserAction.userCode).toBeDefined(); | ||
expect(deviceGrantUserAction.pollingUrl).toBeDefined(); | ||
expect(deviceGrantUserAction.deviceCode).toBeDefined(); | ||
expect(deviceGrantUserAction.expiresInSeconds).toBeDefined(); | ||
expect(deviceGrantUserAction.pollingIntervalInSeconds).toBeDefined(); | ||
}; | ||
const verifySignInDeviceGrantSuccess = (signInDeviceGrant: DeviceSignInSuccessResponse) => { | ||
expect(signInDeviceGrant.accessToken).toBeDefined(); | ||
expect(signInDeviceGrant.refreshToken).toBeDefined(); | ||
expect(signInDeviceGrant.refreshToken).not.toEqual(KEYCLOAK_REFRESH_TOKEN); | ||
expect(signInDeviceGrant.accessTokenExpiryInSeconds).toBeDefined(); | ||
expect(signInDeviceGrant.refreshTokenExpiryInSeconds).toBeDefined(); | ||
expect(signInDeviceGrant.scope).toBeDefined(); | ||
expect(signInDeviceGrant.sessionState).toBeDefined(); | ||
expect(signInDeviceGrant.tokenType).toBeDefined(); | ||
expect(signInDeviceGrant.tokenType.toLowerCase()).toEqual('bearer'); | ||
}; | ||
beforeEach(() => { | ||
endpoint = createEndpoint(ACROLINX_ONE_SERVER_URL); | ||
endpoint = createEndpoint(TEST_SERVER_URL); | ||
}); | ||
describe('Sign in with device grant', () => { | ||
it('get device verification url', async () => { | ||
const deviceGrantUserAction = (await endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
})) as DeviceAuthResponse; | ||
console.log(deviceGrantUserAction); | ||
verifyDeviceGrantUserActionInfo(deviceGrantUserAction); | ||
}); | ||
// This test requires valid refresh token | ||
it.skip('validate refresh token', async () => { | ||
const signInDeviceGrant = (await endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
refreshToken: KEYCLOAK_REFRESH_TOKEN, | ||
})) as DeviceSignInSuccessResponse; | ||
console.log(signInDeviceGrant); | ||
verifySignInDeviceGrantSuccess(signInDeviceGrant); | ||
}); | ||
it('invalid refresh token triggers new device grant', async () => { | ||
const deviceGrantUserAction = (await endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
refreshToken: 'invalid', | ||
})) as DeviceAuthResponse; | ||
console.log(deviceGrantUserAction); | ||
verifyDeviceGrantUserActionInfo(deviceGrantUserAction); | ||
}); | ||
it('poll for sign in result', async () => { | ||
const deviceGrantUserAction = (await endpoint.deviceAuthSignIn({ | ||
tenantId: KEYCLOAK_TENANT_ID, | ||
clientId: KEYCLOAK_CLIENT_ID, | ||
})) as DeviceAuthResponse; | ||
console.log(deviceGrantUserAction); | ||
verifyDeviceGrantUserActionInfo(deviceGrantUserAction); | ||
deviceGrantUserAction.expiresInSeconds = 10; | ||
await expect(endpoint.pollDeviceSignInCompletion(KEYCLOAK_CLIENT_ID, deviceGrantUserAction)).rejects.toThrow(); | ||
}, 30000); | ||
// This test requires valid keycloak access token | ||
it.skip('sign-in with auth header', async () => { | ||
const headers: StringMap = { | ||
Authorization: `Bearer ${KEYCLOAK_ACCESS_TOKEN!}`, | ||
}; | ||
const ep = createEndpoint(ACROLINX_ONE_SERVER_URL, headers); | ||
const result = await ep.signInWithHeaders(); | ||
expect(result).toBeDefined(); | ||
expect(result.data.accessToken).toBeDefined(); | ||
}); | ||
}); | ||
describe.skip('AI Service Integration Tests', () => { | ||
@@ -123,6 +38,5 @@ // This tests requires valid keycloak access token | ||
it('getAiFeatures returns whether certain AI features are enabled', async () => { | ||
const ep = createEndpoint(ACROLINX_ONE_SERVER_URL); | ||
const aiService = new AIService(ep); | ||
const aiService = new AIService(endpoint); | ||
const aiFeatures = await aiService.getAiFeatures(KEYCLOAK_ACCESS_TOKEN!); | ||
const aiFeatures = await aiService.getAiFeatures(ACROLINX_API_TOKEN); | ||
@@ -134,9 +48,5 @@ expect(typeof aiFeatures.ai).toEqual('boolean'); | ||
it('check if the ai service is activated', async () => { | ||
const headers: StringMap = { | ||
Authorization: `Bearer ${KEYCLOAK_ACCESS_TOKEN!}`, | ||
}; | ||
const ep = createEndpoint(ACROLINX_ONE_SERVER_URL, headers); | ||
const aiService = new AIService(ep); | ||
const aiService = new AIService(endpoint); | ||
const aiResult = await aiService.getAIEnabled(KEYCLOAK_ACCESS_TOKEN!); | ||
const aiResult = await aiService.getAIEnabled(ACROLINX_API_TOKEN); | ||
expect(aiResult.tenant).toBeDefined(); | ||
@@ -148,7 +58,3 @@ expect(aiResult.value).toBeDefined(); | ||
it('get a chat completion from the ai service', async () => { | ||
const headers: StringMap = { | ||
Authorization: `Bearer ${KEYCLOAK_ACCESS_TOKEN!}`, | ||
}; | ||
const ep = createEndpoint(ACROLINX_ONE_SERVER_URL, headers); | ||
const aiService = new AIService(ep); | ||
const aiService = new AIService(endpoint); | ||
const aiResult = await aiService.getAIChatCompletion( | ||
@@ -164,3 +70,3 @@ { | ||
}, | ||
KEYCLOAK_ACCESS_TOKEN!, | ||
ACROLINX_API_TOKEN, | ||
); | ||
@@ -167,0 +73,0 @@ expect(aiResult.response).toBeDefined(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
0
11
646980
201
12641
- Removedjwt-decode@^4.0.0
- Removedjwt-decode@4.0.0(transitive)