@anmiles/google-api-wrapper 6.1.1 → 7.0.1
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 +8 -0
- package/dist/lib/jsonLib.d.ts +2 -2
- package/dist/lib/jsonLib.js +11 -4
- package/dist/lib/jsonLib.js.map +1 -1
- package/dist/lib/secrets.d.ts +2 -0
- package/dist/lib/secrets.js +19 -3
- package/dist/lib/secrets.js.map +1 -1
- package/package.json +2 -1
- package/src/lib/__tests__/jsonLib.test.ts +104 -16
- package/src/lib/__tests__/secrets.test.ts +50 -10
- package/src/lib/jsonLib.ts +13 -4
- package/src/lib/secrets.ts +24 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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.1](../../tags/v7.0.1) - 2023-04-22
|
|
9
|
+
### Changed
|
|
10
|
+
- Compatibility for `open` package
|
|
11
|
+
|
|
12
|
+
## [7.0.0](../../tags/v7.0.0) - 2023-04-22
|
|
13
|
+
### Changed
|
|
14
|
+
- Invalidate tokens after 7 days due to Google policy for testing apps
|
|
15
|
+
|
|
8
16
|
## [6.1.1](../../tags/v6.1.1) - 2023-03-27
|
|
9
17
|
### Changed
|
|
10
18
|
- Immediately destroy server after receiving needed response
|
package/dist/lib/jsonLib.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ declare const _default: {
|
|
|
7
7
|
checkJSON: typeof checkJSON;
|
|
8
8
|
};
|
|
9
9
|
export default _default;
|
|
10
|
-
declare function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any
|
|
11
|
-
declare function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>): Promise<T>;
|
|
10
|
+
declare function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any>>, validateJSON?: (json: T) => boolean): T;
|
|
11
|
+
declare function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>, validateJSONAsync?: (json: T) => Promise<boolean>): Promise<T>;
|
|
12
12
|
declare function writeJSON<T>(filename: string, json: T): void;
|
|
13
13
|
declare function readJSON<T>(filename: string): T;
|
|
14
14
|
declare function checkJSON<T>(filename: string, json: T): void;
|
package/dist/lib/jsonLib.js
CHANGED
|
@@ -9,9 +9,12 @@ const logger_1 = require("./logger");
|
|
|
9
9
|
const paths_1 = require("./paths");
|
|
10
10
|
const jsonLib_1 = __importDefault(require("./jsonLib"));
|
|
11
11
|
exports.default = { getJSON, getJSONAsync, writeJSON, readJSON, checkJSON };
|
|
12
|
-
function getJSON(filename, createCallback) {
|
|
12
|
+
function getJSON(filename, createCallback, validateJSON) {
|
|
13
13
|
if (fs_1.default.existsSync(filename)) {
|
|
14
|
-
|
|
14
|
+
const json = jsonLib_1.default.readJSON(filename);
|
|
15
|
+
if (!validateJSON || validateJSON(json)) {
|
|
16
|
+
return json;
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
const json = createCallback();
|
|
17
20
|
jsonLib_1.default.checkJSON(filename, json);
|
|
@@ -20,12 +23,16 @@ function getJSON(filename, createCallback) {
|
|
|
20
23
|
return json;
|
|
21
24
|
}
|
|
22
25
|
exports.getJSON = getJSON;
|
|
23
|
-
async function getJSONAsync(filename, createCallbackAsync) {
|
|
26
|
+
async function getJSONAsync(filename, createCallbackAsync, validateJSONAsync) {
|
|
24
27
|
if (fs_1.default.existsSync(filename)) {
|
|
25
|
-
|
|
28
|
+
const json = jsonLib_1.default.readJSON(filename);
|
|
29
|
+
if (!validateJSONAsync || await validateJSONAsync(json)) {
|
|
30
|
+
return json;
|
|
31
|
+
}
|
|
26
32
|
}
|
|
27
33
|
const json = await createCallbackAsync();
|
|
28
34
|
jsonLib_1.default.checkJSON(filename, json);
|
|
35
|
+
(0, paths_1.ensureFile)(filename);
|
|
29
36
|
jsonLib_1.default.writeJSON(filename, json);
|
|
30
37
|
return json;
|
|
31
38
|
}
|
package/dist/lib/jsonLib.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/lib/secrets.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ declare const _default: {
|
|
|
5
5
|
getScopes: typeof getScopes;
|
|
6
6
|
getSecrets: typeof getSecrets;
|
|
7
7
|
getCredentials: typeof getCredentials;
|
|
8
|
+
validateCredentials: typeof validateCredentials;
|
|
8
9
|
createCredentials: typeof createCredentials;
|
|
9
10
|
checkSecrets: typeof checkSecrets;
|
|
10
11
|
getSecretsError: typeof getSecretsError;
|
|
@@ -14,6 +15,7 @@ export default _default;
|
|
|
14
15
|
declare function getScopes(): string[];
|
|
15
16
|
declare function getSecrets(profile: string): Secrets;
|
|
16
17
|
declare function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials>;
|
|
18
|
+
declare function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<boolean>;
|
|
17
19
|
declare function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials>;
|
|
18
20
|
declare function checkSecrets(profile: string, secretsObject: Secrets, secretsFile: string): true | void;
|
|
19
21
|
declare function getScopesError(scopesFile: string): string;
|
package/dist/lib/secrets.js
CHANGED
|
@@ -30,13 +30,15 @@ exports.getCredentials = exports.getSecrets = void 0;
|
|
|
30
30
|
const http_1 = __importDefault(require("http"));
|
|
31
31
|
const server_destroy_1 = __importDefault(require("server-destroy"));
|
|
32
32
|
const colorette = __importStar(require("colorette"));
|
|
33
|
+
const open_1 = __importDefault(require("open"));
|
|
33
34
|
const jsonLib_1 = require("./jsonLib");
|
|
34
35
|
const logger_1 = require("./logger");
|
|
35
36
|
const paths_1 = require("./paths");
|
|
36
37
|
const secrets_1 = __importDefault(require("./secrets"));
|
|
37
|
-
exports.default = { getScopes, getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
38
|
+
exports.default = { getScopes, getSecrets, getCredentials, validateCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
38
39
|
const callbackPort = 6006;
|
|
39
40
|
const callbackURI = `http://localhost:${callbackPort}/oauthcallback`;
|
|
41
|
+
const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
|
|
40
42
|
function getScopes() {
|
|
41
43
|
const scopesFile = (0, paths_1.getScopesFile)();
|
|
42
44
|
const scopes = (0, jsonLib_1.getJSON)(scopesFile, () => (0, logger_1.error)(secrets_1.default.getScopesError(scopesFile)));
|
|
@@ -53,9 +55,18 @@ async function getCredentials(profile, auth, options) {
|
|
|
53
55
|
const credentialsFile = (0, paths_1.getCredentialsFile)(profile);
|
|
54
56
|
return (options === null || options === void 0 ? void 0 : options.temporary)
|
|
55
57
|
? secrets_1.default.createCredentials(profile, auth, options)
|
|
56
|
-
: (0, jsonLib_1.getJSONAsync)(credentialsFile, () => secrets_1.default.createCredentials(profile, auth, options));
|
|
58
|
+
: (0, jsonLib_1.getJSONAsync)(credentialsFile, () => secrets_1.default.createCredentials(profile, auth, options), secrets_1.default.validateCredentials);
|
|
57
59
|
}
|
|
58
60
|
exports.getCredentials = getCredentials;
|
|
61
|
+
async function validateCredentials(credentials) {
|
|
62
|
+
if (!credentials.access_token) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (!credentials.expiry_date) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return new Date().getTime() - credentials.expiry_date < tokenExpiration;
|
|
69
|
+
}
|
|
59
70
|
async function createCredentials(profile, auth, options) {
|
|
60
71
|
const scope = (options === null || options === void 0 ? void 0 : options.scopes) || secrets_1.default.getScopes();
|
|
61
72
|
return new Promise((resolve) => {
|
|
@@ -79,7 +90,12 @@ async function createCredentials(profile, auth, options) {
|
|
|
79
90
|
});
|
|
80
91
|
(0, server_destroy_1.default)(server);
|
|
81
92
|
server.listen(callbackPort);
|
|
82
|
-
|
|
93
|
+
if (options === null || options === void 0 ? void 0 : options.temporary) {
|
|
94
|
+
(0, logger_1.info)(`Please open ${colorette.yellow(authUrl)} in your browser using google profile for ${colorette.yellow(profile)} and allow access to ${colorette.yellow(scope.join(','))}`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
(0, open_1.default)(authUrl);
|
|
98
|
+
}
|
|
83
99
|
});
|
|
84
100
|
}
|
|
85
101
|
function checkSecrets(profile, secretsObject, secretsFile) {
|
package/dist/lib/secrets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAwB;AACxB,oEAA2C;AAC3C,qDAAuC;
|
|
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": "
|
|
3
|
+
"version": "7.0.1",
|
|
4
4
|
"description": "Provides quick interface for getting google API data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"google",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"colorette": "^2.0.19",
|
|
33
33
|
"execa": "^5.1.1",
|
|
34
34
|
"googleapis": "^104.0.0",
|
|
35
|
+
"open": "^8.4.2",
|
|
35
36
|
"server-destroy": "^1.0.1"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
@@ -33,11 +33,15 @@ const json = { key : 'value' };
|
|
|
33
33
|
const jsonString = JSON.stringify(json, null, ' ');
|
|
34
34
|
const fallbackJSON = { fallbackKey : 'fallbackValue' };
|
|
35
35
|
|
|
36
|
+
let fileExists: boolean;
|
|
37
|
+
let validation: boolean;
|
|
38
|
+
|
|
39
|
+
const validateCallback = jest.fn().mockImplementation(() => validation);
|
|
40
|
+
const validateCallbackAsync = jest.fn().mockImplementation(async () => validation);
|
|
41
|
+
|
|
36
42
|
const createCallback = jest.fn().mockReturnValue(fallbackJSON);
|
|
37
43
|
const createCallbackAsync = jest.fn().mockResolvedValue(fallbackJSON);
|
|
38
44
|
|
|
39
|
-
let fileExists: boolean;
|
|
40
|
-
|
|
41
45
|
describe('src/lib/jsonLib', () => {
|
|
42
46
|
describe('readJSON', () => {
|
|
43
47
|
it('should read specified file', () => {
|
|
@@ -62,91 +66,175 @@ describe('src/lib/jsonLib', () => {
|
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
describe('getJSON', () => {
|
|
65
|
-
|
|
69
|
+
|
|
70
|
+
it('should call readJSON if file exists and json is valid', () => {
|
|
66
71
|
fileExists = true;
|
|
72
|
+
validation = true;
|
|
67
73
|
|
|
68
|
-
original.getJSON(filename, createCallback);
|
|
74
|
+
original.getJSON(filename, createCallback, validateCallback);
|
|
69
75
|
|
|
70
76
|
expect(jsonLib.readJSON).toBeCalledWith(filename);
|
|
71
77
|
expect(createCallback).not.toBeCalled();
|
|
72
78
|
});
|
|
73
79
|
|
|
80
|
+
it('should call createCallback if file exists but json is not valid', () => {
|
|
81
|
+
fileExists = true;
|
|
82
|
+
validation = false;
|
|
83
|
+
|
|
84
|
+
original.getJSON(filename, createCallback, validateCallback);
|
|
85
|
+
|
|
86
|
+
expect(jsonLib.readJSON).toBeCalledWith(filename);
|
|
87
|
+
expect(createCallback).toBeCalledWith();
|
|
88
|
+
});
|
|
89
|
+
|
|
74
90
|
it('should call createCallback if file not exists', () => {
|
|
75
91
|
fileExists = false;
|
|
76
92
|
|
|
77
|
-
original.getJSON(filename, createCallback);
|
|
93
|
+
original.getJSON(filename, createCallback, validateCallback);
|
|
78
94
|
|
|
79
95
|
expect(jsonLib.readJSON).not.toBeCalled();
|
|
80
96
|
expect(createCallback).toBeCalledWith();
|
|
81
97
|
});
|
|
82
98
|
|
|
99
|
+
it('should not write fallback JSON back if file exists and json is valid', () => {
|
|
100
|
+
fileExists = true;
|
|
101
|
+
validation = true;
|
|
102
|
+
|
|
103
|
+
original.getJSON(filename, createCallback, validateCallback);
|
|
104
|
+
|
|
105
|
+
expect(jsonLib.writeJSON).not.toBeCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should write fallback JSON back if file exists but json is not valid', () => {
|
|
109
|
+
fileExists = true;
|
|
110
|
+
validation = false;
|
|
111
|
+
|
|
112
|
+
original.getJSON(filename, createCallback, validateCallback);
|
|
113
|
+
|
|
114
|
+
expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
|
|
115
|
+
expect(paths.ensureFile).toBeCalledWith(filename);
|
|
116
|
+
expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
|
|
117
|
+
});
|
|
118
|
+
|
|
83
119
|
it('should write fallback JSON back if file not exists', () => {
|
|
84
120
|
fileExists = false;
|
|
85
121
|
|
|
86
|
-
original.getJSON(filename, createCallback);
|
|
122
|
+
original.getJSON(filename, createCallback, validateCallback);
|
|
87
123
|
|
|
88
124
|
expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
|
|
89
125
|
expect(paths.ensureFile).toBeCalledWith(filename);
|
|
90
126
|
expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
|
|
91
127
|
});
|
|
92
128
|
|
|
93
|
-
it('should return JSON if file exists', () => {
|
|
129
|
+
it('should return JSON if file exists and json is valid', () => {
|
|
94
130
|
fileExists = true;
|
|
131
|
+
validation = true;
|
|
95
132
|
|
|
96
|
-
const result = original.getJSON(filename, createCallback);
|
|
133
|
+
const result = original.getJSON(filename, createCallback, validateCallback);
|
|
97
134
|
|
|
98
135
|
expect(result).toEqual(json);
|
|
99
136
|
});
|
|
100
137
|
|
|
138
|
+
it('should return fallback JSON if file exists but json is not valid', () => {
|
|
139
|
+
fileExists = true;
|
|
140
|
+
validation = false;
|
|
141
|
+
|
|
142
|
+
const result = original.getJSON(filename, createCallback, validateCallback);
|
|
143
|
+
|
|
144
|
+
expect(result).toEqual(fallbackJSON);
|
|
145
|
+
});
|
|
146
|
+
|
|
101
147
|
it('should return fallback JSON if file not exists', () => {
|
|
102
148
|
fileExists = false;
|
|
103
149
|
|
|
104
|
-
const result = original.getJSON(filename, createCallback);
|
|
150
|
+
const result = original.getJSON(filename, createCallback, validateCallback);
|
|
105
151
|
|
|
106
152
|
expect(result).toEqual(fallbackJSON);
|
|
107
153
|
});
|
|
108
154
|
});
|
|
109
155
|
|
|
110
156
|
describe('getJSONAsync', () => {
|
|
111
|
-
it('should call readJSON if file exists', async () => {
|
|
157
|
+
it('should call readJSON if file exists and json is valid', async () => {
|
|
112
158
|
fileExists = true;
|
|
159
|
+
validation = true;
|
|
113
160
|
|
|
114
|
-
await original.getJSONAsync(filename, createCallbackAsync);
|
|
161
|
+
await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
115
162
|
|
|
116
163
|
expect(jsonLib.readJSON).toBeCalledWith(filename);
|
|
117
164
|
expect(createCallbackAsync).not.toBeCalled();
|
|
118
165
|
});
|
|
119
166
|
|
|
167
|
+
it('should call createCallback if file exists but json is not valid', async () => {
|
|
168
|
+
fileExists = true;
|
|
169
|
+
validation = false;
|
|
170
|
+
|
|
171
|
+
await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
172
|
+
|
|
173
|
+
expect(jsonLib.readJSON).toBeCalledWith(filename);
|
|
174
|
+
expect(createCallbackAsync).toBeCalledWith();
|
|
175
|
+
});
|
|
176
|
+
|
|
120
177
|
it('should call createCallback if file not exists', async () => {
|
|
121
178
|
fileExists = false;
|
|
122
179
|
|
|
123
|
-
await original.getJSONAsync(filename, createCallbackAsync);
|
|
180
|
+
await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
124
181
|
|
|
125
182
|
expect(jsonLib.readJSON).not.toBeCalled();
|
|
126
183
|
expect(createCallbackAsync).toBeCalledWith();
|
|
127
184
|
});
|
|
128
185
|
|
|
186
|
+
it('should not write fallback JSON back if file exists and json is valid', async () => {
|
|
187
|
+
fileExists = true;
|
|
188
|
+
validation = true;
|
|
189
|
+
|
|
190
|
+
await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
191
|
+
|
|
192
|
+
expect(jsonLib.writeJSON).not.toBeCalled();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should write fallback JSON back if file exists but json is not valid', async () => {
|
|
196
|
+
fileExists = true;
|
|
197
|
+
validation = false;
|
|
198
|
+
|
|
199
|
+
await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
200
|
+
|
|
201
|
+
expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
|
|
202
|
+
expect(paths.ensureFile).toBeCalledWith(filename);
|
|
203
|
+
expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
|
|
204
|
+
});
|
|
205
|
+
|
|
129
206
|
it('should write fallback JSON back if file not exists', async () => {
|
|
130
207
|
fileExists = false;
|
|
131
208
|
|
|
132
|
-
await original.getJSONAsync(filename, createCallbackAsync);
|
|
209
|
+
await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
133
210
|
|
|
134
211
|
expect(jsonLib.checkJSON).toBeCalledWith(filename, fallbackJSON);
|
|
212
|
+
expect(paths.ensureFile).toBeCalledWith(filename);
|
|
135
213
|
expect(jsonLib.writeJSON).toBeCalledWith(filename, fallbackJSON);
|
|
136
214
|
});
|
|
137
215
|
|
|
138
|
-
it('should return JSON if file exists', async () => {
|
|
216
|
+
it('should return JSON if file exists and json is valid', async () => {
|
|
139
217
|
fileExists = true;
|
|
218
|
+
validation = true;
|
|
140
219
|
|
|
141
|
-
const result = await original.getJSONAsync(filename, createCallbackAsync);
|
|
220
|
+
const result = await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
142
221
|
|
|
143
222
|
expect(result).toEqual(json);
|
|
144
223
|
});
|
|
145
224
|
|
|
225
|
+
it('should return fallback JSON if file exists but json is not valid', async () => {
|
|
226
|
+
fileExists = true;
|
|
227
|
+
validation = false;
|
|
228
|
+
|
|
229
|
+
const result = await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
230
|
+
|
|
231
|
+
expect(result).toEqual(fallbackJSON);
|
|
232
|
+
});
|
|
233
|
+
|
|
146
234
|
it('should return fallback JSON if file not exists', async () => {
|
|
147
235
|
fileExists = false;
|
|
148
236
|
|
|
149
|
-
const result = await original.getJSONAsync(filename, createCallbackAsync);
|
|
237
|
+
const result = await original.getJSONAsync(filename, createCallbackAsync, validateCallbackAsync);
|
|
150
238
|
|
|
151
239
|
expect(result).toEqual(fallbackJSON);
|
|
152
240
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import * as colorette from 'colorette';
|
|
4
|
+
import open from 'open';
|
|
4
5
|
import type GoogleApis from 'googleapis';
|
|
5
6
|
import jsonLib from '../jsonLib';
|
|
6
7
|
import logger from '../logger';
|
|
@@ -9,13 +10,14 @@ import type { Secrets } from '../../types';
|
|
|
9
10
|
import secrets from '../secrets';
|
|
10
11
|
const original = jest.requireActual('../secrets').default as typeof secrets;
|
|
11
12
|
jest.mock<typeof secrets>('../secrets', () => ({
|
|
12
|
-
getScopes
|
|
13
|
-
getSecrets
|
|
14
|
-
getCredentials
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
getScopes : jest.fn().mockImplementation(() => scopesJSON),
|
|
14
|
+
getSecrets : jest.fn().mockImplementation(() => secretsJSON),
|
|
15
|
+
getCredentials : jest.fn(),
|
|
16
|
+
validateCredentials : jest.fn(),
|
|
17
|
+
createCredentials : jest.fn(),
|
|
18
|
+
checkSecrets : jest.fn(),
|
|
19
|
+
getScopesError : jest.fn().mockImplementation(() => scopesError),
|
|
20
|
+
getSecretsError : jest.fn().mockImplementation(() => secretsError),
|
|
19
21
|
}));
|
|
20
22
|
|
|
21
23
|
jest.mock<Partial<typeof http>>('http', () => ({
|
|
@@ -39,6 +41,8 @@ jest.mock<Partial<typeof colorette>>('colorette', () => ({
|
|
|
39
41
|
yellow : jest.fn().mockImplementation((text) => `yellow:${text}`),
|
|
40
42
|
}));
|
|
41
43
|
|
|
44
|
+
jest.mock<Partial<typeof open>>('open', () => jest.fn());
|
|
45
|
+
|
|
42
46
|
jest.mock<Partial<typeof jsonLib>>('../jsonLib', () => ({
|
|
43
47
|
getJSON : jest.fn().mockImplementation(() => json),
|
|
44
48
|
getJSONAsync : jest.fn().mockImplementation(async () => json),
|
|
@@ -89,7 +93,7 @@ const code = 'code';
|
|
|
89
93
|
const authUrl = 'https://authUrl';
|
|
90
94
|
const auth = {
|
|
91
95
|
generateAuthUrl : jest.fn().mockReturnValue(authUrl),
|
|
92
|
-
getToken
|
|
96
|
+
getToken : jest.fn().mockResolvedValue({ tokens : credentialsJSON }),
|
|
93
97
|
} as unknown as GoogleApis.Common.OAuth2Client;
|
|
94
98
|
|
|
95
99
|
let request: http.IncomingMessage;
|
|
@@ -255,6 +259,31 @@ describe('src/lib/secrets', () => {
|
|
|
255
259
|
});
|
|
256
260
|
});
|
|
257
261
|
|
|
262
|
+
describe('validateCredentials', () => {
|
|
263
|
+
it('should return false if no access token', async () => {
|
|
264
|
+
expect(await original.validateCredentials({})).toEqual(false);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should return true if no expiration', async () => {
|
|
268
|
+
// eslint-disable-next-line camelcase
|
|
269
|
+
expect(await original.validateCredentials({ access_token : 'token' })).toEqual(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should return true if credentials are not more than 1 week ago', async () => {
|
|
273
|
+
const expiryDate = new Date();
|
|
274
|
+
expiryDate.setDate(expiryDate.getDate() - 6);
|
|
275
|
+
// eslint-disable-next-line camelcase
|
|
276
|
+
expect(await original.validateCredentials({ access_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should return true if credentials are more than 1 week ago', async () => {
|
|
280
|
+
const expiryDate = new Date();
|
|
281
|
+
expiryDate.setDate(expiryDate.getDate() - 8);
|
|
282
|
+
// eslint-disable-next-line camelcase
|
|
283
|
+
expect(await original.validateCredentials({ access_token : 'token', expiry_date : expiryDate.getTime() })).toEqual(false);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
258
287
|
describe('createCredentials', () => {
|
|
259
288
|
function willOpen(request: http.IncomingMessage, timeout: number) {
|
|
260
289
|
setTimeout(async () => {
|
|
@@ -307,19 +336,30 @@ describe('src/lib/secrets', () => {
|
|
|
307
336
|
expect(listen).toBeCalledWith(6006);
|
|
308
337
|
});
|
|
309
338
|
|
|
310
|
-
it('should
|
|
339
|
+
it('should open browser page without prompt if this is permanent request that tends to save credentials in the file', async () => {
|
|
311
340
|
willOpen(request, 100);
|
|
312
341
|
|
|
313
342
|
await original.createCredentials(profile, auth);
|
|
314
343
|
|
|
344
|
+
expect(open).toBeCalledWith('https://authUrl');
|
|
345
|
+
expect(logger.info).not.toBeCalled();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should ask to open browser page', async () => {
|
|
349
|
+
willOpen(request, 100);
|
|
350
|
+
|
|
351
|
+
await original.createCredentials(profile, auth, { temporary : true });
|
|
352
|
+
|
|
353
|
+
expect(open).not.toBeCalled();
|
|
315
354
|
expect(logger.info).toBeCalledWith(`Please open yellow:https://authUrl in your browser using google profile for yellow:${profile} and allow access to yellow:https://www.googleapis.com/auth/calendar.calendars.readonly,https://www.googleapis.com/auth/calendar.events.readonly`);
|
|
316
355
|
});
|
|
317
356
|
|
|
318
357
|
it('should ask to open browser page with custom scopes', async () => {
|
|
319
358
|
willOpen(request, 100);
|
|
320
359
|
|
|
321
|
-
await original.createCredentials(profile, auth, { scopes : [ 'scope1', 'scope2' ] });
|
|
360
|
+
await original.createCredentials(profile, auth, { temporary : true, scopes : [ 'scope1', 'scope2' ] });
|
|
322
361
|
|
|
362
|
+
expect(open).not.toBeCalled();
|
|
323
363
|
expect(logger.info).toBeCalledWith(`Please open yellow:https://authUrl in your browser using google profile for yellow:${profile} and allow access to yellow:scope1,scope2`);
|
|
324
364
|
});
|
|
325
365
|
|
package/src/lib/jsonLib.ts
CHANGED
|
@@ -7,9 +7,13 @@ import jsonLib from './jsonLib';
|
|
|
7
7
|
export { getJSON, getJSONAsync, writeJSON };
|
|
8
8
|
export default { getJSON, getJSONAsync, writeJSON, readJSON, checkJSON };
|
|
9
9
|
|
|
10
|
-
function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any
|
|
10
|
+
function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<any>>, validateJSON?: (json: T) => boolean): T {
|
|
11
11
|
if (fs.existsSync(filename)) {
|
|
12
|
-
|
|
12
|
+
const json = jsonLib.readJSON<T>(filename);
|
|
13
|
+
|
|
14
|
+
if (!validateJSON || validateJSON(json)) {
|
|
15
|
+
return json;
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
const json = createCallback();
|
|
@@ -19,13 +23,18 @@ function getJSON<T>(filename: string, createCallback: () => Exclude<T, Promise<a
|
|
|
19
23
|
return json;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
async function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>): Promise<T> {
|
|
26
|
+
async function getJSONAsync<T>(filename: string, createCallbackAsync: () => Promise<T>, validateJSONAsync?: (json: T) => Promise<boolean>): Promise<T> {
|
|
23
27
|
if (fs.existsSync(filename)) {
|
|
24
|
-
|
|
28
|
+
const json = jsonLib.readJSON<T>(filename);
|
|
29
|
+
|
|
30
|
+
if (!validateJSONAsync || await validateJSONAsync(json)) {
|
|
31
|
+
return json;
|
|
32
|
+
}
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
const json = await createCallbackAsync();
|
|
28
36
|
jsonLib.checkJSON(filename, json);
|
|
37
|
+
ensureFile(filename);
|
|
29
38
|
jsonLib.writeJSON(filename, json);
|
|
30
39
|
return json;
|
|
31
40
|
}
|
package/src/lib/secrets.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import enableDestroy from 'server-destroy';
|
|
3
3
|
import * as colorette from 'colorette';
|
|
4
|
+
import open from 'open';
|
|
4
5
|
import type GoogleApis from 'googleapis';
|
|
5
6
|
import type { Secrets, AuthOptions } from '../types';
|
|
6
7
|
import { getJSON, getJSONAsync } from './jsonLib';
|
|
@@ -10,10 +11,11 @@ import { getScopesFile, getSecretsFile, getCredentialsFile } from './paths';
|
|
|
10
11
|
import secrets from './secrets';
|
|
11
12
|
|
|
12
13
|
export { getSecrets, getCredentials };
|
|
13
|
-
export default { getScopes, getSecrets, getCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
14
|
+
export default { getScopes, getSecrets, getCredentials, validateCredentials, createCredentials, checkSecrets, getSecretsError, getScopesError };
|
|
14
15
|
|
|
15
|
-
const callbackPort
|
|
16
|
-
const callbackURI
|
|
16
|
+
const callbackPort = 6006;
|
|
17
|
+
const callbackURI = `http://localhost:${callbackPort}/oauthcallback`;
|
|
18
|
+
const tokenExpiration = 7 * 24 * 60 * 60 * 1000;
|
|
17
19
|
|
|
18
20
|
function getScopes(): string[] {
|
|
19
21
|
const scopesFile = getScopesFile();
|
|
@@ -33,7 +35,19 @@ async function getCredentials(profile: string, auth: GoogleApis.Common.OAuth2Cli
|
|
|
33
35
|
|
|
34
36
|
return options?.temporary
|
|
35
37
|
? secrets.createCredentials(profile, auth, options)
|
|
36
|
-
: getJSONAsync(credentialsFile, () => secrets.createCredentials(profile, auth, options));
|
|
38
|
+
: getJSONAsync(credentialsFile, () => secrets.createCredentials(profile, auth, options), secrets.validateCredentials);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function validateCredentials(credentials: GoogleApis.Auth.Credentials): Promise<boolean> {
|
|
42
|
+
if (!credentials.access_token) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!credentials.expiry_date) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new Date().getTime() - credentials.expiry_date < tokenExpiration;
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Client, options?: AuthOptions): Promise<GoogleApis.Auth.Credentials> {
|
|
@@ -65,7 +79,12 @@ async function createCredentials(profile: string, auth: GoogleApis.Auth.OAuth2Cl
|
|
|
65
79
|
|
|
66
80
|
enableDestroy(server);
|
|
67
81
|
server.listen(callbackPort);
|
|
68
|
-
|
|
82
|
+
|
|
83
|
+
if (options?.temporary) {
|
|
84
|
+
info(`Please open ${colorette.yellow(authUrl)} in your browser using google profile for ${colorette.yellow(profile)} and allow access to ${colorette.yellow(scope.join(','))}`);
|
|
85
|
+
} else {
|
|
86
|
+
open(authUrl);
|
|
87
|
+
}
|
|
69
88
|
});
|
|
70
89
|
}
|
|
71
90
|
|