@posthog/wizard 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +5 -0
- package/dist/bin.js.map +1 -1
- package/dist/src/lib/agent-runner.js +2 -0
- package/dist/src/lib/agent-runner.js.map +1 -1
- package/dist/src/lib/constants.d.ts +1 -1
- package/dist/src/lib/version.d.ts +1 -1
- package/dist/src/lib/version.js +1 -1
- package/dist/src/lib/version.js.map +1 -1
- package/dist/src/lib/wizard-session.d.ts +4 -0
- package/dist/src/lib/wizard-session.js +2 -0
- package/dist/src/lib/wizard-session.js.map +1 -1
- package/dist/src/run.d.ts +1 -0
- package/dist/src/run.js +2 -0
- package/dist/src/run.js.map +1 -1
- package/dist/src/utils/__tests__/provisioning.test.d.ts +1 -0
- package/dist/src/utils/__tests__/provisioning.test.js +192 -0
- package/dist/src/utils/__tests__/provisioning.test.js.map +1 -0
- package/dist/src/utils/provisioning.d.ts +25 -0
- package/dist/src/utils/provisioning.js +191 -0
- package/dist/src/utils/provisioning.js.map +1 -0
- package/dist/src/utils/setup-utils.d.ts +4 -1
- package/dist/src/utils/setup-utils.js +46 -2
- package/dist/src/utils/setup-utils.js.map +1 -1
- package/dist/src/utils/types.d.ts +4 -0
- package/dist/src/utils/types.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const axios_1 = __importDefault(require("axios"));
|
|
7
|
+
const provisioning_1 = require("../provisioning");
|
|
8
|
+
jest.mock('axios');
|
|
9
|
+
jest.mock('../debug', () => ({ logToFile: jest.fn() }));
|
|
10
|
+
jest.mock('../analytics', () => ({
|
|
11
|
+
analytics: { captureException: jest.fn() },
|
|
12
|
+
}));
|
|
13
|
+
const mockedAxios = axios_1.default;
|
|
14
|
+
describe('provisionNewAccount', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
it('completes the full PKCE flow and returns credentials', async () => {
|
|
19
|
+
// Step 1: account_requests
|
|
20
|
+
mockedAxios.post.mockResolvedValueOnce({
|
|
21
|
+
data: {
|
|
22
|
+
id: 'req_1',
|
|
23
|
+
type: 'oauth',
|
|
24
|
+
oauth: { code: 'test_code_123' },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
// Step 2: oauth/token
|
|
28
|
+
mockedAxios.post.mockResolvedValueOnce({
|
|
29
|
+
data: {
|
|
30
|
+
token_type: 'bearer',
|
|
31
|
+
access_token: 'pha_test_access',
|
|
32
|
+
refresh_token: 'phr_test_refresh',
|
|
33
|
+
expires_in: 3600,
|
|
34
|
+
account: { id: 'org_123' },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
// Step 3: resources
|
|
38
|
+
mockedAxios.post.mockResolvedValueOnce({
|
|
39
|
+
data: {
|
|
40
|
+
status: 'complete',
|
|
41
|
+
id: '42',
|
|
42
|
+
service_id: 'analytics',
|
|
43
|
+
complete: {
|
|
44
|
+
access_configuration: {
|
|
45
|
+
api_key: 'phc_test_key',
|
|
46
|
+
host: 'https://us.posthog.com',
|
|
47
|
+
personal_api_key: 'phx_test_pat',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const result = await (0, provisioning_1.provisionNewAccount)('user@example.com', 'Test User');
|
|
53
|
+
expect(result).toEqual({
|
|
54
|
+
accessToken: 'pha_test_access',
|
|
55
|
+
refreshToken: 'phr_test_refresh',
|
|
56
|
+
projectApiKey: 'phc_test_key',
|
|
57
|
+
host: 'https://us.posthog.com',
|
|
58
|
+
personalApiKey: 'phx_test_pat',
|
|
59
|
+
projectId: '42',
|
|
60
|
+
accountId: 'org_123',
|
|
61
|
+
});
|
|
62
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(3);
|
|
63
|
+
// Verify account_requests call
|
|
64
|
+
const accountCall = mockedAxios.post.mock.calls[0];
|
|
65
|
+
expect(accountCall[0]).toContain('/account_requests');
|
|
66
|
+
expect(accountCall[1]).toMatchObject({
|
|
67
|
+
email: 'user@example.com',
|
|
68
|
+
name: 'Test User',
|
|
69
|
+
code_challenge_method: 'S256',
|
|
70
|
+
configuration: { region: 'US' },
|
|
71
|
+
});
|
|
72
|
+
expect(accountCall[1].code_challenge).toBeTruthy();
|
|
73
|
+
expect(accountCall[1].client_id).toBeTruthy();
|
|
74
|
+
// Verify token exchange includes code_verifier
|
|
75
|
+
const tokenCall = mockedAxios.post.mock.calls[1];
|
|
76
|
+
expect(tokenCall[0]).toContain('/oauth/token');
|
|
77
|
+
expect(tokenCall[1]).toContain('code_verifier=');
|
|
78
|
+
expect(tokenCall[1]).toContain('grant_type=authorization_code');
|
|
79
|
+
// Verify resources call uses bearer token
|
|
80
|
+
const resourceCall = mockedAxios.post.mock.calls[2];
|
|
81
|
+
expect(resourceCall[0]).toContain('/resources');
|
|
82
|
+
expect(resourceCall[2]?.headers?.Authorization).toBe('Bearer pha_test_access');
|
|
83
|
+
});
|
|
84
|
+
it('throws when account already exists', async () => {
|
|
85
|
+
mockedAxios.post.mockResolvedValueOnce({
|
|
86
|
+
data: {
|
|
87
|
+
id: 'req_2',
|
|
88
|
+
type: 'requires_auth',
|
|
89
|
+
requires_auth: { type: 'redirect', redirect: { url: 'https://...' } },
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
await expect((0, provisioning_1.provisionNewAccount)('existing@example.com', '')).rejects.toThrow('already associated');
|
|
93
|
+
});
|
|
94
|
+
it('throws on API error response', async () => {
|
|
95
|
+
mockedAxios.post.mockResolvedValueOnce({
|
|
96
|
+
data: {
|
|
97
|
+
id: 'req_3',
|
|
98
|
+
type: 'error',
|
|
99
|
+
error: { code: 'forbidden', message: 'Account creation disabled' },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
await expect((0, provisioning_1.provisionNewAccount)('blocked@example.com', '')).rejects.toThrow('Account creation disabled');
|
|
103
|
+
});
|
|
104
|
+
it('throws when resource provisioning fails', async () => {
|
|
105
|
+
mockedAxios.post
|
|
106
|
+
.mockResolvedValueOnce({
|
|
107
|
+
data: { id: 'req_4', type: 'oauth', oauth: { code: 'code_4' } },
|
|
108
|
+
})
|
|
109
|
+
.mockResolvedValueOnce({
|
|
110
|
+
data: {
|
|
111
|
+
token_type: 'bearer',
|
|
112
|
+
access_token: 'pha_4',
|
|
113
|
+
refresh_token: 'phr_4',
|
|
114
|
+
expires_in: 3600,
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
.mockResolvedValueOnce({
|
|
118
|
+
data: { status: 'error', id: '0', service_id: 'analytics' },
|
|
119
|
+
});
|
|
120
|
+
await expect((0, provisioning_1.provisionNewAccount)('fail@example.com', '')).rejects.toThrow('did not complete');
|
|
121
|
+
});
|
|
122
|
+
it('sends correct region parameter', async () => {
|
|
123
|
+
mockedAxios.post
|
|
124
|
+
.mockResolvedValueOnce({
|
|
125
|
+
data: { id: 'req_5', type: 'oauth', oauth: { code: 'code_5' } },
|
|
126
|
+
})
|
|
127
|
+
.mockResolvedValueOnce({
|
|
128
|
+
data: {
|
|
129
|
+
token_type: 'bearer',
|
|
130
|
+
access_token: 'pha_5',
|
|
131
|
+
refresh_token: 'phr_5',
|
|
132
|
+
expires_in: 3600,
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
.mockResolvedValueOnce({
|
|
136
|
+
data: {
|
|
137
|
+
status: 'complete',
|
|
138
|
+
id: '99',
|
|
139
|
+
service_id: 'analytics',
|
|
140
|
+
complete: {
|
|
141
|
+
access_configuration: {
|
|
142
|
+
api_key: 'phc_eu',
|
|
143
|
+
host: 'https://eu.posthog.com',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
const result = await (0, provisioning_1.provisionNewAccount)('eu@example.com', '', 'EU');
|
|
149
|
+
const accountCall = mockedAxios.post.mock.calls[0];
|
|
150
|
+
expect(accountCall[1].configuration).toEqual({
|
|
151
|
+
region: 'EU',
|
|
152
|
+
});
|
|
153
|
+
expect(result.host).toBe('https://eu.posthog.com');
|
|
154
|
+
});
|
|
155
|
+
it('includes timeouts on all requests', async () => {
|
|
156
|
+
mockedAxios.post
|
|
157
|
+
.mockResolvedValueOnce({
|
|
158
|
+
data: { id: 'req_6', type: 'oauth', oauth: { code: 'code_6' } },
|
|
159
|
+
})
|
|
160
|
+
.mockResolvedValueOnce({
|
|
161
|
+
data: {
|
|
162
|
+
token_type: 'bearer',
|
|
163
|
+
access_token: 'pha_6',
|
|
164
|
+
refresh_token: 'phr_6',
|
|
165
|
+
expires_in: 3600,
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
.mockResolvedValueOnce({
|
|
169
|
+
data: {
|
|
170
|
+
status: 'complete',
|
|
171
|
+
id: '1',
|
|
172
|
+
service_id: 'analytics',
|
|
173
|
+
complete: {
|
|
174
|
+
access_configuration: {
|
|
175
|
+
api_key: 'phc_t',
|
|
176
|
+
host: 'https://us.posthog.com',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
await (0, provisioning_1.provisionNewAccount)('timeout@example.com', '');
|
|
182
|
+
// account_requests and resources have config at index 2
|
|
183
|
+
const accountConfig = mockedAxios.post.mock.calls[0][2];
|
|
184
|
+
const resourceConfig = mockedAxios.post.mock.calls[2][2];
|
|
185
|
+
expect(accountConfig?.timeout).toBe(30_000);
|
|
186
|
+
expect(resourceConfig?.timeout).toBe(30_000);
|
|
187
|
+
// token exchange has config at index 2 (URL-encoded body is at index 1)
|
|
188
|
+
const tokenConfig = mockedAxios.post.mock.calls[1][2];
|
|
189
|
+
expect(tokenConfig?.timeout).toBe(30_000);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=provisioning.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.test.js","sourceRoot":"","sources":["../../../../src/utils/__tests__/provisioning.test.ts"],"names":[],"mappings":"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","sourcesContent":["import axios from 'axios';\nimport { provisionNewAccount } from '../provisioning';\n\njest.mock('axios');\njest.mock('../debug', () => ({ logToFile: jest.fn() }));\njest.mock('../analytics', () => ({\n analytics: { captureException: jest.fn() },\n}));\n\nconst mockedAxios = axios as jest.Mocked<typeof axios>;\n\ndescribe('provisionNewAccount', () => {\n beforeEach(() => {\n jest.clearAllMocks();\n });\n\n it('completes the full PKCE flow and returns credentials', async () => {\n // Step 1: account_requests\n mockedAxios.post.mockResolvedValueOnce({\n data: {\n id: 'req_1',\n type: 'oauth',\n oauth: { code: 'test_code_123' },\n },\n });\n\n // Step 2: oauth/token\n mockedAxios.post.mockResolvedValueOnce({\n data: {\n token_type: 'bearer',\n access_token: 'pha_test_access',\n refresh_token: 'phr_test_refresh',\n expires_in: 3600,\n account: { id: 'org_123' },\n },\n });\n\n // Step 3: resources\n mockedAxios.post.mockResolvedValueOnce({\n data: {\n status: 'complete',\n id: '42',\n service_id: 'analytics',\n complete: {\n access_configuration: {\n api_key: 'phc_test_key',\n host: 'https://us.posthog.com',\n personal_api_key: 'phx_test_pat',\n },\n },\n },\n });\n\n const result = await provisionNewAccount('user@example.com', 'Test User');\n\n expect(result).toEqual({\n accessToken: 'pha_test_access',\n refreshToken: 'phr_test_refresh',\n projectApiKey: 'phc_test_key',\n host: 'https://us.posthog.com',\n personalApiKey: 'phx_test_pat',\n projectId: '42',\n accountId: 'org_123',\n });\n\n expect(mockedAxios.post).toHaveBeenCalledTimes(3);\n\n // Verify account_requests call\n const accountCall = mockedAxios.post.mock.calls[0];\n expect(accountCall[0]).toContain('/account_requests');\n expect(accountCall[1]).toMatchObject({\n email: 'user@example.com',\n name: 'Test User',\n code_challenge_method: 'S256',\n configuration: { region: 'US' },\n });\n expect(\n (accountCall[1] as Record<string, unknown>).code_challenge,\n ).toBeTruthy();\n expect((accountCall[1] as Record<string, unknown>).client_id).toBeTruthy();\n\n // Verify token exchange includes code_verifier\n const tokenCall = mockedAxios.post.mock.calls[1];\n expect(tokenCall[0]).toContain('/oauth/token');\n expect(tokenCall[1]).toContain('code_verifier=');\n expect(tokenCall[1]).toContain('grant_type=authorization_code');\n\n // Verify resources call uses bearer token\n const resourceCall = mockedAxios.post.mock.calls[2];\n expect(resourceCall[0]).toContain('/resources');\n expect(resourceCall[2]?.headers?.Authorization).toBe(\n 'Bearer pha_test_access',\n );\n });\n\n it('throws when account already exists', async () => {\n mockedAxios.post.mockResolvedValueOnce({\n data: {\n id: 'req_2',\n type: 'requires_auth',\n requires_auth: { type: 'redirect', redirect: { url: 'https://...' } },\n },\n });\n\n await expect(\n provisionNewAccount('existing@example.com', ''),\n ).rejects.toThrow('already associated');\n });\n\n it('throws on API error response', async () => {\n mockedAxios.post.mockResolvedValueOnce({\n data: {\n id: 'req_3',\n type: 'error',\n error: { code: 'forbidden', message: 'Account creation disabled' },\n },\n });\n\n await expect(\n provisionNewAccount('blocked@example.com', ''),\n ).rejects.toThrow('Account creation disabled');\n });\n\n it('throws when resource provisioning fails', async () => {\n mockedAxios.post\n .mockResolvedValueOnce({\n data: { id: 'req_4', type: 'oauth', oauth: { code: 'code_4' } },\n })\n .mockResolvedValueOnce({\n data: {\n token_type: 'bearer',\n access_token: 'pha_4',\n refresh_token: 'phr_4',\n expires_in: 3600,\n },\n })\n .mockResolvedValueOnce({\n data: { status: 'error', id: '0', service_id: 'analytics' },\n });\n\n await expect(provisionNewAccount('fail@example.com', '')).rejects.toThrow(\n 'did not complete',\n );\n });\n\n it('sends correct region parameter', async () => {\n mockedAxios.post\n .mockResolvedValueOnce({\n data: { id: 'req_5', type: 'oauth', oauth: { code: 'code_5' } },\n })\n .mockResolvedValueOnce({\n data: {\n token_type: 'bearer',\n access_token: 'pha_5',\n refresh_token: 'phr_5',\n expires_in: 3600,\n },\n })\n .mockResolvedValueOnce({\n data: {\n status: 'complete',\n id: '99',\n service_id: 'analytics',\n complete: {\n access_configuration: {\n api_key: 'phc_eu',\n host: 'https://eu.posthog.com',\n },\n },\n },\n });\n\n const result = await provisionNewAccount('eu@example.com', '', 'EU');\n\n const accountCall = mockedAxios.post.mock.calls[0];\n expect((accountCall[1] as Record<string, unknown>).configuration).toEqual({\n region: 'EU',\n });\n expect(result.host).toBe('https://eu.posthog.com');\n });\n\n it('includes timeouts on all requests', async () => {\n mockedAxios.post\n .mockResolvedValueOnce({\n data: { id: 'req_6', type: 'oauth', oauth: { code: 'code_6' } },\n })\n .mockResolvedValueOnce({\n data: {\n token_type: 'bearer',\n access_token: 'pha_6',\n refresh_token: 'phr_6',\n expires_in: 3600,\n },\n })\n .mockResolvedValueOnce({\n data: {\n status: 'complete',\n id: '1',\n service_id: 'analytics',\n complete: {\n access_configuration: {\n api_key: 'phc_t',\n host: 'https://us.posthog.com',\n },\n },\n },\n });\n\n await provisionNewAccount('timeout@example.com', '');\n\n // account_requests and resources have config at index 2\n const accountConfig = mockedAxios.post.mock.calls[0][2] as\n | Record<string, unknown>\n | undefined;\n const resourceConfig = mockedAxios.post.mock.calls[2][2] as\n | Record<string, unknown>\n | undefined;\n expect(accountConfig?.timeout).toBe(30_000);\n expect(resourceConfig?.timeout).toBe(30_000);\n // token exchange has config at index 2 (URL-encoded body is at index 1)\n const tokenConfig = mockedAxios.post.mock.calls[1][2] as\n | Record<string, unknown>\n | undefined;\n expect(tokenConfig?.timeout).toBe(30_000);\n });\n});\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provisioning API client for creating new PostHog accounts.
|
|
3
|
+
*
|
|
4
|
+
* Uses the agentic provisioning API with PKCE auth:
|
|
5
|
+
* 1. POST /account_requests - create account, get auth code
|
|
6
|
+
* 2. POST /oauth/token - exchange code for tokens (with PKCE)
|
|
7
|
+
* 3. POST /resources - provision project, get API key
|
|
8
|
+
*/
|
|
9
|
+
export interface ProvisioningResult {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken: string;
|
|
12
|
+
projectApiKey: string;
|
|
13
|
+
host: string;
|
|
14
|
+
personalApiKey?: string;
|
|
15
|
+
projectId: string;
|
|
16
|
+
accountId: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a new PostHog account and provision a project via the provisioning API.
|
|
20
|
+
*
|
|
21
|
+
* This is the "no browser" signup path: the wizard collects the email,
|
|
22
|
+
* calls the provisioning API to create the account, and gets back
|
|
23
|
+
* credentials without opening a browser.
|
|
24
|
+
*/
|
|
25
|
+
export declare function provisionNewAccount(email: string, name: string, region?: 'US' | 'EU'): Promise<ProvisioningResult>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Provisioning API client for creating new PostHog accounts.
|
|
4
|
+
*
|
|
5
|
+
* Uses the agentic provisioning API with PKCE auth:
|
|
6
|
+
* 1. POST /account_requests - create account, get auth code
|
|
7
|
+
* 2. POST /oauth/token - exchange code for tokens (with PKCE)
|
|
8
|
+
* 3. POST /resources - provision project, get API key
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.provisionNewAccount = provisionNewAccount;
|
|
48
|
+
const crypto = __importStar(require("node:crypto"));
|
|
49
|
+
const axios_1 = __importDefault(require("axios"));
|
|
50
|
+
const zod_1 = require("zod");
|
|
51
|
+
const constants_1 = require("../lib/constants");
|
|
52
|
+
const debug_1 = require("./debug");
|
|
53
|
+
const analytics_1 = require("./analytics");
|
|
54
|
+
const WIZARD_CLIENT_ID = 'posthog-wizard';
|
|
55
|
+
const API_VERSION = '0.1d';
|
|
56
|
+
const PROVISIONING_BASE_URL = constants_1.IS_DEV
|
|
57
|
+
? 'http://localhost:8010'
|
|
58
|
+
: 'https://us.posthog.com';
|
|
59
|
+
function generateCodeVerifier() {
|
|
60
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
61
|
+
}
|
|
62
|
+
function generateCodeChallenge(verifier) {
|
|
63
|
+
return crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
64
|
+
}
|
|
65
|
+
// --- Response schemas ---
|
|
66
|
+
const AccountRequestResponseSchema = zod_1.z.object({
|
|
67
|
+
id: zod_1.z.string(),
|
|
68
|
+
type: zod_1.z.enum(['oauth', 'requires_auth', 'error']),
|
|
69
|
+
oauth: zod_1.z
|
|
70
|
+
.object({
|
|
71
|
+
code: zod_1.z.string(),
|
|
72
|
+
})
|
|
73
|
+
.optional(),
|
|
74
|
+
error: zod_1.z
|
|
75
|
+
.object({
|
|
76
|
+
code: zod_1.z.string(),
|
|
77
|
+
message: zod_1.z.string(),
|
|
78
|
+
})
|
|
79
|
+
.optional(),
|
|
80
|
+
});
|
|
81
|
+
const TokenResponseSchema = zod_1.z.object({
|
|
82
|
+
token_type: zod_1.z.string(),
|
|
83
|
+
access_token: zod_1.z.string(),
|
|
84
|
+
refresh_token: zod_1.z.string(),
|
|
85
|
+
expires_in: zod_1.z.number(),
|
|
86
|
+
account: zod_1.z
|
|
87
|
+
.object({
|
|
88
|
+
id: zod_1.z.string(),
|
|
89
|
+
})
|
|
90
|
+
.optional(),
|
|
91
|
+
});
|
|
92
|
+
const ResourceResponseSchema = zod_1.z.object({
|
|
93
|
+
status: zod_1.z.string(),
|
|
94
|
+
id: zod_1.z.string(),
|
|
95
|
+
service_id: zod_1.z.string(),
|
|
96
|
+
complete: zod_1.z
|
|
97
|
+
.object({
|
|
98
|
+
access_configuration: zod_1.z.object({
|
|
99
|
+
api_key: zod_1.z.string(),
|
|
100
|
+
host: zod_1.z.string(),
|
|
101
|
+
personal_api_key: zod_1.z.string().optional(),
|
|
102
|
+
}),
|
|
103
|
+
})
|
|
104
|
+
.optional(),
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Create a new PostHog account and provision a project via the provisioning API.
|
|
108
|
+
*
|
|
109
|
+
* This is the "no browser" signup path: the wizard collects the email,
|
|
110
|
+
* calls the provisioning API to create the account, and gets back
|
|
111
|
+
* credentials without opening a browser.
|
|
112
|
+
*/
|
|
113
|
+
async function provisionNewAccount(email, name, region = 'US') {
|
|
114
|
+
const codeVerifier = generateCodeVerifier();
|
|
115
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
116
|
+
(0, debug_1.logToFile)('[provisioning] starting account creation');
|
|
117
|
+
// Step 1: Create account
|
|
118
|
+
const accountRes = await axios_1.default.post(`${PROVISIONING_BASE_URL}/api/agentic/provisioning/account_requests`, {
|
|
119
|
+
id: crypto.randomUUID(),
|
|
120
|
+
email,
|
|
121
|
+
name,
|
|
122
|
+
client_id: WIZARD_CLIENT_ID,
|
|
123
|
+
code_challenge: codeChallenge,
|
|
124
|
+
code_challenge_method: 'S256',
|
|
125
|
+
configuration: { region },
|
|
126
|
+
}, {
|
|
127
|
+
headers: {
|
|
128
|
+
'Content-Type': 'application/json',
|
|
129
|
+
'API-Version': API_VERSION,
|
|
130
|
+
'User-Agent': constants_1.WIZARD_USER_AGENT,
|
|
131
|
+
},
|
|
132
|
+
timeout: 30_000,
|
|
133
|
+
});
|
|
134
|
+
const accountData = AccountRequestResponseSchema.parse(accountRes.data);
|
|
135
|
+
if (accountData.type === 'error') {
|
|
136
|
+
const msg = accountData.error?.message ?? 'Account creation failed';
|
|
137
|
+
analytics_1.analytics.captureException(new Error(msg), {
|
|
138
|
+
step: 'provisioning_account_request',
|
|
139
|
+
error_code: accountData.error?.code,
|
|
140
|
+
});
|
|
141
|
+
throw new Error(msg);
|
|
142
|
+
}
|
|
143
|
+
if (accountData.type === 'requires_auth') {
|
|
144
|
+
throw new Error('This email is already associated with a PostHog account. Please use the login flow instead.');
|
|
145
|
+
}
|
|
146
|
+
const code = accountData.oauth?.code;
|
|
147
|
+
if (!code) {
|
|
148
|
+
throw new Error('No authorization code received from account creation');
|
|
149
|
+
}
|
|
150
|
+
(0, debug_1.logToFile)('[provisioning] account created, exchanging code for tokens');
|
|
151
|
+
// Step 2: Exchange code for tokens
|
|
152
|
+
const tokenRes = await axios_1.default.post(`${PROVISIONING_BASE_URL}/api/agentic/oauth/token`, new URLSearchParams({
|
|
153
|
+
grant_type: 'authorization_code',
|
|
154
|
+
code,
|
|
155
|
+
code_verifier: codeVerifier,
|
|
156
|
+
}).toString(), {
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
159
|
+
'API-Version': API_VERSION,
|
|
160
|
+
'User-Agent': constants_1.WIZARD_USER_AGENT,
|
|
161
|
+
},
|
|
162
|
+
timeout: 30_000,
|
|
163
|
+
});
|
|
164
|
+
const tokenData = TokenResponseSchema.parse(tokenRes.data);
|
|
165
|
+
(0, debug_1.logToFile)('[provisioning] tokens received, provisioning resources');
|
|
166
|
+
// Step 3: Provision resources
|
|
167
|
+
const resourceRes = await axios_1.default.post(`${PROVISIONING_BASE_URL}/api/agentic/provisioning/resources`, { service_id: 'analytics' }, {
|
|
168
|
+
headers: {
|
|
169
|
+
'Content-Type': 'application/json',
|
|
170
|
+
Authorization: `Bearer ${tokenData.access_token}`,
|
|
171
|
+
'API-Version': API_VERSION,
|
|
172
|
+
'User-Agent': constants_1.WIZARD_USER_AGENT,
|
|
173
|
+
},
|
|
174
|
+
timeout: 30_000,
|
|
175
|
+
});
|
|
176
|
+
const resourceData = ResourceResponseSchema.parse(resourceRes.data);
|
|
177
|
+
if (resourceData.status !== 'complete' || !resourceData.complete) {
|
|
178
|
+
throw new Error('Resource provisioning did not complete');
|
|
179
|
+
}
|
|
180
|
+
(0, debug_1.logToFile)('[provisioning] resources provisioned successfully');
|
|
181
|
+
return {
|
|
182
|
+
accessToken: tokenData.access_token,
|
|
183
|
+
refreshToken: tokenData.refresh_token,
|
|
184
|
+
projectApiKey: resourceData.complete.access_configuration.api_key,
|
|
185
|
+
host: resourceData.complete.access_configuration.host,
|
|
186
|
+
personalApiKey: resourceData.complete.access_configuration.personal_api_key,
|
|
187
|
+
projectId: resourceData.id,
|
|
188
|
+
accountId: tokenData.account?.id ?? '',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=provisioning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../../../src/utils/provisioning.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provisioning API client for creating new PostHog accounts.\n *\n * Uses the agentic provisioning API with PKCE auth:\n * 1. POST /account_requests - create account, get auth code\n * 2. POST /oauth/token - exchange code for tokens (with PKCE)\n * 3. POST /resources - provision project, get API key\n */\n\nimport * as crypto from 'node:crypto';\nimport axios from 'axios';\nimport { z } from 'zod';\nimport { IS_DEV, WIZARD_USER_AGENT } from '../lib/constants';\nimport { logToFile } from './debug';\nimport { analytics } from './analytics';\n\nconst WIZARD_CLIENT_ID = 'posthog-wizard';\nconst API_VERSION = '0.1d';\n\nconst PROVISIONING_BASE_URL = IS_DEV\n ? 'http://localhost:8010'\n : 'https://us.posthog.com';\n\nfunction generateCodeVerifier(): string {\n return crypto.randomBytes(32).toString('base64url');\n}\n\nfunction generateCodeChallenge(verifier: string): string {\n return crypto.createHash('sha256').update(verifier).digest('base64url');\n}\n\n// --- Response schemas ---\n\nconst AccountRequestResponseSchema = z.object({\n id: z.string(),\n type: z.enum(['oauth', 'requires_auth', 'error']),\n oauth: z\n .object({\n code: z.string(),\n })\n .optional(),\n error: z\n .object({\n code: z.string(),\n message: z.string(),\n })\n .optional(),\n});\n\nconst TokenResponseSchema = z.object({\n token_type: z.string(),\n access_token: z.string(),\n refresh_token: z.string(),\n expires_in: z.number(),\n account: z\n .object({\n id: z.string(),\n })\n .optional(),\n});\n\nconst ResourceResponseSchema = z.object({\n status: z.string(),\n id: z.string(),\n service_id: z.string(),\n complete: z\n .object({\n access_configuration: z.object({\n api_key: z.string(),\n host: z.string(),\n personal_api_key: z.string().optional(),\n }),\n })\n .optional(),\n});\n\nexport interface ProvisioningResult {\n accessToken: string;\n refreshToken: string;\n projectApiKey: string;\n host: string;\n personalApiKey?: string;\n projectId: string;\n accountId: string;\n}\n\n/**\n * Create a new PostHog account and provision a project via the provisioning API.\n *\n * This is the \"no browser\" signup path: the wizard collects the email,\n * calls the provisioning API to create the account, and gets back\n * credentials without opening a browser.\n */\nexport async function provisionNewAccount(\n email: string,\n name: string,\n region: 'US' | 'EU' = 'US',\n): Promise<ProvisioningResult> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n\n logToFile('[provisioning] starting account creation');\n\n // Step 1: Create account\n const accountRes = await axios.post(\n `${PROVISIONING_BASE_URL}/api/agentic/provisioning/account_requests`,\n {\n id: crypto.randomUUID(),\n email,\n name,\n client_id: WIZARD_CLIENT_ID,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n configuration: { region },\n },\n {\n headers: {\n 'Content-Type': 'application/json',\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 30_000,\n },\n );\n\n const accountData = AccountRequestResponseSchema.parse(accountRes.data);\n\n if (accountData.type === 'error') {\n const msg = accountData.error?.message ?? 'Account creation failed';\n analytics.captureException(new Error(msg), {\n step: 'provisioning_account_request',\n error_code: accountData.error?.code,\n });\n throw new Error(msg);\n }\n\n if (accountData.type === 'requires_auth') {\n throw new Error(\n 'This email is already associated with a PostHog account. Please use the login flow instead.',\n );\n }\n\n const code = accountData.oauth?.code;\n if (!code) {\n throw new Error('No authorization code received from account creation');\n }\n\n logToFile('[provisioning] account created, exchanging code for tokens');\n\n // Step 2: Exchange code for tokens\n const tokenRes = await axios.post(\n `${PROVISIONING_BASE_URL}/api/agentic/oauth/token`,\n new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n code_verifier: codeVerifier,\n }).toString(),\n {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 30_000,\n },\n );\n\n const tokenData = TokenResponseSchema.parse(tokenRes.data);\n\n logToFile('[provisioning] tokens received, provisioning resources');\n\n // Step 3: Provision resources\n const resourceRes = await axios.post(\n `${PROVISIONING_BASE_URL}/api/agentic/provisioning/resources`,\n { service_id: 'analytics' },\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${tokenData.access_token}`,\n 'API-Version': API_VERSION,\n 'User-Agent': WIZARD_USER_AGENT,\n },\n timeout: 30_000,\n },\n );\n\n const resourceData = ResourceResponseSchema.parse(resourceRes.data);\n\n if (resourceData.status !== 'complete' || !resourceData.complete) {\n throw new Error('Resource provisioning did not complete');\n }\n\n logToFile('[provisioning] resources provisioned successfully');\n\n return {\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n projectApiKey: resourceData.complete.access_configuration.api_key,\n host: resourceData.complete.access_configuration.host,\n personalApiKey: resourceData.complete.access_configuration.personal_api_key,\n projectId: resourceData.id,\n accountId: tokenData.account?.id ?? '',\n };\n}\n"]}
|
|
@@ -63,7 +63,10 @@ export declare function isUsingTypeScript({ installDir, }: Pick<WizardOptions, '
|
|
|
63
63
|
/**
|
|
64
64
|
* Get project data for the wizard via OAuth or CI API key.
|
|
65
65
|
*/
|
|
66
|
-
export declare function getOrAskForProjectData(_options: Pick<WizardOptions, 'signup' | 'ci' | 'apiKey' | 'projectId'>
|
|
66
|
+
export declare function getOrAskForProjectData(_options: Pick<WizardOptions, 'signup' | 'ci' | 'apiKey' | 'projectId'> & {
|
|
67
|
+
email?: string;
|
|
68
|
+
region?: CloudRegion;
|
|
69
|
+
}): Promise<{
|
|
67
70
|
host: string;
|
|
68
71
|
projectApiKey: string;
|
|
69
72
|
accessToken: string;
|
|
@@ -58,6 +58,7 @@ const analytics_1 = require("./analytics");
|
|
|
58
58
|
const ui_1 = require("../ui");
|
|
59
59
|
const urls_1 = require("./urls");
|
|
60
60
|
const oauth_1 = require("./oauth");
|
|
61
|
+
const provisioning_1 = require("./provisioning");
|
|
61
62
|
const api_1 = require("../lib/api");
|
|
62
63
|
const semver_1 = require("./semver");
|
|
63
64
|
const wizard_abort_1 = require("./wizard-abort");
|
|
@@ -256,6 +257,8 @@ async function getOrAskForProjectData(_options) {
|
|
|
256
257
|
}
|
|
257
258
|
const { host, projectApiKey, accessToken, projectId, cloudRegion } = await (0, telemetry_1.traceStep)('login', () => askForWizardLogin({
|
|
258
259
|
signup: _options.signup,
|
|
260
|
+
email: _options.email,
|
|
261
|
+
region: _options.region,
|
|
259
262
|
}));
|
|
260
263
|
if (!projectApiKey) {
|
|
261
264
|
const cloudUrl = (0, urls_1.getCloudUrlFromRegion)(cloudRegion);
|
|
@@ -296,6 +299,9 @@ async function fetchProjectDataById(apiKey, projectId, cloudUrl) {
|
|
|
296
299
|
};
|
|
297
300
|
}
|
|
298
301
|
async function askForWizardLogin(options) {
|
|
302
|
+
if (options.signup) {
|
|
303
|
+
return askForProvisioningSignup(options.email, options.region);
|
|
304
|
+
}
|
|
299
305
|
const tokenResponse = await (0, oauth_1.performOAuthFlow)({
|
|
300
306
|
scopes: [
|
|
301
307
|
'user:read',
|
|
@@ -306,7 +312,7 @@ async function askForWizardLogin(options) {
|
|
|
306
312
|
'insight:write',
|
|
307
313
|
'query:read',
|
|
308
314
|
],
|
|
309
|
-
signup:
|
|
315
|
+
signup: false,
|
|
310
316
|
});
|
|
311
317
|
const projectId = tokenResponse.scoped_teams?.[0];
|
|
312
318
|
if (projectId === undefined) {
|
|
@@ -331,11 +337,49 @@ async function askForWizardLogin(options) {
|
|
|
331
337
|
projectId: projectId,
|
|
332
338
|
cloudRegion,
|
|
333
339
|
};
|
|
334
|
-
(0, ui_1.getUI)().log.success(
|
|
340
|
+
(0, ui_1.getUI)().log.success('Login complete.');
|
|
335
341
|
analytics_1.analytics.setTag('opened-wizard-link', true);
|
|
336
342
|
analytics_1.analytics.setDistinctId(data.distinctId);
|
|
337
343
|
return data;
|
|
338
344
|
}
|
|
345
|
+
async function askForProvisioningSignup(email, region) {
|
|
346
|
+
if (!email || !email.includes('@')) {
|
|
347
|
+
(0, ui_1.getUI)().log.error('Email is required for signup. Use --email your@email.com with --signup.');
|
|
348
|
+
await abort();
|
|
349
|
+
throw new Error('unreachable');
|
|
350
|
+
}
|
|
351
|
+
const spinner = (0, ui_1.getUI)().spinner();
|
|
352
|
+
spinner.start('Creating your PostHog account...');
|
|
353
|
+
try {
|
|
354
|
+
const provisionRegion = (region ?? 'us').toUpperCase();
|
|
355
|
+
const result = await (0, provisioning_1.provisionNewAccount)(email, '', provisionRegion);
|
|
356
|
+
spinner.stop('Account created!');
|
|
357
|
+
(0, ui_1.getUI)().log.success('Welcome to PostHog!');
|
|
358
|
+
const host = result.host;
|
|
359
|
+
const cloudRegion = host.includes('eu.') ? 'eu' : 'us';
|
|
360
|
+
analytics_1.analytics.setTag('provisioning-signup', true);
|
|
361
|
+
return {
|
|
362
|
+
accessToken: result.accessToken,
|
|
363
|
+
projectApiKey: result.projectApiKey,
|
|
364
|
+
host,
|
|
365
|
+
distinctId: email,
|
|
366
|
+
projectId: parseInt(result.projectId, 10) || 0,
|
|
367
|
+
cloudRegion,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
spinner.stop('Account creation failed.');
|
|
372
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
373
|
+
if (message.includes('already associated')) {
|
|
374
|
+
(0, ui_1.getUI)().log.info('This email already has a PostHog account. Switching to login flow...');
|
|
375
|
+
return askForWizardLogin({ signup: false });
|
|
376
|
+
}
|
|
377
|
+
(0, ui_1.getUI)().log.error(`Failed to create account: ${message}`);
|
|
378
|
+
analytics_1.analytics.captureException(error instanceof Error ? error : new Error(message), { step: 'provisioning_signup' });
|
|
379
|
+
await abort();
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
339
383
|
/**
|
|
340
384
|
* Creates a new config file with the given filepath and codeSnippet.
|
|
341
385
|
*/
|