@knocklabs/cli 0.1.23 → 0.2.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/README.md +128 -102
- package/bin/dev.js +4 -4
- package/dist/commands/branch/create.js +56 -0
- package/dist/commands/branch/delete.js +60 -0
- package/dist/commands/branch/list.js +89 -0
- package/dist/commands/knock.js +3 -0
- package/dist/commands/login.js +50 -0
- package/dist/commands/logout.js +48 -0
- package/dist/commands/whoami.js +9 -6
- package/dist/lib/api-v1.js +30 -7
- package/dist/lib/auth.js +256 -0
- package/dist/lib/base-command.js +85 -12
- package/dist/lib/helpers/arg.js +23 -0
- package/dist/lib/helpers/browser.js +25 -0
- package/dist/lib/helpers/request.js +48 -2
- package/dist/lib/marshal/index.isomorphic.js +8 -4
- package/dist/lib/marshal/reusable-step/helpers.js +72 -0
- package/dist/lib/marshal/reusable-step/index.js +19 -0
- package/dist/lib/marshal/reusable-step/processor.isomorphic.js +86 -0
- package/dist/lib/marshal/reusable-step/types.js +4 -0
- package/dist/lib/types.js +4 -0
- package/dist/lib/urls.js +32 -0
- package/dist/lib/user-config.js +69 -31
- package/oclif.manifest.json +393 -146
- package/package.json +11 -8
package/bin/dev.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node_modules/.bin/ts-node
|
|
2
2
|
// eslint-disable-next-line node/shebang, unicorn/prefer-top-level-await
|
|
3
|
-
|
|
4
|
-
const oclif = await import(
|
|
5
|
-
await oclif.execute({development: true, dir: __dirname})
|
|
6
|
-
})()
|
|
3
|
+
(async () => {
|
|
4
|
+
const oclif = await import("@oclif/core");
|
|
5
|
+
await oclif.execute({ development: true, dir: __dirname });
|
|
6
|
+
})();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "default", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return BranchCreate;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _basecommand = /*#__PURE__*/ _interop_require_default(require("../../lib/base-command"));
|
|
12
|
+
const _arg = require("../../lib/helpers/arg");
|
|
13
|
+
const _request = require("../../lib/helpers/request");
|
|
14
|
+
function _define_property(obj, key, value) {
|
|
15
|
+
if (key in obj) {
|
|
16
|
+
Object.defineProperty(obj, key, {
|
|
17
|
+
value: value,
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true
|
|
21
|
+
});
|
|
22
|
+
} else {
|
|
23
|
+
obj[key] = value;
|
|
24
|
+
}
|
|
25
|
+
return obj;
|
|
26
|
+
}
|
|
27
|
+
function _interop_require_default(obj) {
|
|
28
|
+
return obj && obj.__esModule ? obj : {
|
|
29
|
+
default: obj
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
class BranchCreate extends _basecommand.default {
|
|
33
|
+
async run() {
|
|
34
|
+
const { args, flags } = this.props;
|
|
35
|
+
const resp = await this.request(args.slug);
|
|
36
|
+
if (flags.json) return resp;
|
|
37
|
+
this.render(resp);
|
|
38
|
+
}
|
|
39
|
+
async request(slug) {
|
|
40
|
+
return (0, _request.withSpinnerV2)(()=>this.apiV1.mgmtClient.post(`/v1/branches/${slug}`));
|
|
41
|
+
}
|
|
42
|
+
async render(data) {
|
|
43
|
+
this.log(`‣ Successfully created branch \`${data.slug}\``);
|
|
44
|
+
this.log(` Created at: ${data.created_at}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Hide until branches are released in GA
|
|
48
|
+
_define_property(BranchCreate, "hidden", true);
|
|
49
|
+
_define_property(BranchCreate, "summary", "Creates a new branch off of the development environment.");
|
|
50
|
+
_define_property(BranchCreate, "enableJsonFlag", true);
|
|
51
|
+
_define_property(BranchCreate, "args", {
|
|
52
|
+
slug: _arg.CustomArgs.slugArg({
|
|
53
|
+
required: true,
|
|
54
|
+
description: "The slug for the new branch"
|
|
55
|
+
})
|
|
56
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "default", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return BranchDelete;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _core = require("@oclif/core");
|
|
12
|
+
const _basecommand = /*#__PURE__*/ _interop_require_default(require("../../lib/base-command"));
|
|
13
|
+
const _arg = require("../../lib/helpers/arg");
|
|
14
|
+
const _request = require("../../lib/helpers/request");
|
|
15
|
+
const _ux = require("../../lib/helpers/ux");
|
|
16
|
+
function _define_property(obj, key, value) {
|
|
17
|
+
if (key in obj) {
|
|
18
|
+
Object.defineProperty(obj, key, {
|
|
19
|
+
value: value,
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
obj[key] = value;
|
|
26
|
+
}
|
|
27
|
+
return obj;
|
|
28
|
+
}
|
|
29
|
+
function _interop_require_default(obj) {
|
|
30
|
+
return obj && obj.__esModule ? obj : {
|
|
31
|
+
default: obj
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
class BranchDelete extends _basecommand.default {
|
|
35
|
+
async run() {
|
|
36
|
+
const { args, flags } = this.props;
|
|
37
|
+
// Confirm before deleting the branch, unless forced
|
|
38
|
+
const prompt = `Delete branch \`${args.slug}\`?`;
|
|
39
|
+
const input = flags.force || await (0, _ux.promptToConfirm)(prompt);
|
|
40
|
+
if (!input) return;
|
|
41
|
+
await (0, _request.withSpinnerV2)(()=>this.apiV1.mgmtClient.delete(`/v1/branches/${args.slug}`), {
|
|
42
|
+
action: "‣ Deleting branch"
|
|
43
|
+
});
|
|
44
|
+
this.log(`‣ Successfully deleted branch \`${args.slug}\``);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Hide until branches are released in GA
|
|
48
|
+
_define_property(BranchDelete, "hidden", true);
|
|
49
|
+
_define_property(BranchDelete, "summary", "Deletes an existing branch with the given slug.");
|
|
50
|
+
_define_property(BranchDelete, "args", {
|
|
51
|
+
slug: _arg.CustomArgs.slugArg({
|
|
52
|
+
required: true,
|
|
53
|
+
description: "The slug of the branch to delete"
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
_define_property(BranchDelete, "flags", {
|
|
57
|
+
force: _core.Flags.boolean({
|
|
58
|
+
summary: "Remove the confirmation prompt."
|
|
59
|
+
})
|
|
60
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "default", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return BranchList;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _core = require("@oclif/core");
|
|
12
|
+
const _basecommand = /*#__PURE__*/ _interop_require_default(require("../../lib/base-command"));
|
|
13
|
+
const _date = require("../../lib/helpers/date");
|
|
14
|
+
const _page = require("../../lib/helpers/page");
|
|
15
|
+
const _request = require("../../lib/helpers/request");
|
|
16
|
+
function _define_property(obj, key, value) {
|
|
17
|
+
if (key in obj) {
|
|
18
|
+
Object.defineProperty(obj, key, {
|
|
19
|
+
value: value,
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
obj[key] = value;
|
|
26
|
+
}
|
|
27
|
+
return obj;
|
|
28
|
+
}
|
|
29
|
+
function _interop_require_default(obj) {
|
|
30
|
+
return obj && obj.__esModule ? obj : {
|
|
31
|
+
default: obj
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
class BranchList extends _basecommand.default {
|
|
35
|
+
async run() {
|
|
36
|
+
const resp = await this.request();
|
|
37
|
+
const { flags } = this.props;
|
|
38
|
+
if (flags.json) return resp;
|
|
39
|
+
this.render(resp);
|
|
40
|
+
}
|
|
41
|
+
async request(pageParams = {}) {
|
|
42
|
+
const queryParams = (0, _page.toPageParams)({
|
|
43
|
+
...this.props.flags,
|
|
44
|
+
...pageParams
|
|
45
|
+
});
|
|
46
|
+
return (0, _request.withSpinnerV2)(()=>this.apiV1.mgmtClient.get("/v1/branches", {
|
|
47
|
+
query: queryParams
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
async render(data) {
|
|
51
|
+
const { entries } = data;
|
|
52
|
+
this.log(`‣ Showing ${entries.length} branches off of the development environment\n`);
|
|
53
|
+
_core.ux.table(entries, {
|
|
54
|
+
slug: {
|
|
55
|
+
header: "Slug"
|
|
56
|
+
},
|
|
57
|
+
created_at: {
|
|
58
|
+
header: "Created at",
|
|
59
|
+
get: (entry)=>(0, _date.formatDate)(entry.created_at)
|
|
60
|
+
},
|
|
61
|
+
updated_at: {
|
|
62
|
+
header: "Updated at",
|
|
63
|
+
get: (entry)=>(0, _date.formatDate)(entry.updated_at)
|
|
64
|
+
},
|
|
65
|
+
last_commit_at: {
|
|
66
|
+
header: "Last commit at",
|
|
67
|
+
get: (entry)=>entry.last_commit_at ? (0, _date.formatDate)(entry.last_commit_at) : "Never"
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return this.prompt(data);
|
|
71
|
+
}
|
|
72
|
+
async prompt(data) {
|
|
73
|
+
const { page_info } = data;
|
|
74
|
+
const pageAction = await (0, _page.maybePromptPageAction)(page_info);
|
|
75
|
+
const pageParams = pageAction && (0, _page.paramsForPageAction)(pageAction, page_info);
|
|
76
|
+
if (pageParams) {
|
|
77
|
+
this.log("\n");
|
|
78
|
+
const resp = await this.request(pageParams);
|
|
79
|
+
return this.render(resp);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Hide until branches are released in GA
|
|
84
|
+
_define_property(BranchList, "hidden", true);
|
|
85
|
+
_define_property(BranchList, "summary", "Display all existing branches off of the development environment.");
|
|
86
|
+
_define_property(BranchList, "flags", {
|
|
87
|
+
..._page.pageFlags
|
|
88
|
+
});
|
|
89
|
+
_define_property(BranchList, "enableJsonFlag", true);
|
package/dist/commands/knock.js
CHANGED
|
@@ -112,6 +112,9 @@ class Knock extends _basecommand.default {
|
|
|
112
112
|
this.log("");
|
|
113
113
|
this.log("Thank you for using Knock, and have a nice day! 🙂");
|
|
114
114
|
}
|
|
115
|
+
constructor(...args){
|
|
116
|
+
super(...args), _define_property(this, "requiresAuth", false);
|
|
117
|
+
}
|
|
115
118
|
}
|
|
116
119
|
// Because, it's a secret :)
|
|
117
120
|
_define_property(Knock, "hidden", true);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "default", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return Login;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _auth = /*#__PURE__*/ _interop_require_default(require("../lib/auth"));
|
|
12
|
+
const _basecommand = /*#__PURE__*/ _interop_require_default(require("../lib/base-command"));
|
|
13
|
+
const _ux = require("../lib/helpers/ux");
|
|
14
|
+
function _define_property(obj, key, value) {
|
|
15
|
+
if (key in obj) {
|
|
16
|
+
Object.defineProperty(obj, key, {
|
|
17
|
+
value: value,
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true
|
|
21
|
+
});
|
|
22
|
+
} else {
|
|
23
|
+
obj[key] = value;
|
|
24
|
+
}
|
|
25
|
+
return obj;
|
|
26
|
+
}
|
|
27
|
+
function _interop_require_default(obj) {
|
|
28
|
+
return obj && obj.__esModule ? obj : {
|
|
29
|
+
default: obj
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
class Login extends _basecommand.default {
|
|
33
|
+
async run() {
|
|
34
|
+
const { flags } = this.props;
|
|
35
|
+
if (flags["service-token"]) {
|
|
36
|
+
this.log("Service token provided, skipping login.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
_ux.spinner.start("‣ Authenticating with Knock...");
|
|
40
|
+
const resp = await _auth.default.waitForAccessToken(this.sessionContext.dashboardOrigin, this.sessionContext.authOrigin);
|
|
41
|
+
_ux.spinner.stop();
|
|
42
|
+
await this.configStore.set({
|
|
43
|
+
userSession: resp
|
|
44
|
+
});
|
|
45
|
+
this.log("‣ Successfully authenticated with Knock.");
|
|
46
|
+
}
|
|
47
|
+
constructor(...args){
|
|
48
|
+
super(...args), _define_property(this, "requiresAuth", false);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "default", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return Logout;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _basecommand = /*#__PURE__*/ _interop_require_default(require("../lib/base-command"));
|
|
12
|
+
const _ux = require("../lib/helpers/ux");
|
|
13
|
+
function _define_property(obj, key, value) {
|
|
14
|
+
if (key in obj) {
|
|
15
|
+
Object.defineProperty(obj, key, {
|
|
16
|
+
value: value,
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true
|
|
20
|
+
});
|
|
21
|
+
} else {
|
|
22
|
+
obj[key] = value;
|
|
23
|
+
}
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
function _interop_require_default(obj) {
|
|
27
|
+
return obj && obj.__esModule ? obj : {
|
|
28
|
+
default: obj
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
class Logout extends _basecommand.default {
|
|
32
|
+
async run() {
|
|
33
|
+
const { flags } = this.props;
|
|
34
|
+
if (flags["service-token"]) {
|
|
35
|
+
this.log("Service token provided, skipping logout.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
_ux.spinner.start("‣ Logging out of Knock...");
|
|
39
|
+
await this.configStore.set({
|
|
40
|
+
userSession: undefined
|
|
41
|
+
});
|
|
42
|
+
_ux.spinner.stop();
|
|
43
|
+
this.log("‣ Successfully logged out of Knock. See you around.");
|
|
44
|
+
}
|
|
45
|
+
constructor(...args){
|
|
46
|
+
super(...args), _define_property(this, "requiresAuth", false);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -31,13 +31,16 @@ function _interop_require_default(obj) {
|
|
|
31
31
|
}
|
|
32
32
|
class Whoami extends _basecommand.default {
|
|
33
33
|
async run() {
|
|
34
|
-
const resp = await (0, _request.
|
|
34
|
+
const resp = await (0, _request.withSpinnerV2)(()=>this.apiV1.mgmtClient.auth.verify());
|
|
35
35
|
const { flags } = this.props;
|
|
36
|
-
if (flags.json) return resp
|
|
37
|
-
this.log(`‣ Successfully
|
|
38
|
-
const info = [
|
|
39
|
-
`Account name: ${resp.
|
|
40
|
-
`Service token name: ${resp.
|
|
36
|
+
if (flags.json) return resp;
|
|
37
|
+
this.log(`‣ Successfully authenticated:`);
|
|
38
|
+
const info = resp.service_token_name ? [
|
|
39
|
+
`Account name: ${resp.account_name}`,
|
|
40
|
+
`Service token name: ${resp.service_token_name}`
|
|
41
|
+
] : [
|
|
42
|
+
`Account name: ${resp.account_name}`,
|
|
43
|
+
`User ID: ${resp.user_id}`
|
|
41
44
|
];
|
|
42
45
|
this.log((0, _string.indentString)(info.join("\n"), 4));
|
|
43
46
|
}
|
package/dist/lib/api-v1.js
CHANGED
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "default", {
|
|
|
8
8
|
return ApiV1;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
const _mgmt = /*#__PURE__*/ _interop_require_default(require("@knocklabs/mgmt"));
|
|
11
12
|
const _axios = /*#__PURE__*/ _interop_require_default(require("axios"));
|
|
12
13
|
const _objectisomorphic = require("./helpers/object.isomorphic");
|
|
13
14
|
const _page = require("./helpers/page");
|
|
@@ -29,9 +30,16 @@ function _interop_require_default(obj) {
|
|
|
29
30
|
default: obj
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
|
-
const DEFAULT_ORIGIN = "https://control.knock.app";
|
|
33
33
|
const API_VERSION = "v1";
|
|
34
|
+
/**
|
|
35
|
+
* KnockMgmt client requires a service token, but we set the Authorization
|
|
36
|
+
* request header directly, so use a placeholder when service token is not
|
|
37
|
+
* provided.
|
|
38
|
+
*/ const PLACEHOLDER_SERVICE_TOKEN = "placeholder-service-token";
|
|
34
39
|
class ApiV1 {
|
|
40
|
+
getToken(sessionContext) {
|
|
41
|
+
return sessionContext.session ? sessionContext.session.accessToken : sessionContext.token;
|
|
42
|
+
}
|
|
35
43
|
async ping() {
|
|
36
44
|
return this.get("/ping");
|
|
37
45
|
}
|
|
@@ -401,17 +409,32 @@ class ApiV1 {
|
|
|
401
409
|
async put(subpath, data, config) {
|
|
402
410
|
return this.client.put(`/${API_VERSION}` + subpath, data, config);
|
|
403
411
|
}
|
|
404
|
-
constructor(
|
|
412
|
+
constructor(sessionContext, config){
|
|
413
|
+
var _sessionContext_session;
|
|
405
414
|
_define_property(this, "client", void 0);
|
|
406
|
-
|
|
415
|
+
_define_property(this, "mgmtClient", void 0);
|
|
416
|
+
const baseURL = sessionContext.apiOrigin;
|
|
417
|
+
const token = this.getToken(sessionContext);
|
|
418
|
+
var _sessionContext_session_clientId;
|
|
419
|
+
const headers = {
|
|
420
|
+
// Used to authenticate the request to the API.
|
|
421
|
+
Authorization: `Bearer ${token}`,
|
|
422
|
+
// Used in conjunction with the JWT access token, to allow the OAuth server to
|
|
423
|
+
// verify the client ID of the OAuth client that issued the access token.
|
|
424
|
+
"x-knock-client-id": (_sessionContext_session_clientId = (_sessionContext_session = sessionContext.session) === null || _sessionContext_session === void 0 ? void 0 : _sessionContext_session.clientId) !== null && _sessionContext_session_clientId !== void 0 ? _sessionContext_session_clientId : undefined,
|
|
425
|
+
"User-Agent": `${config.userAgent}`
|
|
426
|
+
};
|
|
407
427
|
this.client = _axios.default.create({
|
|
408
428
|
baseURL,
|
|
409
|
-
headers
|
|
410
|
-
Authorization: `Bearer ${flags["service-token"]}`,
|
|
411
|
-
"User-Agent": `${config.userAgent}`
|
|
412
|
-
},
|
|
429
|
+
headers,
|
|
413
430
|
// Don't reject the promise based on a response status code.
|
|
414
431
|
validateStatus: null
|
|
415
432
|
});
|
|
433
|
+
// This should eventually replace the Axios client
|
|
434
|
+
this.mgmtClient = new _mgmt.default({
|
|
435
|
+
serviceToken: sessionContext.token || PLACEHOLDER_SERVICE_TOKEN,
|
|
436
|
+
baseURL,
|
|
437
|
+
defaultHeaders: headers
|
|
438
|
+
});
|
|
416
439
|
}
|
|
417
440
|
}
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get default () {
|
|
13
|
+
return _default;
|
|
14
|
+
},
|
|
15
|
+
get exchangeCodeForToken () {
|
|
16
|
+
return exchangeCodeForToken;
|
|
17
|
+
},
|
|
18
|
+
get getOAuthServerUrls () {
|
|
19
|
+
return getOAuthServerUrls;
|
|
20
|
+
},
|
|
21
|
+
get refreshAccessToken () {
|
|
22
|
+
return refreshAccessToken;
|
|
23
|
+
},
|
|
24
|
+
get registerClient () {
|
|
25
|
+
return registerClient;
|
|
26
|
+
},
|
|
27
|
+
get waitForAccessToken () {
|
|
28
|
+
return waitForAccessToken;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
const _nodecrypto = /*#__PURE__*/ _interop_require_default(require("node:crypto"));
|
|
32
|
+
const _nodehttp = /*#__PURE__*/ _interop_require_default(require("node:http"));
|
|
33
|
+
const _browser = require("./helpers/browser");
|
|
34
|
+
const _urls = require("./urls");
|
|
35
|
+
function _interop_require_default(obj) {
|
|
36
|
+
return obj && obj.__esModule ? obj : {
|
|
37
|
+
default: obj
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
41
|
+
function createChallenge() {
|
|
42
|
+
// PKCE code verifier and challenge
|
|
43
|
+
const codeVerifier = _nodecrypto.default.randomBytes(32).toString("base64url");
|
|
44
|
+
const codeChallenge = _nodecrypto.default.createHash("sha256").update(codeVerifier).digest("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
45
|
+
const state = _nodecrypto.default.randomUUID();
|
|
46
|
+
return {
|
|
47
|
+
codeVerifier,
|
|
48
|
+
codeChallenge,
|
|
49
|
+
state
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function getOAuthServerUrls(apiUrl) {
|
|
53
|
+
const { protocol, host } = new URL(apiUrl);
|
|
54
|
+
const wellKnownUrl = `${protocol}//${host}/.well-known/oauth-authorization-server`;
|
|
55
|
+
const response = await fetch(wellKnownUrl, {
|
|
56
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT)
|
|
57
|
+
});
|
|
58
|
+
if (response.ok) {
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
return {
|
|
61
|
+
registrationEndpoint: data.registration_endpoint,
|
|
62
|
+
authorizationEndpoint: data.authorization_endpoint,
|
|
63
|
+
tokenEndpoint: data.token_endpoint,
|
|
64
|
+
issuer: data.issuer
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
throw new Error("Failed to fetch OAuth server metadata");
|
|
68
|
+
}
|
|
69
|
+
async function registerClient(registrationEndpoint, redirectUri) {
|
|
70
|
+
const registrationResponse = await fetch(registrationEndpoint, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
"Content-Type": "application/json"
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
client_name: "Knock CLI",
|
|
77
|
+
token_endpoint_auth_method: "none",
|
|
78
|
+
grant_types: [
|
|
79
|
+
"authorization_code"
|
|
80
|
+
],
|
|
81
|
+
response_types: [
|
|
82
|
+
"code"
|
|
83
|
+
],
|
|
84
|
+
redirect_uris: [
|
|
85
|
+
redirectUri
|
|
86
|
+
]
|
|
87
|
+
}),
|
|
88
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT)
|
|
89
|
+
});
|
|
90
|
+
if (!registrationResponse.ok) {
|
|
91
|
+
console.log(await registrationResponse.json());
|
|
92
|
+
throw new Error(`Could not register client with OAuth server`);
|
|
93
|
+
}
|
|
94
|
+
const registrationData = await registrationResponse.json();
|
|
95
|
+
return registrationData.client_id;
|
|
96
|
+
}
|
|
97
|
+
async function parseTokenResponse(response) {
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
return {
|
|
100
|
+
accessToken: data.access_token,
|
|
101
|
+
refreshToken: data.refresh_token,
|
|
102
|
+
idToken: data.id_token,
|
|
103
|
+
expiresAt: new Date(Date.now() + data.expires_in * 1000)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function exchangeCodeForToken(params) {
|
|
107
|
+
const response = await fetch(params.tokenEndpoint, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
111
|
+
},
|
|
112
|
+
body: new URLSearchParams({
|
|
113
|
+
grant_type: "authorization_code",
|
|
114
|
+
client_id: params.clientId,
|
|
115
|
+
code: params.code,
|
|
116
|
+
code_verifier: params.codeVerifier,
|
|
117
|
+
redirect_uri: params.redirectUri
|
|
118
|
+
}),
|
|
119
|
+
signal: AbortSignal.timeout(5000)
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
let errorDescription;
|
|
123
|
+
try {
|
|
124
|
+
const errorResponse = await response.json();
|
|
125
|
+
errorDescription = errorResponse.error_description || errorResponse.error;
|
|
126
|
+
} catch {
|
|
127
|
+
// ignore
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
error: errorDescription !== null && errorDescription !== void 0 ? errorDescription : "unknown error"
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
...await parseTokenResponse(response),
|
|
135
|
+
clientId: params.clientId
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function refreshAccessToken(params) {
|
|
139
|
+
const { tokenEndpoint } = await getOAuthServerUrls(params.authUrl);
|
|
140
|
+
const response = await fetch(tokenEndpoint, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
144
|
+
},
|
|
145
|
+
body: new URLSearchParams({
|
|
146
|
+
grant_type: "refresh_token",
|
|
147
|
+
client_id: params.clientId,
|
|
148
|
+
refresh_token: params.refreshToken
|
|
149
|
+
}),
|
|
150
|
+
signal: AbortSignal.timeout(5000)
|
|
151
|
+
});
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new Error("Failed to refresh access token");
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...await parseTokenResponse(response),
|
|
157
|
+
clientId: params.clientId
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function waitForAccessToken(dashboardUrl, authUrl) {
|
|
161
|
+
const { authorizationEndpoint, tokenEndpoint, registrationEndpoint } = await getOAuthServerUrls(authUrl);
|
|
162
|
+
let resolve;
|
|
163
|
+
let reject;
|
|
164
|
+
const promise = new Promise((res, rej)=>{
|
|
165
|
+
resolve = res;
|
|
166
|
+
reject = rej;
|
|
167
|
+
});
|
|
168
|
+
const { codeVerifier, codeChallenge, state } = createChallenge();
|
|
169
|
+
const timeout = setTimeout(()=>{
|
|
170
|
+
cleanupAndReject(`authentication timed out after ${DEFAULT_TIMEOUT / 1000} seconds`);
|
|
171
|
+
}, 60000);
|
|
172
|
+
function cleanupAndReject(message) {
|
|
173
|
+
cleanup();
|
|
174
|
+
reject(new Error(`Could not authenticate: ${message}`));
|
|
175
|
+
}
|
|
176
|
+
function cleanup() {
|
|
177
|
+
clearTimeout(timeout);
|
|
178
|
+
server.close();
|
|
179
|
+
server.closeAllConnections();
|
|
180
|
+
}
|
|
181
|
+
const server = _nodehttp.default.createServer();
|
|
182
|
+
server.listen();
|
|
183
|
+
const address = server.address();
|
|
184
|
+
if (address === null || typeof address !== "object") {
|
|
185
|
+
throw new Error("Could not start server");
|
|
186
|
+
}
|
|
187
|
+
const callbackPath = "/oauth_callback";
|
|
188
|
+
const redirectUri = `http://localhost:${address.port}${callbackPath}`;
|
|
189
|
+
const clientId = await registerClient(registrationEndpoint, redirectUri);
|
|
190
|
+
const params = {
|
|
191
|
+
response_type: "code",
|
|
192
|
+
client_id: clientId,
|
|
193
|
+
redirect_uri: redirectUri,
|
|
194
|
+
state,
|
|
195
|
+
code_challenge: codeChallenge,
|
|
196
|
+
code_challenge_method: "S256",
|
|
197
|
+
scope: "openid email offline_access"
|
|
198
|
+
};
|
|
199
|
+
const browserUrl = `${authorizationEndpoint}?${new URLSearchParams(params).toString()}`;
|
|
200
|
+
server.on("request", async (req, res)=>{
|
|
201
|
+
if (!clientId || !redirectUri) {
|
|
202
|
+
res.writeHead(500).end("Something went wrong");
|
|
203
|
+
cleanupAndReject("something went wrong");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
var _req_url;
|
|
207
|
+
const url = new URL((_req_url = req.url) !== null && _req_url !== void 0 ? _req_url : "/", "http://127.0.0.1");
|
|
208
|
+
if (url.pathname !== callbackPath) {
|
|
209
|
+
res.writeHead(404).end("Invalid path");
|
|
210
|
+
cleanupAndReject("invalid path");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const error = url.searchParams.get("error");
|
|
214
|
+
if (error) {
|
|
215
|
+
res.writeHead(400).end("Could not authenticate");
|
|
216
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
217
|
+
cleanupAndReject(`${errorDescription || error} `);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const code = url.searchParams.get("code");
|
|
221
|
+
if (!code) {
|
|
222
|
+
res.writeHead(400).end("Could not authenticate");
|
|
223
|
+
cleanupAndReject("no code provided");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const response = await exchangeCodeForToken({
|
|
227
|
+
tokenEndpoint,
|
|
228
|
+
clientId,
|
|
229
|
+
code,
|
|
230
|
+
codeVerifier,
|
|
231
|
+
redirectUri
|
|
232
|
+
});
|
|
233
|
+
if ("error" in response) {
|
|
234
|
+
res.writeHead(302, {
|
|
235
|
+
location: (0, _urls.authErrorUrl)(dashboardUrl, "Could not authenticate: unable to fetch access token")
|
|
236
|
+
}).end("Could not authenticate");
|
|
237
|
+
cleanupAndReject(JSON.stringify(response.error));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
res.writeHead(302, {
|
|
241
|
+
location: (0, _urls.authSuccessUrl)(dashboardUrl)
|
|
242
|
+
}).end("Authentication successful");
|
|
243
|
+
cleanup();
|
|
244
|
+
resolve({
|
|
245
|
+
...response,
|
|
246
|
+
clientId
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
console.log(`Opened web browser to facilitate auth: ${browserUrl}`);
|
|
250
|
+
_browser.browser.openUrl(browserUrl);
|
|
251
|
+
return promise;
|
|
252
|
+
}
|
|
253
|
+
const _default = {
|
|
254
|
+
waitForAccessToken,
|
|
255
|
+
refreshAccessToken
|
|
256
|
+
};
|