@anmiles/google-api-wrapper 7.0.2 → 7.0.4

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/CHANGELOG.md CHANGED
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [7.0.4](../../tags/v7.0.4) - 2023-04-22
9
+ ### Changed
10
+ - Always require refresh token for permanent credentials
11
+
8
12
  ## [7.0.2](../../tags/v7.0.2) - 2023-04-22
9
13
  ### Changed
10
14
  - Show instructions in the browser to prevent direct opening profile-oriented pages in wrong browsers
@@ -1,4 +1,4 @@
1
- export { getJSON, getJSONAsync, writeJSON };
1
+ export { getJSON, getJSONAsync, writeJSON, readJSON };
2
2
  declare const _default: {
3
3
  getJSON: typeof getJSON;
4
4
  getJSONAsync: typeof getJSONAsync;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeJSON = exports.getJSONAsync = exports.getJSON = void 0;
6
+ exports.readJSON = exports.writeJSON = exports.getJSONAsync = exports.getJSON = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const logger_1 = require("./logger");
9
9
  const paths_1 = require("./paths");
@@ -46,6 +46,7 @@ function readJSON(filename) {
46
46
  const jsonString = fs_1.default.readFileSync(filename).toString();
47
47
  return JSON.parse(jsonString);
48
48
  }
49
+ exports.readJSON = readJSON;
49
50
  function checkJSON(filename, json) {
50
51
  if (json) {
51
52
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"jsonLib.js","sourceRoot":"","sources":["../../src/lib/jsonLib.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,qCAAiC;AACjC,mCAAqC;AAErC,wDAAgC;AAGhC,kBAAe,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAEzE,SAAS,OAAO,CAAI,QAAgB,EAAE,cAA8C,EAAE,YAAmC;IACxH,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,GAAG,iBAAO,CAAC,QAAQ,CAAI,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE;YACxC,OAAO,IAAI,CAAC;SACZ;KACD;IAED,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;IACrB,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAjBQ,0BAAO;AAmBhB,KAAK,UAAU,YAAY,CAAI,QAAgB,EAAE,mBAAqC,EAAE,iBAAiD;IACxI,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,GAAG,iBAAO,CAAC,QAAQ,CAAI,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,iBAAiB,IAAI,MAAM,iBAAiB,CAAC,IAAI,CAAC,EAAE;YACxD,OAAO,IAAI,CAAC;SACZ;KACD;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,EAAE,CAAC;IACzC,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;IACrB,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAjCiB,oCAAY;AAmC9B,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACtD,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAtC+B,8BAAS;AAwCzC,SAAS,QAAQ,CAAI,QAAgB;IACpC,MAAM,UAAU,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAM,CAAC;AACpC,CAAC;AAED,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,IAAI,IAAI,EAAE;QACT,OAAO;KACP;IACD,IAAA,cAAK,EAAC,QAAQ,QAAQ,sGAAsG,CAAC,CAAC;AAC/H,CAAC"}
1
+ {"version":3,"file":"jsonLib.js","sourceRoot":"","sources":["../../src/lib/jsonLib.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,qCAAiC;AACjC,mCAAqC;AAErC,wDAAgC;AAGhC,kBAAe,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AAEzE,SAAS,OAAO,CAAI,QAAgB,EAAE,cAA8C,EAAE,YAAmC;IACxH,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,GAAG,iBAAO,CAAC,QAAQ,CAAI,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE;YACxC,OAAO,IAAI,CAAC;SACZ;KACD;IAED,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;IACrB,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAjBQ,0BAAO;AAmBhB,KAAK,UAAU,YAAY,CAAI,QAAgB,EAAE,mBAAqC,EAAE,iBAAiD;IACxI,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,GAAG,iBAAO,CAAC,QAAQ,CAAI,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,iBAAiB,IAAI,MAAM,iBAAiB,CAAC,IAAI,CAAC,EAAE;YACxD,OAAO,IAAI,CAAC;SACZ;KACD;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,EAAE,CAAC;IACzC,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,IAAA,kBAAU,EAAC,QAAQ,CAAC,CAAC;IACrB,iBAAO,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACb,CAAC;AAjCiB,oCAAY;AAmC9B,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACtD,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAtC+B,8BAAS;AAwCzC,SAAS,QAAQ,CAAI,QAAgB;IACpC,MAAM,UAAU,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAM,CAAC;AACpC,CAAC;AA3C0C,4BAAQ;AA6CnD,SAAS,SAAS,CAAI,QAAgB,EAAE,IAAO;IAC9C,IAAI,IAAI,EAAE;QACT,OAAO;KACP;IACD,IAAA,cAAK,EAAC,QAAQ,QAAQ,sGAAsG,CAAC,CAAC;AAC/H,CAAC"}
@@ -16,7 +16,7 @@ declare function getScopes(): string[];
16
16
  declare function getSecrets(profile: string): Secrets;
17
17
  declare function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials>;
18
18
  declare function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<boolean>;
19
- declare function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials>;
19
+ declare function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions, prompt?: GoogleApis.Auth.GenerateAuthUrlOpts['prompt']): Promise<GoogleApis.Auth.Credentials>;
20
20
  declare function checkSecrets(profile: string, secretsObject: Secrets, secretsFile: string): true | void;
21
21
  declare function getScopesError(scopesFile: string): string;
22
22
  declare function getSecretsError(profile: string, secretsFile: string): string;
@@ -30,26 +30,38 @@ function getSecrets(profile) {
30
30
  exports.getSecrets = getSecrets;
31
31
  async function getCredentials(profile, auth, options) {
32
32
  const credentialsFile = (0, paths_1.getCredentialsFile)(profile);
33
- return (options === null || options === void 0 ? void 0 : options.temporary)
34
- ? secrets_1.default.createCredentials(profile, auth, options)
35
- : (0, jsonLib_1.getJSONAsync)(credentialsFile, () => secrets_1.default.createCredentials(profile, auth, options), secrets_1.default.validateCredentials);
33
+ if (options === null || options === void 0 ? void 0 : options.temporary) {
34
+ return secrets_1.default.createCredentials(profile, auth, options);
35
+ }
36
+ return (0, jsonLib_1.getJSONAsync)(credentialsFile, async () => {
37
+ // eslint-disable-next-line camelcase
38
+ const refresh_token = (0, paths_1.ensureFile)(credentialsFile) ? (0, jsonLib_1.readJSON)(credentialsFile).refresh_token : undefined;
39
+ // eslint-disable-next-line camelcase
40
+ const credentials = await secrets_1.default.createCredentials(profile, auth, options, refresh_token ? undefined : 'consent');
41
+ // eslint-disable-next-line camelcase
42
+ return { refresh_token, ...credentials };
43
+ }, secrets_1.default.validateCredentials);
36
44
  }
37
45
  exports.getCredentials = getCredentials;
38
46
  async function validateCredentials(credentials) {
39
47
  if (!credentials.access_token) {
40
48
  return false;
41
49
  }
50
+ if (!credentials.refresh_token) {
51
+ return false;
52
+ }
42
53
  if (!credentials.expiry_date) {
43
54
  return true;
44
55
  }
45
56
  return new Date().getTime() - credentials.expiry_date < tokenExpiration;
46
57
  }
47
- async function createCredentials(profile, auth, options) {
58
+ async function createCredentials(profile, auth, options, prompt) {
48
59
  const scope = (options === null || options === void 0 ? void 0 : options.scopes) || secrets_1.default.getScopes();
49
60
  return new Promise((resolve) => {
50
61
  const authUrl = auth.generateAuthUrl({
51
62
  // eslint-disable-next-line camelcase
52
63
  access_type: 'offline',
64
+ prompt,
53
65
  scope,
54
66
  });
55
67
  const server = http_1.default.createServer(async (request, response) => {
@@ -1 +1 @@
1
- {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anmiles/google-api-wrapper",
3
- "version": "7.0.2",
3
+ "version": "7.0.4",
4
4
  "description": "Provides quick interface for getting google API data",
5
5
  "keywords": [
6
6
  "google",
@@ -5,6 +5,7 @@ import open from 'open';
5
5
  import type GoogleApis from 'googleapis';
6
6
  import jsonLib from '../jsonLib';
7
7
  import logger from '../logger';
8
+ import paths from '../paths';
8
9
  import type { Secrets } from '../../types';
9
10
 
10
11
  import secrets from '../secrets';
@@ -14,7 +15,7 @@ jest.mock<typeof secrets>('../secrets', () => ({
14
15
  getSecrets : jest.fn().mockImplementation(() => secretsJSON),
15
16
  getCredentials : jest.fn(),
16
17
  validateCredentials : jest.fn(),
17
- createCredentials : jest.fn(),
18
+ createCredentials : jest.fn().mockImplementation(() => credentialsJSON),
18
19
  checkSecrets : jest.fn(),
19
20
  getScopesError : jest.fn().mockImplementation(() => scopesError),
20
21
  getSecretsError : jest.fn().mockImplementation(() => secretsError),
@@ -46,8 +47,9 @@ jest.mock('open', () => jest.fn().mockImplementation((url: string) => {
46
47
  }));
47
48
 
48
49
  jest.mock<Partial<typeof jsonLib>>('../jsonLib', () => ({
49
- getJSON : jest.fn().mockImplementation(() => json),
50
+ getJSON : jest.fn().mockImplementation(() => json),
50
51
  getJSONAsync : jest.fn().mockImplementation(async () => json),
52
+ readJSON : jest.fn().mockImplementation(async () => json),
51
53
  }));
52
54
 
53
55
  jest.mock<Partial<typeof logger>>('../logger', () => ({
@@ -57,6 +59,13 @@ jest.mock<Partial<typeof logger>>('../logger', () => ({
57
59
  }) as jest.Mock<never, any>,
58
60
  }));
59
61
 
62
+ jest.mock<Partial<typeof paths>>('../paths', () => ({
63
+ getScopesFile : jest.fn().mockImplementation(() => scopesFile),
64
+ getSecretsFile : jest.fn().mockImplementation(() => secretsFile),
65
+ getCredentialsFile : jest.fn().mockImplementation(() => credentialsFile),
66
+ ensureFile : jest.fn().mockImplementation(() => fileExists),
67
+ }));
68
+
60
69
  const profile = 'username1';
61
70
  const scopesFile = 'scopes.json';
62
71
  const secretsFile = 'secrets/username1.json';
@@ -85,11 +94,12 @@ const secretsJSON: Secrets = {
85
94
  },
86
95
  };
87
96
 
88
- const credentialsJSON = {
89
- token : {},
97
+ const credentialsJSON: GoogleApis.Auth.Credentials = {
98
+ // eslint-disable-next-line camelcase
99
+ access_token : 'access_token222',
90
100
  };
91
101
 
92
- let json: object;
102
+ let json: any;
93
103
 
94
104
  const code = 'code';
95
105
  const authUrl = 'https://authUrl';
@@ -138,6 +148,8 @@ const connections = [
138
148
  { remoteAddress : 'server', remotePort : '1003', on : jest.fn(), destroy : jest.fn() },
139
149
  ];
140
150
 
151
+ let fileExists: boolean;
152
+
141
153
  describe('src/lib/secrets', () => {
142
154
  describe('getScopes', () => {
143
155
  const getJSONSpy = jest.spyOn(jsonLib, 'getJSON');
@@ -203,7 +215,8 @@ describe('src/lib/secrets', () => {
203
215
  const getJSONAsyncSpy = jest.spyOn(jsonLib, 'getJSONAsync');
204
216
 
205
217
  beforeEach(() => {
206
- json = credentialsJSON;
218
+ json = credentialsJSON;
219
+ fileExists = false;
207
220
  });
208
221
 
209
222
  it('should get json from credentials file by default', async () => {
@@ -213,7 +226,7 @@ describe('src/lib/secrets', () => {
213
226
  expect(getJSONAsyncSpy.mock.calls[0][0]).toEqual(credentialsFile);
214
227
  });
215
228
 
216
- it('should get json from credentials file if temporariness explicitly unset', async () => {
229
+ it('should get json from credentials file if temporariness not set', async () => {
217
230
  await original.getCredentials(profile, auth, { temporary : false });
218
231
 
219
232
  expect(getJSONAsyncSpy).toBeCalled();
@@ -226,46 +239,85 @@ describe('src/lib/secrets', () => {
226
239
  expect(getJSONAsyncSpy).not.toBeCalled();
227
240
  });
228
241
 
229
- it('should fallback to createCredentials by default', async () => {
242
+ it('should call createCredentials with consent in fallback if no existing credentials', async () => {
243
+ fileExists = false;
244
+
230
245
  await original.getCredentials(profile, auth);
231
246
 
247
+ expect(secrets.createCredentials).not.toBeCalled();
248
+
232
249
  const fallback = getJSONAsyncSpy.mock.calls[0][1];
233
- await fallback();
250
+ const result = await fallback();
234
251
 
235
- expect(secrets.createCredentials).toBeCalledWith(profile, auth, undefined);
252
+ expect(jsonLib.readJSON).not.toBeCalled();
253
+ expect(secrets.createCredentials).toBeCalledWith(profile, auth, undefined, 'consent');
254
+ expect(result).toEqual(credentialsJSON);
236
255
  });
237
256
 
238
- it('should call createCredentials directly if temporariness explicitly unset', async () => {
257
+ it('should call createCredentials with consent in fallback if no existing credentials and pass temporariness', async () => {
258
+ fileExists = false;
259
+
239
260
  await original.getCredentials(profile, auth, { temporary : false });
240
261
 
262
+ expect(secrets.createCredentials).not.toBeCalled();
263
+
241
264
  const fallback = getJSONAsyncSpy.mock.calls[0][1];
242
- await fallback();
265
+ const result = await fallback();
243
266
 
244
- expect(secrets.createCredentials).toBeCalledWith(profile, auth, { temporary : false });
267
+ expect(jsonLib.readJSON).not.toBeCalled();
268
+ expect(secrets.createCredentials).toBeCalledWith(profile, auth, { temporary : false }, 'consent');
269
+ expect(result).toEqual(credentialsJSON);
245
270
  });
246
271
 
247
- it('should call createCredentials directly if temporariness set', async () => {
248
- await original.getCredentials(profile, auth, { temporary : true });
272
+ it('should call createCredentials with consent in fallback if existing credentials do not have refresh token', async () => {
273
+ fileExists = true;
249
274
 
250
- expect(secrets.createCredentials).toBeCalledWith(profile, auth, { temporary : true });
251
- });
275
+ await original.getCredentials(profile, auth);
252
276
 
253
- it('should return credentials by default', async () => {
254
- const result = await original.getCredentials(profile, auth);
277
+ expect(secrets.createCredentials).not.toBeCalled();
255
278
 
279
+ const fallback = getJSONAsyncSpy.mock.calls[0][1];
280
+ const result = await fallback();
281
+
282
+ expect(jsonLib.readJSON).toBeCalledWith(credentialsFile);
283
+ expect(secrets.createCredentials).toBeCalledWith(profile, auth, undefined, 'consent');
256
284
  expect(result).toEqual(credentialsJSON);
257
285
  });
258
286
 
259
- it('should return credentials if temporariness explicitly unset', async () => {
260
- const result = await original.getCredentials(profile, auth, { temporary : false });
287
+ it('should call createCredentials without consent in fallback and replace refresh_token if existing credentials have refresh token', async () => {
288
+ fileExists = true;
289
+ // eslint-disable-next-line camelcase
290
+ jest.spyOn(jsonLib, 'readJSON').mockReturnValueOnce({ ...credentialsJSON, refresh_token : 'refresh_token' });
291
+
292
+ await original.getCredentials(profile, auth);
261
293
 
262
- expect(result).toEqual(credentialsJSON);
294
+ expect(secrets.createCredentials).not.toBeCalled();
295
+
296
+ const fallback = getJSONAsyncSpy.mock.calls[0][1];
297
+ const result = await fallback();
298
+
299
+ expect(jsonLib.readJSON).toBeCalledWith(credentialsFile);
300
+ expect(secrets.createCredentials).toBeCalledWith(profile, auth, undefined, undefined);
301
+ // eslint-disable-next-line camelcase
302
+ expect(result).toEqual({ ... credentialsJSON, refresh_token : 'refresh_token' });
263
303
  });
264
304
 
265
- it('should return nothing if temporariness set', async () => {
266
- const result = await original.getCredentials(profile, auth, { temporary : true });
305
+ it('should call createCredentials without consent in fallback and leave refresh token if existing credentials have refresh token', async () => {
306
+ fileExists = true;
307
+ // eslint-disable-next-line camelcase
308
+ jest.spyOn(jsonLib, 'readJSON').mockReturnValueOnce({ ...credentialsJSON, refresh_token : 'refresh_token' });
309
+ // eslint-disable-next-line camelcase
310
+ jest.spyOn(secrets, 'createCredentials').mockResolvedValueOnce({ ...credentialsJSON, refresh_token : 'refresh_token_exists' });
311
+
312
+ await original.getCredentials(profile, auth);
267
313
 
268
- expect(result).toBeUndefined();
314
+ const fallback = getJSONAsyncSpy.mock.calls[0][1];
315
+ const result = await fallback();
316
+
317
+ expect(jsonLib.readJSON).toBeCalledWith(credentialsFile);
318
+ expect(secrets.createCredentials).toBeCalledWith(profile, auth, undefined, undefined);
319
+ // eslint-disable-next-line camelcase
320
+ expect(result).toEqual({ ...credentialsJSON, refresh_token : 'refresh_token_exists' });
269
321
  });
270
322
  });
271
323
 
@@ -274,23 +326,28 @@ describe('src/lib/secrets', () => {
274
326
  expect(await original.validateCredentials({})).toEqual(false);
275
327
  });
276
328
 
329
+ it('should return true if no refresh token', async () => {
330
+ // eslint-disable-next-line camelcase
331
+ expect(await original.validateCredentials({ access_token : 'token' })).toEqual(false);
332
+ });
333
+
277
334
  it('should return true if no expiration', async () => {
278
335
  // eslint-disable-next-line camelcase
279
- expect(await original.validateCredentials({ access_token : 'token' })).toEqual(true);
336
+ expect(await original.validateCredentials({ access_token : 'token', refresh_token : 'token' })).toEqual(true);
280
337
  });
281
338
 
282
339
  it('should return true if credentials are not more than 1 week ago', async () => {
283
340
  const expiryDate = new Date();
284
341
  expiryDate.setDate(expiryDate.getDate() - 6);
285
342
  // eslint-disable-next-line camelcase
286
- expect(await original.validateCredentials({ access_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(true);
343
+ expect(await original.validateCredentials({ access_token : 'token', refresh_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(true);
287
344
  });
288
345
 
289
346
  it('should return true if credentials are more than 1 week ago', async () => {
290
347
  const expiryDate = new Date();
291
348
  expiryDate.setDate(expiryDate.getDate() - 8);
292
349
  // eslint-disable-next-line camelcase
293
- expect(await original.validateCredentials({ access_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(false);
350
+ expect(await original.validateCredentials({ access_token : 'token', refresh_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(false);
294
351
  });
295
352
  });
296
353
 
@@ -305,6 +362,23 @@ describe('src/lib/secrets', () => {
305
362
  expect(auth.generateAuthUrl).toBeCalledWith({
306
363
  // eslint-disable-next-line camelcase
307
364
  access_type : 'offline',
365
+ prompt : undefined,
366
+ scope : [
367
+ 'https://www.googleapis.com/auth/calendar.calendars.readonly',
368
+ 'https://www.googleapis.com/auth/calendar.events.readonly',
369
+ ],
370
+ });
371
+ });
372
+
373
+ it('should generate authUrl and require consent if explicitly asked', async () => {
374
+ willOpen(tokenUrl, 100);
375
+
376
+ await original.createCredentials(profile, auth, { temporary : true }, 'consent');
377
+
378
+ expect(auth.generateAuthUrl).toBeCalledWith({
379
+ // eslint-disable-next-line camelcase
380
+ access_type : 'offline',
381
+ prompt : 'consent',
308
382
  scope : [
309
383
  'https://www.googleapis.com/auth/calendar.calendars.readonly',
310
384
  'https://www.googleapis.com/auth/calendar.events.readonly',
@@ -320,6 +394,7 @@ describe('src/lib/secrets', () => {
320
394
  expect(auth.generateAuthUrl).toBeCalledWith({
321
395
  // eslint-disable-next-line camelcase
322
396
  access_type : 'offline',
397
+ prompt : undefined,
323
398
  scope : [ 'scope1', 'scope2' ],
324
399
  });
325
400
  });
@@ -4,7 +4,7 @@ import { ensureFile } from './paths';
4
4
 
5
5
  import jsonLib from './jsonLib';
6
6
 
7
- export { getJSON, getJSONAsync, writeJSON };
7
+ export { getJSON, getJSONAsync, writeJSON, readJSON };
8
8
  export default { getJSON, getJSONAsync, writeJSON, readJSON, checkJSON };
9
9
 
10
10
  function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any>>, validateJSON?: (json: T) => boolean): T {
@@ -3,9 +3,9 @@ import enableDestroy from 'server-destroy';
3
3
  import open from 'open';
4
4
  import type GoogleApis from 'googleapis';
5
5
  import type { Secrets, AuthOptions } from '../types';
6
- import { getJSON, getJSONAsync } from './jsonLib';
6
+ import { getJSON, getJSONAsync, readJSON } from './jsonLib';
7
7
  import { warn, error } from './logger';
8
- import { getScopesFile, getSecretsFile, getCredentialsFile } from './paths';
8
+ import { getScopesFile, getSecretsFile, getCredentialsFile, ensureFile } from './paths';
9
9
 
10
10
  import secrets from './secrets';
11
11
 
@@ -33,9 +33,18 @@ function getSecrets(profile: string): Secrets {
33
33
  async function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials> {
34
34
  const credentialsFile = getCredentialsFile(profile);
35
35
 
36
- return options?.temporary
37
- ? secrets.createCredentials(profile, auth, options)
38
- : getJSONAsync(credentialsFile, () => secrets.createCredentials(profile, auth, options), secrets.validateCredentials);
36
+ if (options?.temporary) {
37
+ return secrets.createCredentials(profile, auth, options);
38
+ }
39
+
40
+ return getJSONAsync(credentialsFile, async () => {
41
+ // eslint-disable-next-line camelcase
42
+ const refresh_token = ensureFile(credentialsFile) ? readJSON<GoogleApis.Auth.Credentials>(credentialsFile).refresh_token : undefined;
43
+ // eslint-disable-next-line camelcase
44
+ const credentials = await secrets.createCredentials(profile, auth, options, refresh_token ? undefined : 'consent');
45
+ // eslint-disable-next-line camelcase
46
+ return { refresh_token, ...credentials };
47
+ }, secrets.validateCredentials);
39
48
  }
40
49
 
41
50
  async function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<boolean> {
@@ -43,6 +52,10 @@ async function validateCredentials(credentials: GoogleApis.Auth.Credentials): Pr
43
52
  return false;
44
53
  }
45
54
 
55
+ if (!credentials.refresh_token) {
56
+ return false;
57
+ }
58
+
46
59
  if (!credentials.expiry_date) {
47
60
  return true;
48
61
  }
@@ -50,13 +63,14 @@ async function validateCredentials(credentials: GoogleApis.Auth.Credentials): Pr
50
63
  return new Date().getTime() - credentials.expiry_date < tokenExpiration;
51
64
  }
52
65
 
53
- async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials> {
66
+ async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions, prompt?: GoogleApis.Auth.GenerateAuthUrlOpts['prompt']): Promise<GoogleApis.Auth.Credentials> {
54
67
  const scope = options?.scopes || secrets.getScopes();
55
68
 
56
69
  return new Promise((resolve) => {
57
70
  const authUrl = auth.generateAuthUrl({
58
71
  // eslint-disable-next-line camelcase
59
72
  access_type : 'offline',
73
+ prompt,
60
74
  scope,
61
75
  });
62
76