@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.
@@ -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'>): Promise<{
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: options.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(`Login complete. ${options.signup ? 'Welcome to PostHog!' : ''}`);
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
  */