@emilgroup/translation-sdk 1.0.1-beta.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/api.ts CHANGED
@@ -15,15 +15,17 @@
15
15
 
16
16
  import { Configuration } from './configuration';
17
17
  import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
18
+ // URLSearchParams not necessarily used
19
+ // @ts-ignore
20
+ import { URL, URLSearchParams } from 'url';
21
+ import FormData from 'form-data'
18
22
  // Some imports not used depending on template conditions
19
23
  // @ts-ignore
20
24
  import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
21
25
  // @ts-ignore
22
26
  import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';
23
- import { DefaultApi } from './api';
24
27
  import { TranslationApi } from './api';
25
28
 
26
29
 
27
- export * from './api/default-api';
28
30
  export * from './api/translation-api';
29
31
 
package/base.ts CHANGED
@@ -14,13 +14,20 @@
14
14
 
15
15
 
16
16
  import { Configuration } from "./configuration";
17
- import { defaultStorage } from "./common";
18
17
  // Some imports not used depending on template conditions
19
18
  // @ts-ignore
20
19
  import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
21
-
20
+ import * as fs from 'fs';
21
+ import * as path from 'path';
22
+ import * as os from 'os';
22
23
 
23
24
  export const BASE_PATH = "https://apiv2.emil.de".replace(/\/+$/, "");
25
+ const CONFIG_DIRECTORY = '.emil';
26
+ const CONFIG_FILENAME = 'credentials';
27
+ const KEY_USERNAME = 'emil_username';
28
+ const KEY_PASSWORD = 'emil_password';
29
+
30
+ const filePath = os.homedir() + path.sep + CONFIG_DIRECTORY + path.sep + CONFIG_FILENAME;
24
31
  /**
25
32
  *
26
33
  * @export
@@ -34,25 +41,13 @@ export const COLLECTION_FORMATS = {
34
41
 
35
42
  export interface LoginClass {
36
43
  accessToken: string;
37
- permissions: string;
38
- }
39
-
40
- export interface SwitchWorkspaceRequest {
41
- username: string;
42
- targetWorkspace: string;
43
- }
44
-
45
- export interface SwitchWorkspaceResponseClass {
46
- accessToken: string;
47
- permissions: string;
44
+ permissions: Array<string>;
48
45
  }
49
46
 
50
47
  export enum Environment {
51
48
  Production = 'https://apiv2.emil.de',
52
49
  Test = 'https://apiv2-test.emil.de',
53
- Staging = 'https://apiv2-staging.emil.de',
54
50
  Development = 'https://apiv2-dev.emil.de',
55
- ProductionZurich = 'https://eu-central-2.apiv2.emil.de',
56
51
  }
57
52
 
58
53
  let _retry_count = 0
@@ -72,14 +67,7 @@ export interface RequestArgs {
72
67
  options: AxiosRequestConfig;
73
68
  }
74
69
 
75
- interface TokenData {
76
- accessToken?: string;
77
- username?: string;
78
- permissions?: string;
79
- }
80
-
81
70
  const NETWORK_ERROR_MESSAGE = "Network Error";
82
- const TOKEN_DATA = 'APP_TOKEN';
83
71
 
84
72
  /**
85
73
  *
@@ -87,54 +75,80 @@ const TOKEN_DATA = 'APP_TOKEN';
87
75
  * @class BaseAPI
88
76
  */
89
77
  export class BaseAPI {
90
- protected configuration: Configuration | undefined;
91
- private tokenData?: TokenData;
92
-
93
- constructor(configuration?: Configuration,
94
- protected basePath: string = BASE_PATH,
95
- protected axios: AxiosInstance = globalAxios) {
96
-
97
- this.loadTokenData();
78
+ protected configuration: Configuration;
79
+ private username?: string;
80
+ private password?: string;
98
81
 
82
+ constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
99
83
  if (configuration) {
100
- const { accessToken } = this.tokenData;
101
84
  this.configuration = configuration;
102
85
  this.basePath = configuration.basePath || this.basePath;
103
-
104
- // Use config token if provided, otherwise use tokenData token
105
- const configToken = this.configuration.accessToken;
106
- const storedToken = accessToken ? `Bearer ${accessToken}` : '';
107
- this.configuration.accessToken = configToken || storedToken;
108
86
  } else {
109
- const { accessToken, username } = this.tokenData;
110
-
111
87
  this.configuration = new Configuration({
112
88
  basePath: this.basePath,
113
- accessToken: accessToken ? `Bearer ${accessToken}` : '',
114
- username,
115
89
  });
116
90
  }
117
91
 
118
92
  this.attachInterceptor(axios);
119
93
  }
120
94
 
121
- selectEnvironment(env: Environment) {
122
- this.selectBasePath(env);
95
+ async initialize(env: Environment = Environment.Production) {
96
+ this.configuration.basePath = env;
97
+
98
+ await this.loadCredentials();
99
+
100
+ if (this.username) {
101
+ await this.authorize(this.username, this.password);
102
+ this.password = null; // to avoid keeping password loaded in memory.
103
+ }
104
+ }
105
+
106
+ private async loadCredentials() {
107
+ try {
108
+ await this.readConfigFile();
109
+ } catch (error) {
110
+ console.warn(`No credentials file found. Check that ${filePath} exists.`);
111
+ }
112
+
113
+ this.readEnvVariables();
114
+
115
+ if (!this.username) {
116
+ console.info(`No credentials found in credentials file or environment variables. Either provide some or use
117
+ authorize() function.`);
118
+ }
123
119
  }
124
120
 
125
- selectBasePath(path: string) {
126
- this.configuration.basePath = path;
121
+ private async readConfigFile() {
122
+ const file = await fs.promises.readFile(filePath, 'utf-8');
123
+
124
+ const lines = file.split(os.EOL)
125
+ .filter(Boolean);
126
+
127
+ lines.forEach((line: string) => {
128
+ if (line.startsWith(KEY_USERNAME)) {
129
+ this.username = line.length > KEY_USERNAME.length + 1 ? line.substring(KEY_USERNAME.length + 1) : '';
130
+ } else if (line.startsWith(KEY_PASSWORD)) {
131
+ this.password = line.length > KEY_PASSWORD.length + 1 ? line.substring(KEY_PASSWORD.length + 1) : '';
132
+ }
133
+ });
127
134
  }
128
135
 
129
- getPermissions(): Array<string> {
130
- if (!this.tokenData?.permissions) {
131
- return [];
136
+ private readEnvVariables(): boolean {
137
+ if (process.env.EMIL_USERNAME) {
138
+ this.username = process.env.EMIL_USERNAME;
139
+ this.password = process.env.EMIL_PASSWORD || '';
140
+
141
+ return true;
132
142
  }
133
143
 
134
- return this.tokenData.permissions.split(',');
144
+ return false;
145
+ }
146
+
147
+ selectEnvironment(env: Environment) {
148
+ this.configuration.basePath = env;
135
149
  }
136
150
 
137
- async authorize(username: string, password: string, targetWorkspace?: string): Promise<void> {
151
+ async authorize(username: string, password: string): Promise<void> {
138
152
  const options: AxiosRequestConfig = {
139
153
  method: 'POST',
140
154
  url: `${this.configuration.basePath}/authservice/v1/login`,
@@ -148,100 +162,50 @@ export class BaseAPI {
148
162
 
149
163
  const response = await globalAxios.request<LoginClass>(options);
150
164
 
151
- const { data: { accessToken, permissions } } = response;
152
-
165
+ const { data: { accessToken } } = response;
153
166
  this.configuration.username = username;
154
167
  this.configuration.accessToken = `Bearer ${accessToken}`;
155
- this.tokenData.username = username;
156
- this.tokenData.accessToken = accessToken;
157
- this.tokenData.permissions = permissions;
158
168
 
159
- // Switch workspace if provided
160
- if (targetWorkspace) {
161
- await this.switchWorkspace(targetWorkspace);
162
- } else {
163
- // Only store if no workspace switch (since switchWorkspace will store after switching)
164
- this.storeTokenData({
165
- ...this.tokenData
166
- });
167
- }
169
+ const refreshToken = this.extractRefreshToken(response)
170
+ this.configuration.refreshToken = refreshToken;
168
171
  }
169
172
 
170
- async switchWorkspace(targetWorkspace: string): Promise<void> {
171
- const options: AxiosRequestConfig = {
172
- method: 'POST',
173
- url: `${this.configuration.basePath}/authservice/v1/workspaces/switch`,
174
- headers: {
175
- 'Content-Type': 'application/json',
176
- 'Authorization': `Bearer ${this.configuration.accessToken}`,
177
- },
178
- data: {
179
- username: this.configuration.username,
180
- targetWorkspace,
181
- } as SwitchWorkspaceRequest,
182
- withCredentials: true,
183
- };
184
-
185
- const response = await globalAxios.request<SwitchWorkspaceResponseClass>(options);
186
-
187
- const { data: { accessToken, permissions } } = response;
173
+ async refreshTokenInternal(): Promise<string> {
174
+ const { username, refreshToken } = this.configuration;
188
175
 
189
- this.configuration.accessToken = `Bearer ${accessToken}`;
190
- this.tokenData.accessToken = accessToken;
191
- this.tokenData.permissions = permissions;
192
176
 
193
- this.storeTokenData({
194
- ...this.tokenData
195
- });
196
- }
197
-
198
- async refreshTokenInternal(): Promise<LoginClass> {
199
- const { username } = this.configuration;
200
-
201
- if (!username) {
202
- throw new Error('Failed to refresh token.');
177
+ if (!username || !refreshToken) {
178
+ return '';
203
179
  }
204
180
 
205
- const refreshTokenAxios = globalAxios.create()
206
- refreshTokenAxios.interceptors.response.use(response => {
207
- const { permissions } = response.data;
208
-
209
- this.tokenData.permissions = permissions;
210
- this.storeTokenData(this.tokenData);
211
-
212
- return response;
213
- })
214
-
215
181
  const options: AxiosRequestConfig = {
216
182
  method: 'POST',
217
183
  url: `${this.configuration.basePath}/authservice/v1/refresh-token`,
218
184
  headers: {
219
185
  'Content-Type': 'application/json',
186
+ Cookie: refreshToken,
220
187
  },
221
188
  data: { username: username },
222
189
  withCredentials: true,
223
190
  };
224
191
 
225
- const response = await refreshTokenAxios.request<LoginClass>(options);
226
- return response.data;
227
- }
192
+ const { data: { accessToken } } = await globalAxios.request<LoginClass>(options);
228
193
 
229
- private storeTokenData(tokenData?: TokenData) {
230
- if (typeof window !== 'undefined') {
231
- defaultStorage().set<TokenData>(TOKEN_DATA, tokenData);
232
- }
194
+ return accessToken;
233
195
  }
234
196
 
235
- public loadTokenData() {
236
- if (typeof window !== 'undefined') {
237
- this.tokenData = defaultStorage().get<TokenData>(TOKEN_DATA) || {};
238
- } else {
239
- this.tokenData = {};
197
+ private extractRefreshToken(response: AxiosResponse): string {
198
+ if (response.headers && response.headers['set-cookie']
199
+ && response.headers['set-cookie'].length > 0) {
200
+
201
+ return `${response.headers['set-cookie'][0].split(';')[0]};`;
240
202
  }
203
+
204
+ return '';
241
205
  }
242
206
 
243
- public cleanTokenData() {
244
- this.storeTokenData(null);
207
+ getConfiguration(): Configuration {
208
+ return this.configuration;
245
209
  }
246
210
 
247
211
  private attachInterceptor(axios: AxiosInstance) {
@@ -251,26 +215,19 @@ export class BaseAPI {
251
215
  },
252
216
  async (err) => {
253
217
  let originalConfig = err.config;
254
- if (err.response && !(err.response instanceof XMLHttpRequest)) { // sometimes buggy and is of type request
218
+ if (err.response) {
255
219
  // Access Token was expired
256
- if ((err.response.status === 401 || err.response.status === 403)
257
- && !originalConfig._retry) {
220
+ if (err.response.status === 401 && !originalConfig._retry) {
258
221
  originalConfig._retry = true;
259
222
  try {
260
- const { accessToken: tokenString, permissions } = await this.refreshTokenInternal();
223
+ const tokenString = await this.refreshTokenInternal();
261
224
  const accessToken = `Bearer ${tokenString}`;
262
- this.tokenData.permissions = permissions;
263
225
 
264
- delete originalConfig.headers['Authorization']
265
-
266
- originalConfig.headers['Authorization'] = accessToken;
226
+ originalConfig.headers['Authorization'] = `Bearer ${accessToken}`
267
227
 
268
228
  this.configuration.accessToken = accessToken;
269
- this.tokenData.accessToken = tokenString;
270
-
271
- this.storeTokenData(this.tokenData);
272
229
 
273
- return axios(originalConfig);
230
+ return axios.request(originalConfig);
274
231
  } catch (_error) {
275
232
  if (_error.response && _error.response.data) {
276
233
  return Promise.reject(_error.response.data);
@@ -278,23 +235,23 @@ export class BaseAPI {
278
235
  return Promise.reject(_error);
279
236
  }
280
237
  }
281
- } else if (err.message === NETWORK_ERROR_MESSAGE
238
+ if (err.response.status === 403 && err.response.data) {
239
+ return Promise.reject(err.response.data);
240
+ }
241
+ } else if(err.message === NETWORK_ERROR_MESSAGE
242
+ && err.isAxiosError
282
243
  && originalConfig.headers.hasOwnProperty('Authorization')
283
244
  && _retry_count < 4
284
- ) {
245
+ ){
285
246
  _retry_count++;
286
247
  try {
287
- const { accessToken: tokenString, permissions } = await this.refreshTokenInternal();
248
+ const tokenString = await this.refreshTokenInternal();
288
249
  const accessToken = `Bearer ${tokenString}`;
289
- this.tokenData.permissions = permissions;
290
250
 
291
251
  _retry = true;
292
- originalConfig.headers['Authorization'] = accessToken;
252
+ originalConfig.headers['Authorization'] = accessToken;
293
253
 
294
254
  this.configuration.accessToken = accessToken;
295
- this.tokenData.accessToken = tokenString;
296
-
297
- this.storeTokenData(this.tokenData);
298
255
 
299
256
  return axios.request({
300
257
  ...originalConfig,
@@ -304,7 +261,7 @@ export class BaseAPI {
304
261
  return Promise.reject(_error.response.data);
305
262
  }
306
263
  return Promise.reject(_error);
307
- }
264
+ }
308
265
  }
309
266
  return Promise.reject(err);
310
267
  }
@@ -319,7 +276,7 @@ export class BaseAPI {
319
276
  * @extends {Error}
320
277
  */
321
278
  export class RequiredError extends Error {
322
- override name: "RequiredError" = "RequiredError";
279
+ name: "RequiredError" = "RequiredError";
323
280
  constructor(public field: string, msg?: string) {
324
281
  super(msg);
325
282
  }
package/common.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  import { Configuration } from "./configuration";
17
17
  import { RequiredError, RequestArgs } from "./base";
18
18
  import { AxiosInstance, AxiosResponse } from 'axios';
19
+ import { URL, URLSearchParams } from 'url';
19
20
  /**
20
21
  *
21
22
  * @export
@@ -65,7 +66,7 @@ export const setBearerAuthToObject = async function (object: any, configuration?
65
66
  const accessToken = typeof configuration.accessToken === 'function'
66
67
  ? await configuration.accessToken()
67
68
  : await configuration.accessToken;
68
- object["Authorization"] = configuration.getBearerToken(accessToken);
69
+ object["Authorization"] = "Bearer " + accessToken;
69
70
  }
70
71
  }
71
72
 
@@ -78,7 +79,7 @@ export const setOAuthToObject = async function (object: any, name: string, scope
78
79
  const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
79
80
  ? await configuration.accessToken(name, scopes)
80
81
  : await configuration.accessToken;
81
- object["Authorization"] = configuration.getBearerToken(localVarAccessTokenValue);
82
+ object["Authorization"] = "Bearer " + localVarAccessTokenValue;
82
83
  }
83
84
  }
84
85
 
package/configuration.ts CHANGED
@@ -74,6 +74,14 @@ export class Configuration {
74
74
  */
75
75
  formDataCtor?: new () => any;
76
76
 
77
+ /**
78
+ * parameter for automatically refreshing access token for oauth2 security
79
+ *
80
+ * @type {string}
81
+ * @memberof Configuration
82
+ */
83
+ refreshToken?: string;
84
+
77
85
  constructor(param: ConfigurationParameters = {}) {
78
86
  this.apiKey = param.apiKey;
79
87
  this.username = param.username;
@@ -98,13 +106,4 @@ export class Configuration {
98
106
  const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
99
107
  return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
100
108
  }
101
-
102
- /**
103
- * Returns "Bearer" token.
104
- * @param token - access token.
105
- * @return Bearer token.
106
- */
107
- public getBearerToken(token?: string): string {
108
- return ('' + token).startsWith("Bearer") ? token : "Bearer " + token;
109
- }
110
109
  }