Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/auth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const CALLBACK_PORT = 18991;
const CALLBACK_PATH = '/callback';
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
const BROWSER_TIMEOUT_MS = 120_000;
// Creating an account (provider round-trip + consent) takes longer than a sign-in.
// Any /signup entry (account creation or a provider-tagged sign-in) adds a
// provider round-trip before the consent hop, so it gets a longer budget.
const SIGNUP_BROWSER_TIMEOUT_MS = 300_000;

// The dotted process.env reads below are frozen into the bundle by build.ts's
Expand Down Expand Up @@ -363,8 +364,13 @@ export interface BrowserFlowOptions {
// (the console keeps it alive across the provider round-trip), so a brand-new
// user creates the account and lands on the consent screen in one browser trip.
signupEntry?: boolean;
// Which provider the user picked in the CLI. The console signup page
// auto-starts that provider's OAuth without another click.
// Which provider the user picked in the CLI. Setting this routes the browser
// through the console signup page — with or without signupEntry — because
// that page auto-starts the provider's OAuth without another click and its
// login composables are shared with sign-in, so existing accounts just sign
// in. Opening the consent URL directly instead would bounce an
// unauthenticated browser to the generic sign-in page (its auth middleware),
// stranding the user a click away from the provider they already chose.
provider?: 'google' | 'github';
}

Expand Down Expand Up @@ -396,7 +402,7 @@ export function buildBrowserFlowUrls(

let openUrl = authUrl;
let timeoutMs = BROWSER_TIMEOUT_MS;
if (options.signupEntry) {
if (options.signupEntry || options.provider) {
openUrl = new URL(`${consoleBaseUrl(config)}/signup`);
openUrl.searchParams.set('redirect', `${authUrl.pathname}${authUrl.search}`);
if (options.provider) openUrl.searchParams.set('provider', options.provider);
Expand All @@ -405,6 +411,8 @@ export function buildBrowserFlowUrls(
const ref = readInstallRef();
if (ref) openUrl.searchParams.set('ref', ref);
if (runId) openUrl.searchParams.set(ONBOARDING_RUN_QUERY_PARAM, runId);
// The provider round-trip takes longer than a plain consent hop whether or
// not the account is new, so every /signup entry gets the longer budget.
timeoutMs = SIGNUP_BROWSER_TIMEOUT_MS;
}

Expand Down
37 changes: 37 additions & 0 deletions test/onboarding-run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,43 @@ describe('buildBrowserFlowUrls', () => {
assert.equal(openUrl.searchParams.get('provider'), 'github');
assert.ok(openUrl.searchParams.get('redirect')!.includes(`run=${ENV_RUN}`));
});

it('routes a provider-tagged sign-in through the signup entry with the longer budget', () => {
const plain = buildBrowserFlowUrls(mockConfig(), 'state123', 'challenge123');
const tagged = buildBrowserFlowUrls(mockConfig(), 'state123', 'challenge123', {
provider: 'google',
});
assert.equal(tagged.openUrl.pathname, '/signup');
assert.equal(tagged.openUrl.searchParams.get('provider'), 'google');
assert.equal(tagged.openUrl.searchParams.get('ref'), 'gh.readme');
assert.equal(
tagged.openUrl.searchParams.get('redirect'),
`${tagged.authUrl.pathname}${tagged.authUrl.search}`
);
assert.ok(tagged.timeoutMs > plain.timeoutMs);
});

it('keeps the signup entry shape when no provider is named', () => {
const plain = buildBrowserFlowUrls(mockConfig(), 'state123', 'challenge123');
const signup = buildBrowserFlowUrls(mockConfig(), 'state123', 'challenge123', {
signupEntry: true,
});
assert.equal(signup.openUrl.pathname, '/signup');
assert.equal(signup.openUrl.searchParams.get('provider'), null);
assert.equal(
signup.openUrl.searchParams.get('redirect'),
`${signup.authUrl.pathname}${signup.authUrl.search}`
);
assert.equal(signup.timeoutMs, buildBrowserFlowUrls(mockConfig(), 's', 'c', { provider: 'github' }).timeoutMs);
assert.ok(signup.timeoutMs > plain.timeoutMs);
});

it('opens the consent url directly when neither signup entry nor provider is set', () => {
const { authUrl, openUrl, timeoutMs } = buildBrowserFlowUrls(mockConfig(), 'state123', 'challenge123');
assert.equal(openUrl, authUrl);
assert.ok(openUrl.pathname.startsWith('/oauth/'));
assert.ok(timeoutMs > 0);
});
});

describe('auth signup attribution forwarding', () => {
Expand Down
Loading