@credibledata/cred-cli 0.0.6
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/.github/workflows/build.yml +23 -0
- package/.mocharc.json +10 -0
- package/.prettierignore +2 -0
- package/.prettierrc +4 -0
- package/README-DEV.md +40 -0
- package/README.md +11 -0
- package/bun.lock +1158 -0
- package/dist/client/api.js +7715 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/base.js +69 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/common.js +162 -0
- package/dist/client/common.js.map +1 -0
- package/dist/client/configuration.js +44 -0
- package/dist/client/configuration.js.map +1 -0
- package/dist/client/index.js +32 -0
- package/dist/client/index.js.map +1 -0
- package/dist/clientconfiguration.js +205 -0
- package/dist/clientconfiguration.js.map +1 -0
- package/dist/clientconfiguration.spec.js +381 -0
- package/dist/clientconfiguration.spec.js.map +1 -0
- package/dist/commands/add.js +66 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/connection/createconnections.js +89 -0
- package/dist/commands/connection/createconnections.js.map +1 -0
- package/dist/commands/connection/createconnections.spec.js +219 -0
- package/dist/commands/connection/createconnections.spec.js.map +1 -0
- package/dist/commands/connection/deleteconnection.js +52 -0
- package/dist/commands/connection/deleteconnection.js.map +1 -0
- package/dist/commands/connection/deleteconnection.spec.js +130 -0
- package/dist/commands/connection/deleteconnection.spec.js.map +1 -0
- package/dist/commands/connection/listconnections.js +50 -0
- package/dist/commands/connection/listconnections.js.map +1 -0
- package/dist/commands/connection/listconnections.spec.js +117 -0
- package/dist/commands/connection/listconnections.spec.js.map +1 -0
- package/dist/commands/get.js +28 -0
- package/dist/commands/get.js.map +1 -0
- package/dist/commands/group/addmember.js +75 -0
- package/dist/commands/group/addmember.js.map +1 -0
- package/dist/commands/group/addmember.spec.js +148 -0
- package/dist/commands/group/addmember.spec.js.map +1 -0
- package/dist/commands/group/creategroup.js +55 -0
- package/dist/commands/group/creategroup.js.map +1 -0
- package/dist/commands/group/creategroup.spec.js +122 -0
- package/dist/commands/group/creategroup.spec.js.map +1 -0
- package/dist/commands/group/deletegroup.js +48 -0
- package/dist/commands/group/deletegroup.js.map +1 -0
- package/dist/commands/group/deletegroup.spec.js +104 -0
- package/dist/commands/group/deletegroup.spec.js.map +1 -0
- package/dist/commands/group/getgroup.js +50 -0
- package/dist/commands/group/getgroup.js.map +1 -0
- package/dist/commands/group/getgroup.spec.js +111 -0
- package/dist/commands/group/getgroup.spec.js.map +1 -0
- package/dist/commands/group/listgroups.js +56 -0
- package/dist/commands/group/listgroups.js.map +1 -0
- package/dist/commands/group/listgroups.spec.js +118 -0
- package/dist/commands/group/listgroups.spec.js.map +1 -0
- package/dist/commands/group/removemember.js +69 -0
- package/dist/commands/group/removemember.js.map +1 -0
- package/dist/commands/group/removemember.spec.js +138 -0
- package/dist/commands/group/removemember.spec.js.map +1 -0
- package/dist/commands/groupaccesstoken/createToken.js +53 -0
- package/dist/commands/groupaccesstoken/createToken.js.map +1 -0
- package/dist/commands/groupaccesstoken/createToken.spec.js +127 -0
- package/dist/commands/groupaccesstoken/createToken.spec.js.map +1 -0
- package/dist/commands/groupaccesstoken/deleteToken.js +24 -0
- package/dist/commands/groupaccesstoken/deleteToken.js.map +1 -0
- package/dist/commands/groupaccesstoken/deleteToken.spec.js +103 -0
- package/dist/commands/groupaccesstoken/deleteToken.spec.js.map +1 -0
- package/dist/commands/groupaccesstoken/groupContainer.js +71 -0
- package/dist/commands/groupaccesstoken/groupContainer.js.map +1 -0
- package/dist/commands/groupaccesstoken/listTokens.js +25 -0
- package/dist/commands/groupaccesstoken/listTokens.js.map +1 -0
- package/dist/commands/groupaccesstoken/listTokens.spec.js +115 -0
- package/dist/commands/groupaccesstoken/listTokens.spec.js.map +1 -0
- package/dist/commands/list.js +75 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/misc/login.js +290 -0
- package/dist/commands/misc/login.js.map +1 -0
- package/dist/commands/misc/login.spec.js +243 -0
- package/dist/commands/misc/login.spec.js.map +1 -0
- package/dist/commands/misc/logout.js +11 -0
- package/dist/commands/misc/logout.js.map +1 -0
- package/dist/commands/misc/logout.spec.js +30 -0
- package/dist/commands/misc/logout.spec.js.map +1 -0
- package/dist/commands/misc/loopbackOAuth.js +110 -0
- package/dist/commands/misc/loopbackOAuth.js.map +1 -0
- package/dist/commands/misc/set-access-token.js +27 -0
- package/dist/commands/misc/set-access-token.js.map +1 -0
- package/dist/commands/misc/set-access-token.spec.js +65 -0
- package/dist/commands/misc/set-access-token.spec.js.map +1 -0
- package/dist/commands/package/deletepackage.js +54 -0
- package/dist/commands/package/deletepackage.js.map +1 -0
- package/dist/commands/package/deletepackage.spec.js +136 -0
- package/dist/commands/package/deletepackage.spec.js.map +1 -0
- package/dist/commands/package/listpackages.js +57 -0
- package/dist/commands/package/listpackages.js.map +1 -0
- package/dist/commands/package/listpackages.spec.js +129 -0
- package/dist/commands/package/listpackages.spec.js.map +1 -0
- package/dist/commands/package/publish.js +157 -0
- package/dist/commands/package/publish.js.map +1 -0
- package/dist/commands/package/publish.spec.js +511 -0
- package/dist/commands/package/publish.spec.js.map +1 -0
- package/dist/commands/package/updatepackage.js +75 -0
- package/dist/commands/package/updatepackage.js.map +1 -0
- package/dist/commands/package/updatepackage.spec.js +189 -0
- package/dist/commands/package/updatepackage.spec.js.map +1 -0
- package/dist/commands/project/createproject.js +75 -0
- package/dist/commands/project/createproject.js.map +1 -0
- package/dist/commands/project/createproject.spec.js +154 -0
- package/dist/commands/project/createproject.spec.js.map +1 -0
- package/dist/commands/project/deleteproject.js +49 -0
- package/dist/commands/project/deleteproject.js.map +1 -0
- package/dist/commands/project/deleteproject.spec.js +106 -0
- package/dist/commands/project/deleteproject.spec.js.map +1 -0
- package/dist/commands/project/getproject.js +44 -0
- package/dist/commands/project/getproject.js.map +1 -0
- package/dist/commands/project/getproject.spec.js +99 -0
- package/dist/commands/project/getproject.spec.js.map +1 -0
- package/dist/commands/project/listprojects.js +52 -0
- package/dist/commands/project/listprojects.js.map +1 -0
- package/dist/commands/project/listprojects.spec.js +111 -0
- package/dist/commands/project/listprojects.spec.js.map +1 -0
- package/dist/commands/project/setproject.js +26 -0
- package/dist/commands/project/setproject.js.map +1 -0
- package/dist/commands/project/setproject.spec.js +58 -0
- package/dist/commands/project/setproject.spec.js.map +1 -0
- package/dist/commands/project/updateproject.js +68 -0
- package/dist/commands/project/updateproject.js.map +1 -0
- package/dist/commands/project/updateproject.spec.js +129 -0
- package/dist/commands/project/updateproject.spec.js.map +1 -0
- package/dist/commands/rm.js +68 -0
- package/dist/commands/rm.js.map +1 -0
- package/dist/commands/set.js +17 -0
- package/dist/commands/set.js.map +1 -0
- package/dist/commands/update.js +42 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/version/archive-unarchive-version.js +79 -0
- package/dist/commands/version/archive-unarchive-version.js.map +1 -0
- package/dist/commands/version/archive-unarchive-version.spec.js +244 -0
- package/dist/commands/version/archive-unarchive-version.spec.js.map +1 -0
- package/dist/commands/version/listversions.js +46 -0
- package/dist/commands/version/listversions.js.map +1 -0
- package/dist/commands/version/listversions.spec.js +121 -0
- package/dist/commands/version/listversions.spec.js.map +1 -0
- package/dist/common/UserGroupId.js +112 -0
- package/dist/common/UserGroupId.js.map +1 -0
- package/dist/common/UserGroupId.spec.js +92 -0
- package/dist/common/UserGroupId.spec.js.map +1 -0
- package/dist/constants.js +90 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.js +77 -0
- package/dist/logger.js.map +1 -0
- package/dist/logger.spec.js +125 -0
- package/dist/logger.spec.js.map +1 -0
- package/dist/polyfills/file.polyfill.js +6 -0
- package/dist/polyfills/file.polyfill.js.map +1 -0
- package/dist/utils.js +110 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.spec.js +158 -0
- package/dist/utils.spec.js.map +1 -0
- package/dist/validator.js +53 -0
- package/dist/validator.js.map +1 -0
- package/dist/validator.spec.js +41 -0
- package/dist/validator.spec.js.map +1 -0
- package/docker/Dockerfile.ci +18 -0
- package/docker-compose.yml +18 -0
- package/eslint.config.mjs +45 -0
- package/generate-client.sh +27 -0
- package/mocha.setup.ts +4 -0
- package/openapitools.json +7 -0
- package/package.json +75 -0
- package/src/client/.openapi-generator/FILES +9 -0
- package/src/client/.openapi-generator/VERSION +1 -0
- package/src/client/.openapi-generator-ignore +23 -0
- package/src/client/api.ts +9729 -0
- package/src/client/base.ts +86 -0
- package/src/client/common.ts +150 -0
- package/src/client/configuration.ts +110 -0
- package/src/client/git_push.sh +57 -0
- package/src/client/index.ts +18 -0
- package/src/clientconfiguration.spec.ts +490 -0
- package/src/clientconfiguration.ts +263 -0
- package/src/commands/add.ts +125 -0
- package/src/commands/connection/createconnections.spec.ts +403 -0
- package/src/commands/connection/createconnections.ts +109 -0
- package/src/commands/connection/deleteconnection.spec.ts +189 -0
- package/src/commands/connection/deleteconnection.ts +57 -0
- package/src/commands/connection/listconnections.spec.ts +132 -0
- package/src/commands/connection/listconnections.ts +58 -0
- package/src/commands/get.ts +31 -0
- package/src/commands/group/addmember.spec.ts +257 -0
- package/src/commands/group/addmember.ts +85 -0
- package/src/commands/group/creategroup.spec.ts +146 -0
- package/src/commands/group/creategroup.ts +54 -0
- package/src/commands/group/deletegroup.spec.ts +120 -0
- package/src/commands/group/deletegroup.ts +45 -0
- package/src/commands/group/getgroup.spec.ts +129 -0
- package/src/commands/group/getgroup.ts +47 -0
- package/src/commands/group/listgroups.spec.ts +137 -0
- package/src/commands/group/listgroups.ts +45 -0
- package/src/commands/group/removemember.spec.ts +224 -0
- package/src/commands/group/removemember.ts +75 -0
- package/src/commands/groupaccesstoken/createToken.spec.ts +211 -0
- package/src/commands/groupaccesstoken/createToken.ts +72 -0
- package/src/commands/groupaccesstoken/deleteToken.spec.ts +147 -0
- package/src/commands/groupaccesstoken/deleteToken.ts +29 -0
- package/src/commands/groupaccesstoken/groupContainer.ts +83 -0
- package/src/commands/groupaccesstoken/listTokens.spec.ts +147 -0
- package/src/commands/groupaccesstoken/listTokens.ts +29 -0
- package/src/commands/list.ts +131 -0
- package/src/commands/misc/login.spec.ts +213 -0
- package/src/commands/misc/login.ts +325 -0
- package/src/commands/misc/logout.spec.ts +32 -0
- package/src/commands/misc/logout.ts +9 -0
- package/src/commands/misc/loopbackOAuth.ts +127 -0
- package/src/commands/misc/set-access-token.spec.ts +95 -0
- package/src/commands/misc/set-access-token.ts +32 -0
- package/src/commands/package/deletepackage.spec.ts +164 -0
- package/src/commands/package/deletepackage.ts +58 -0
- package/src/commands/package/listpackages.spec.ts +147 -0
- package/src/commands/package/listpackages.ts +67 -0
- package/src/commands/package/publish.spec.ts +597 -0
- package/src/commands/package/publish.ts +211 -0
- package/src/commands/package/updatepackage.spec.ts +319 -0
- package/src/commands/package/updatepackage.ts +95 -0
- package/src/commands/project/createproject.spec.ts +247 -0
- package/src/commands/project/createproject.ts +70 -0
- package/src/commands/project/deleteproject.spec.ts +123 -0
- package/src/commands/project/deleteproject.ts +48 -0
- package/src/commands/project/getproject.spec.ts +109 -0
- package/src/commands/project/getproject.ts +43 -0
- package/src/commands/project/listprojects.spec.ts +121 -0
- package/src/commands/project/listprojects.ts +62 -0
- package/src/commands/project/setproject.spec.ts +63 -0
- package/src/commands/project/setproject.ts +14 -0
- package/src/commands/project/updateproject.spec.ts +217 -0
- package/src/commands/project/updateproject.ts +82 -0
- package/src/commands/rm.ts +101 -0
- package/src/commands/set.ts +16 -0
- package/src/commands/update.ts +64 -0
- package/src/commands/version/archive-unarchive-version.spec.ts +456 -0
- package/src/commands/version/archive-unarchive-version.ts +111 -0
- package/src/commands/version/listversions.spec.ts +138 -0
- package/src/commands/version/listversions.ts +45 -0
- package/src/common/UserGroupId.spec.ts +117 -0
- package/src/common/UserGroupId.ts +137 -0
- package/src/constants.ts +116 -0
- package/src/index.ts +209 -0
- package/src/logger.spec.ts +158 -0
- package/src/logger.ts +86 -0
- package/src/polyfills/file.polyfill.ts +6 -0
- package/src/utils.spec.ts +135 -0
- package/src/utils.ts +115 -0
- package/src/validator.spec.ts +51 -0
- package/src/validator.ts +44 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import sinon from "sinon";
|
|
7
|
+
import * as credConstants from "../../constants";
|
|
8
|
+
import { ClientConfiguration } from "../../clientconfiguration";
|
|
9
|
+
import { login } from "./login";
|
|
10
|
+
import { OrganizationsApi } from "../../client/api";
|
|
11
|
+
import * as loopbackOAuth from "./loopbackOAuth";
|
|
12
|
+
|
|
13
|
+
function createFakeIdToken(payload: Record<string, unknown>): string {
|
|
14
|
+
const header = Buffer.from(
|
|
15
|
+
JSON.stringify({ alg: "none", typ: "JWT" }),
|
|
16
|
+
).toString("base64url");
|
|
17
|
+
const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18
|
+
return `${header}.${body}.`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("login", () => {
|
|
22
|
+
let tmpConfigFilePath: string;
|
|
23
|
+
let axiosStub: sinon.SinonStub;
|
|
24
|
+
let orgPermissionsStub: sinon.SinonStub;
|
|
25
|
+
|
|
26
|
+
const mockDeviceCodeResponse = {
|
|
27
|
+
data: {
|
|
28
|
+
verification_uri_complete: "https://example.com",
|
|
29
|
+
user_code: "123456",
|
|
30
|
+
device_code: "device-code-123",
|
|
31
|
+
interval: 1,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const fakeJwtPayload = {
|
|
36
|
+
name: "Test User",
|
|
37
|
+
email: "test@example.com",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const mockedAccessTokenResponse = {
|
|
41
|
+
data: {
|
|
42
|
+
id_token: createFakeIdToken(fakeJwtPayload),
|
|
43
|
+
access_token: "mock-access-token",
|
|
44
|
+
refresh_token: "mock-refresh-token",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const mockOrgPermissionsResponse = {
|
|
49
|
+
status: 200,
|
|
50
|
+
data: {},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
tmpConfigFilePath = join(os.tmpdir(), "cred.json");
|
|
55
|
+
fs.writeFileSync(tmpConfigFilePath, "{}");
|
|
56
|
+
axiosStub = sinon.stub(axios, "request");
|
|
57
|
+
orgPermissionsStub = sinon
|
|
58
|
+
.stub(OrganizationsApi.prototype, "getOrganization")
|
|
59
|
+
.resolves(mockOrgPermissionsResponse as any);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
fs.rmSync(tmpConfigFilePath);
|
|
64
|
+
sinon.restore();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should not delete config properties on login", async function () {
|
|
68
|
+
this.timeout(2000);
|
|
69
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
70
|
+
config.setEnvironment("localhost");
|
|
71
|
+
config.setOrganization("foo");
|
|
72
|
+
config.setProject("bar");
|
|
73
|
+
axiosStub
|
|
74
|
+
.onFirstCall()
|
|
75
|
+
.resolves(mockDeviceCodeResponse)
|
|
76
|
+
.onSecondCall()
|
|
77
|
+
.resolves(mockedAccessTokenResponse);
|
|
78
|
+
await login("foo", "http://localhost", tmpConfigFilePath);
|
|
79
|
+
expect(config.getOrganization()).to.equal("foo");
|
|
80
|
+
expect(config.getProject()).to.equal("bar");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should change the active organization when login succeeds", async function () {
|
|
84
|
+
this.timeout(2000);
|
|
85
|
+
axiosStub
|
|
86
|
+
.onFirstCall()
|
|
87
|
+
.resolves(mockDeviceCodeResponse)
|
|
88
|
+
.onSecondCall()
|
|
89
|
+
.resolves(mockedAccessTokenResponse);
|
|
90
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
91
|
+
config.setEnvironment("localhost");
|
|
92
|
+
config.setOrganization("foo");
|
|
93
|
+
config.setProject("bar");
|
|
94
|
+
await login("test", "http://localhost", tmpConfigFilePath);
|
|
95
|
+
expect(config.getOrganization()).to.equal("test");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should set an access token when login succeeds", async function () {
|
|
99
|
+
this.timeout(2000);
|
|
100
|
+
axiosStub
|
|
101
|
+
.onFirstCall()
|
|
102
|
+
.resolves(mockDeviceCodeResponse)
|
|
103
|
+
.onSecondCall()
|
|
104
|
+
.resolves(mockedAccessTokenResponse);
|
|
105
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
106
|
+
config.setEnvironment("localhost");
|
|
107
|
+
await login("test", "http://localhost", tmpConfigFilePath);
|
|
108
|
+
expect(config.getAccessToken()).to.equal(
|
|
109
|
+
mockedAccessTokenResponse.data.access_token,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should unset the project field when switching organizations", async function () {
|
|
114
|
+
this.timeout(2000);
|
|
115
|
+
axiosStub
|
|
116
|
+
.onFirstCall()
|
|
117
|
+
.resolves(mockDeviceCodeResponse)
|
|
118
|
+
.onSecondCall()
|
|
119
|
+
.resolves(mockedAccessTokenResponse);
|
|
120
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
121
|
+
config.setEnvironment("localhost");
|
|
122
|
+
config.setOrganization("foo");
|
|
123
|
+
config.setProject("bar");
|
|
124
|
+
await login("not-foo", "http://localhost", tmpConfigFilePath);
|
|
125
|
+
expect(config.getProject()).to.equal(undefined);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should not unset the project field when logging in with the same organization", async function () {
|
|
129
|
+
this.timeout(2000);
|
|
130
|
+
axiosStub
|
|
131
|
+
.onFirstCall()
|
|
132
|
+
.resolves(mockDeviceCodeResponse)
|
|
133
|
+
.onSecondCall()
|
|
134
|
+
.resolves(mockedAccessTokenResponse);
|
|
135
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
136
|
+
config.setEnvironment("localhost");
|
|
137
|
+
config.setOrganization("foo");
|
|
138
|
+
config.setProject("bar");
|
|
139
|
+
await login("foo", "http://localhost", tmpConfigFilePath);
|
|
140
|
+
expect(config.getProject()).to.equal("bar");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("uses auth-code + PKCE with organization in authorize URL for mapped CLI orgs", async function () {
|
|
144
|
+
this.timeout(2000);
|
|
145
|
+
const orgIdStub = sinon
|
|
146
|
+
.stub(credConstants, "getAuth0OrganizationIdForCliLogin")
|
|
147
|
+
.returns("org_nMRKafeSuxOiuJeG");
|
|
148
|
+
const loopbackStub = sinon
|
|
149
|
+
.stub(loopbackOAuth, "authorizationCodeViaLoopback")
|
|
150
|
+
.callsFake(async (opts) => {
|
|
151
|
+
opts.onListening?.();
|
|
152
|
+
return { code: "test-pkce-auth-code" };
|
|
153
|
+
});
|
|
154
|
+
axiosStub.onFirstCall().resolves(mockedAccessTokenResponse);
|
|
155
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
156
|
+
config.setEnvironment("videoamp");
|
|
157
|
+
await login(
|
|
158
|
+
"videoamp",
|
|
159
|
+
"https://admin.videoamp-aws-us-east-1.credibledata.com/api/v0",
|
|
160
|
+
tmpConfigFilePath,
|
|
161
|
+
);
|
|
162
|
+
expect(orgIdStub.calledOnceWithExactly("videoamp")).to.be.true;
|
|
163
|
+
expect(loopbackStub.calledOnce).to.be.true;
|
|
164
|
+
const call = loopbackStub.getCall(0);
|
|
165
|
+
expect(call.args[0].authorizeUrl).to.contain(
|
|
166
|
+
"organization=org_nMRKafeSuxOiuJeG",
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("uses mapped org id for PKCE flow regardless of org name casing", async function () {
|
|
171
|
+
this.timeout(2000);
|
|
172
|
+
const orgIdStub = sinon
|
|
173
|
+
.stub(credConstants, "getAuth0OrganizationIdForCliLogin")
|
|
174
|
+
.callsFake((name: string) =>
|
|
175
|
+
name.toLowerCase() === "videoamp"
|
|
176
|
+
? "org_nMRKafeSuxOiuJeG"
|
|
177
|
+
: undefined,
|
|
178
|
+
);
|
|
179
|
+
const loopbackStub = sinon
|
|
180
|
+
.stub(loopbackOAuth, "authorizationCodeViaLoopback")
|
|
181
|
+
.callsFake(async (opts) => {
|
|
182
|
+
opts.onListening?.();
|
|
183
|
+
return { code: "test-pkce-auth-code" };
|
|
184
|
+
});
|
|
185
|
+
axiosStub.onFirstCall().resolves(mockedAccessTokenResponse);
|
|
186
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
187
|
+
config.setEnvironment("videoamp");
|
|
188
|
+
await login(
|
|
189
|
+
"VideoAmp",
|
|
190
|
+
"https://admin.videoamp-aws-us-east-1.credibledata.com/api/v0",
|
|
191
|
+
tmpConfigFilePath,
|
|
192
|
+
);
|
|
193
|
+
expect(orgIdStub.calledOnceWithExactly("VideoAmp")).to.be.true;
|
|
194
|
+
expect(loopbackStub.getCall(0).args[0].authorizeUrl).to.contain(
|
|
195
|
+
"organization=org_nMRKafeSuxOiuJeG",
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should not pass Auth0 organization for unmapped org names", async function () {
|
|
200
|
+
this.timeout(2000);
|
|
201
|
+
axiosStub
|
|
202
|
+
.onFirstCall()
|
|
203
|
+
.resolves(mockDeviceCodeResponse)
|
|
204
|
+
.onSecondCall()
|
|
205
|
+
.resolves(mockedAccessTokenResponse);
|
|
206
|
+
const config = new ClientConfiguration(tmpConfigFilePath);
|
|
207
|
+
config.setEnvironment("localhost");
|
|
208
|
+
await login("test", "http://localhost", tmpConfigFilePath);
|
|
209
|
+
expect(axiosStub.getCall(0).args[0].data).to.not.have.property(
|
|
210
|
+
"organization",
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import axios, { isAxiosError } from "axios";
|
|
2
|
+
import { jwtDecode } from "jwt-decode";
|
|
3
|
+
import { URLSearchParams } from "url";
|
|
4
|
+
import { OrganizationsApi } from "../../client/api";
|
|
5
|
+
import { Configuration } from "../../client/configuration";
|
|
6
|
+
import { ClientConfiguration } from "../../clientconfiguration";
|
|
7
|
+
import {
|
|
8
|
+
auth0ConfigsByEnv,
|
|
9
|
+
AUTH_CLI_LOOPBACK_REDIRECT_URI,
|
|
10
|
+
Environment,
|
|
11
|
+
getAuth0OrganizationIdForCliLogin,
|
|
12
|
+
LOGIN_TIMEOUT_MS,
|
|
13
|
+
} from "../../constants";
|
|
14
|
+
import { logger } from "../../logger";
|
|
15
|
+
import { printOutError, sleep } from "../../utils";
|
|
16
|
+
import * as loopbackOAuth from "./loopbackOAuth";
|
|
17
|
+
|
|
18
|
+
const organizationsApi = new OrganizationsApi(new Configuration());
|
|
19
|
+
|
|
20
|
+
export async function login(
|
|
21
|
+
organizationName: string,
|
|
22
|
+
controlPlaneBaseURL?: string,
|
|
23
|
+
configPath?: string,
|
|
24
|
+
) {
|
|
25
|
+
const config = new ClientConfiguration(configPath);
|
|
26
|
+
const environment = config.getEnvironment();
|
|
27
|
+
|
|
28
|
+
if (environment === "e2e-test") {
|
|
29
|
+
return loginM2M(organizationName, config);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const auth0OrganizationId =
|
|
33
|
+
getAuth0OrganizationIdForCliLogin(organizationName);
|
|
34
|
+
if (auth0OrganizationId) {
|
|
35
|
+
return loginWithAuthCodePkceOrganization(
|
|
36
|
+
environment,
|
|
37
|
+
organizationName,
|
|
38
|
+
controlPlaneBaseURL,
|
|
39
|
+
config,
|
|
40
|
+
auth0OrganizationId,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return loginWithDeviceCode(
|
|
45
|
+
environment,
|
|
46
|
+
organizationName,
|
|
47
|
+
controlPlaneBaseURL,
|
|
48
|
+
config,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function persistTokensAndVerifyOrganization(
|
|
53
|
+
tokenPayload: {
|
|
54
|
+
id_token?: string;
|
|
55
|
+
access_token: string;
|
|
56
|
+
refresh_token?: string;
|
|
57
|
+
},
|
|
58
|
+
organizationName: string,
|
|
59
|
+
controlPlaneBaseURL: string,
|
|
60
|
+
config: ClientConfiguration,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
if (tokenPayload.id_token) {
|
|
63
|
+
const decoded = jwtDecode(tokenPayload.id_token);
|
|
64
|
+
if (
|
|
65
|
+
typeof decoded === "object" &&
|
|
66
|
+
decoded !== null &&
|
|
67
|
+
"email" in decoded &&
|
|
68
|
+
"name" in decoded
|
|
69
|
+
) {
|
|
70
|
+
const user = decoded as { email?: string; name?: string };
|
|
71
|
+
config.setVSCodeUser(user.name, user.email);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
config.setAccessToken(true, tokenPayload.access_token);
|
|
76
|
+
config.setJwtAccessToken(tokenPayload.access_token);
|
|
77
|
+
if (tokenPayload.refresh_token) {
|
|
78
|
+
config.setRefreshToken(tokenPayload.refresh_token);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const response = await organizationsApi.getOrganization(
|
|
83
|
+
organizationName,
|
|
84
|
+
{
|
|
85
|
+
baseURL: controlPlaneBaseURL,
|
|
86
|
+
headers: config.getHeaders(),
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (response.status === 200) {
|
|
91
|
+
if (organizationName !== config.getOrganization()) {
|
|
92
|
+
config.setOrganization(organizationName);
|
|
93
|
+
config.setProject(undefined);
|
|
94
|
+
}
|
|
95
|
+
logger.info("Login successful");
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (!controlPlaneBaseURL.startsWith("https://")) {
|
|
99
|
+
const errorMsg =
|
|
100
|
+
error instanceof Error ? error.message : String(error);
|
|
101
|
+
logger.warn(
|
|
102
|
+
"Error with organziation check- might be bootstrapping. Ignoring: " +
|
|
103
|
+
errorMsg,
|
|
104
|
+
);
|
|
105
|
+
config.setOrganization(organizationName);
|
|
106
|
+
config.setProject(undefined);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isAxiosError(error)) {
|
|
111
|
+
if (error.response?.status === 403 || error.response?.status === 404) {
|
|
112
|
+
logger.error(
|
|
113
|
+
`The organization "${organizationName}" does not exist, or you do not have permission to access it.`,
|
|
114
|
+
);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function loginWithAuthCodePkceOrganization(
|
|
123
|
+
environment: Environment,
|
|
124
|
+
organizationName: string,
|
|
125
|
+
controlPlaneBaseURL: string,
|
|
126
|
+
config: ClientConfiguration,
|
|
127
|
+
auth0OrganizationId: string,
|
|
128
|
+
): Promise<void> {
|
|
129
|
+
const auth0Domain = auth0ConfigsByEnv[environment].domain;
|
|
130
|
+
const auth0ClientId = auth0ConfigsByEnv[environment].clientId;
|
|
131
|
+
const audience = auth0ConfigsByEnv[environment].audience;
|
|
132
|
+
|
|
133
|
+
const { codeVerifier, codeChallenge } = loopbackOAuth.generatePkcePair();
|
|
134
|
+
const state = loopbackOAuth.generateOAuthState();
|
|
135
|
+
|
|
136
|
+
const authorizeParams = new URLSearchParams({
|
|
137
|
+
response_type: "code",
|
|
138
|
+
client_id: auth0ClientId,
|
|
139
|
+
redirect_uri: AUTH_CLI_LOOPBACK_REDIRECT_URI,
|
|
140
|
+
scope: "openid profile email offline_access",
|
|
141
|
+
audience,
|
|
142
|
+
organization: auth0OrganizationId,
|
|
143
|
+
code_challenge: codeChallenge,
|
|
144
|
+
code_challenge_method: "S256",
|
|
145
|
+
state,
|
|
146
|
+
});
|
|
147
|
+
const authorizeUrl = `${auth0Domain}/authorize?${authorizeParams.toString()}`;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const { code } = await loopbackOAuth.authorizationCodeViaLoopback({
|
|
151
|
+
redirectUri: AUTH_CLI_LOOPBACK_REDIRECT_URI,
|
|
152
|
+
expectedState: state,
|
|
153
|
+
authorizeUrl,
|
|
154
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
155
|
+
onListening: () => {
|
|
156
|
+
logger.info(
|
|
157
|
+
`Please visit the following URL to login ${authorizeUrl}`,
|
|
158
|
+
);
|
|
159
|
+
logger.info("Waiting for login...");
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const tokenResponse = await axios.request({
|
|
164
|
+
method: "POST",
|
|
165
|
+
url: `${auth0Domain}/oauth/token`,
|
|
166
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
167
|
+
data: new URLSearchParams({
|
|
168
|
+
grant_type: "authorization_code",
|
|
169
|
+
client_id: auth0ClientId,
|
|
170
|
+
code,
|
|
171
|
+
redirect_uri: AUTH_CLI_LOOPBACK_REDIRECT_URI,
|
|
172
|
+
code_verifier: codeVerifier,
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
await persistTokensAndVerifyOrganization(
|
|
177
|
+
tokenResponse.data,
|
|
178
|
+
organizationName,
|
|
179
|
+
controlPlaneBaseURL,
|
|
180
|
+
config,
|
|
181
|
+
);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
if (isAxiosError(e)) {
|
|
184
|
+
const d = e.response?.data;
|
|
185
|
+
if (
|
|
186
|
+
typeof d === "object" &&
|
|
187
|
+
d !== null &&
|
|
188
|
+
"error" in d &&
|
|
189
|
+
d.error === "redirect_uri_mismatch"
|
|
190
|
+
) {
|
|
191
|
+
logger.error(
|
|
192
|
+
`Auth0 rejected the CLI callback URL. Add ${AUTH_CLI_LOOPBACK_REDIRECT_URI} to this application's Allowed Callback URLs in Auth0.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
printOutError(e, false);
|
|
196
|
+
} else {
|
|
197
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
198
|
+
logger.error(msg);
|
|
199
|
+
}
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function loginWithDeviceCode(
|
|
205
|
+
environment: Environment,
|
|
206
|
+
organizationName: string,
|
|
207
|
+
controlPlaneBaseURL: string,
|
|
208
|
+
config: ClientConfiguration,
|
|
209
|
+
) {
|
|
210
|
+
const auth0Domain = auth0ConfigsByEnv[environment].domain;
|
|
211
|
+
const auth0ClientId = auth0ConfigsByEnv[environment].clientId;
|
|
212
|
+
let deviceCodeData = null;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const deviceCode = await axios.request({
|
|
216
|
+
method: "POST",
|
|
217
|
+
url: `${auth0Domain}/oauth/device/code`,
|
|
218
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
219
|
+
data: {
|
|
220
|
+
client_id: auth0ClientId,
|
|
221
|
+
audience: auth0ConfigsByEnv[environment].audience,
|
|
222
|
+
scope: "openid profile email offline_access",
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
deviceCodeData = deviceCode.data;
|
|
226
|
+
|
|
227
|
+
logger.info(
|
|
228
|
+
`Please visit the following URL to login ${deviceCode.data.verification_uri_complete} and verify user code ${deviceCode.data.user_code}.`,
|
|
229
|
+
);
|
|
230
|
+
logger.info("Waiting for login...");
|
|
231
|
+
} catch (e) {
|
|
232
|
+
printOutError(e, false);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const startTime = Date.now();
|
|
237
|
+
|
|
238
|
+
while (true) {
|
|
239
|
+
try {
|
|
240
|
+
const accessToken = await axios.request({
|
|
241
|
+
method: "POST",
|
|
242
|
+
url: `${auth0Domain}/oauth/token`,
|
|
243
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
244
|
+
data: new URLSearchParams({
|
|
245
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
246
|
+
device_code: deviceCodeData.device_code,
|
|
247
|
+
client_id: auth0ClientId,
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
await persistTokensAndVerifyOrganization(
|
|
251
|
+
accessToken.data,
|
|
252
|
+
organizationName,
|
|
253
|
+
controlPlaneBaseURL,
|
|
254
|
+
config,
|
|
255
|
+
);
|
|
256
|
+
break;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (
|
|
259
|
+
isAxiosError(error) &&
|
|
260
|
+
error.response?.data?.error !== "authorization_pending"
|
|
261
|
+
) {
|
|
262
|
+
const errorData = error.response?.data;
|
|
263
|
+
if (errorData) {
|
|
264
|
+
if (typeof errorData === "string") {
|
|
265
|
+
logger.error(errorData);
|
|
266
|
+
} else if (errorData.error_description) {
|
|
267
|
+
logger.error(errorData.error_description);
|
|
268
|
+
} else if (errorData.error) {
|
|
269
|
+
logger.error(errorData.error);
|
|
270
|
+
} else {
|
|
271
|
+
logger.error(JSON.stringify(errorData));
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
const errorMsg =
|
|
275
|
+
error instanceof Error ? error.message : String(error);
|
|
276
|
+
logger.error(errorMsg || "Unknown error occurred");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (Date.now() - startTime > LOGIN_TIMEOUT_MS) {
|
|
280
|
+
logger.info("Login timed out after 5 minutes");
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
await sleep(deviceCodeData.interval * 1000);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function loginM2M(
|
|
290
|
+
organizationName: string,
|
|
291
|
+
config: ClientConfiguration,
|
|
292
|
+
): Promise<void> {
|
|
293
|
+
const auth0Config = auth0ConfigsByEnv["e2e-test"];
|
|
294
|
+
|
|
295
|
+
if (!auth0Config) {
|
|
296
|
+
throw new Error("No environment variables present for e2e");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const response = await axios.request({
|
|
301
|
+
method: "POST",
|
|
302
|
+
url: `${auth0Config.domain}/oauth/token`,
|
|
303
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
304
|
+
data: {
|
|
305
|
+
grant_type: "client_credentials",
|
|
306
|
+
client_id: auth0Config.clientId,
|
|
307
|
+
client_secret: auth0Config.clientSecret,
|
|
308
|
+
audience: auth0Config.audience,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const token = response.data.access_token;
|
|
313
|
+
|
|
314
|
+
if (!token) {
|
|
315
|
+
throw new Error("No access token received");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
config.destroyConfig();
|
|
319
|
+
config.setAccessToken(true, token);
|
|
320
|
+
config.setOrganization(organizationName);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
printOutError(e, false);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import sinon from "sinon";
|
|
3
|
+
import { ClientConfiguration } from "../../clientconfiguration";
|
|
4
|
+
import { logger } from "../../logger";
|
|
5
|
+
import { logout } from "./logout";
|
|
6
|
+
|
|
7
|
+
describe("logout", () => {
|
|
8
|
+
let sandbox: sinon.SinonSandbox;
|
|
9
|
+
let destroyConfigStub: sinon.SinonStub;
|
|
10
|
+
let loggerStub: sinon.SinonStubbedInstance<typeof logger>;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
sandbox = sinon.createSandbox();
|
|
14
|
+
loggerStub = sandbox.stub(logger);
|
|
15
|
+
destroyConfigStub = sandbox.stub(
|
|
16
|
+
ClientConfiguration.prototype,
|
|
17
|
+
"destroyConfig",
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
sandbox.restore();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should destroy config and log success message", () => {
|
|
26
|
+
logout();
|
|
27
|
+
|
|
28
|
+
expect(destroyConfigStub.calledOnce).to.be.true;
|
|
29
|
+
expect(loggerStub.info.calledWith(sinon.match("Credentials reset"))).to.be
|
|
30
|
+
.true;
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "crypto";
|
|
2
|
+
import http from "http";
|
|
3
|
+
import { URL } from "url";
|
|
4
|
+
|
|
5
|
+
export function generatePkcePair(): {
|
|
6
|
+
codeVerifier: string;
|
|
7
|
+
codeChallenge: string;
|
|
8
|
+
} {
|
|
9
|
+
const codeVerifier = randomBytes(32).toString("base64url");
|
|
10
|
+
const codeChallenge = createHash("sha256")
|
|
11
|
+
.update(codeVerifier)
|
|
12
|
+
.digest("base64url");
|
|
13
|
+
return { codeVerifier, codeChallenge };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function generateOAuthState(): string {
|
|
17
|
+
return randomBytes(24).toString("base64url");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Listens on loopback `redirectUri` for the OAuth redirect, then exchanges are done by the caller.
|
|
22
|
+
* Caller should print `authorizeUrl` as plain text (e.g. in {@link onListening}) — no browser auto-open.
|
|
23
|
+
* Used when the CLI must send `organization` on `/authorize` (Auth0 Organizations); the documented
|
|
24
|
+
* device-code endpoint does not list an `organization` parameter
|
|
25
|
+
* (https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow/call-your-api-using-the-device-authorization-flow#device-code-parameters).
|
|
26
|
+
*/
|
|
27
|
+
export async function authorizationCodeViaLoopback(options: {
|
|
28
|
+
redirectUri: string;
|
|
29
|
+
expectedState: string;
|
|
30
|
+
authorizeUrl: string;
|
|
31
|
+
timeoutMs: number;
|
|
32
|
+
/** Invoked once the loopback server is ready (safe to tell the user to open the URL). */
|
|
33
|
+
onListening?: () => void;
|
|
34
|
+
}): Promise<{ code: string }> {
|
|
35
|
+
const {
|
|
36
|
+
redirectUri,
|
|
37
|
+
expectedState,
|
|
38
|
+
authorizeUrl: _authorizeUrl,
|
|
39
|
+
timeoutMs,
|
|
40
|
+
onListening,
|
|
41
|
+
} = options;
|
|
42
|
+
const parsed = new URL(redirectUri);
|
|
43
|
+
const port = Number(parsed.port);
|
|
44
|
+
const pathname = parsed.pathname;
|
|
45
|
+
|
|
46
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Invalid redirect URI (need explicit port): ${redirectUri}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
let settled = false;
|
|
54
|
+
// eslint-disable-next-line prefer-const -- assign after finish/timer (handler may run before `const` bindings exist)
|
|
55
|
+
let server: http.Server | undefined;
|
|
56
|
+
|
|
57
|
+
const finish = (err: Error | null, result?: { code: string }) => {
|
|
58
|
+
if (settled) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
settled = true;
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
server?.close();
|
|
64
|
+
if (err) {
|
|
65
|
+
reject(err);
|
|
66
|
+
} else {
|
|
67
|
+
resolve(result!);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
finish(
|
|
73
|
+
new Error(
|
|
74
|
+
`Login timed out after ${Math.round(timeoutMs / 1000)} seconds`,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}, timeoutMs);
|
|
78
|
+
|
|
79
|
+
server = http.createServer((req, res) => {
|
|
80
|
+
if (settled || !req.url) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const reqUrl = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
85
|
+
if (reqUrl.pathname !== pathname) {
|
|
86
|
+
res.writeHead(404);
|
|
87
|
+
res.end();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const code = reqUrl.searchParams.get("code");
|
|
91
|
+
const state = reqUrl.searchParams.get("state");
|
|
92
|
+
const error = reqUrl.searchParams.get("error");
|
|
93
|
+
|
|
94
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
95
|
+
res.end(
|
|
96
|
+
"<!DOCTYPE html><html><body><p>You can close this tab and return to the terminal.</p></body></html>",
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (error) {
|
|
100
|
+
finish(
|
|
101
|
+
new Error(
|
|
102
|
+
reqUrl.searchParams.get("error_description") || error,
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!code || state !== expectedState) {
|
|
108
|
+
finish(new Error("Invalid authorization response"));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
finish(null, { code });
|
|
112
|
+
} catch (e) {
|
|
113
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
server.on("error", (err) => finish(err));
|
|
118
|
+
|
|
119
|
+
server.listen(port, "127.0.0.1", () => {
|
|
120
|
+
try {
|
|
121
|
+
onListening?.();
|
|
122
|
+
} catch (e) {
|
|
123
|
+
finish(e instanceof Error ? e : new Error(String(e)));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|