workos 0.3.2 → 0.4.5
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 +28 -6
- package/dist/bin.js.map +1 -1
- package/dist/commands/login.js +21 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/lib/credential-store.d.ts +28 -0
- package/dist/lib/credential-store.js +150 -0
- package/dist/lib/credential-store.js.map +1 -0
- package/dist/lib/credentials.d.ts +3 -37
- package/dist/lib/credentials.js +2 -85
- package/dist/lib/credentials.js.map +1 -1
- package/dist/lib/version-check.d.ts +6 -0
- package/dist/lib/version-check.js +45 -0
- package/dist/lib/version-check.js.map +1 -0
- package/dist/utils/exec-file.d.ts +15 -0
- package/dist/utils/exec-file.js +38 -0
- package/dist/utils/exec-file.js.map +1 -0
- package/package.json +10 -2
- package/skills/workos-authkit-nextjs/SKILL.md +75 -10
- package/skills/workos-authkit-tanstack-start/SKILL.md +184 -27
package/dist/bin.js
CHANGED
|
@@ -11,6 +11,7 @@ import { getConfig } from './lib/settings.js';
|
|
|
11
11
|
import yargs from 'yargs';
|
|
12
12
|
import { hideBin } from 'yargs/helpers';
|
|
13
13
|
import { ensureAuthenticated } from './lib/ensure-auth.js';
|
|
14
|
+
import { checkForUpdates } from './lib/version-check.js';
|
|
14
15
|
const NODE_VERSION_RANGE = getConfig().nodeVersion;
|
|
15
16
|
// Have to run this above the other imports because they are importing clack that
|
|
16
17
|
// has the problematic imports.
|
|
@@ -20,7 +21,21 @@ if (!satisfies(process.version, NODE_VERSION_RANGE)) {
|
|
|
20
21
|
}
|
|
21
22
|
import { isNonInteractiveEnvironment } from './utils/environment.js';
|
|
22
23
|
import clack from './utils/clack.js';
|
|
23
|
-
|
|
24
|
+
/** Apply insecure storage flag if set */
|
|
25
|
+
async function applyInsecureStorage(insecureStorage) {
|
|
26
|
+
if (insecureStorage) {
|
|
27
|
+
const { setInsecureStorage } = await import('./lib/credentials.js');
|
|
28
|
+
setInsecureStorage(true);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Shared insecure-storage option for commands that access credentials */
|
|
32
|
+
const insecureStorageOption = {
|
|
33
|
+
'insecure-storage': {
|
|
34
|
+
default: false,
|
|
35
|
+
describe: 'Store credentials in plaintext file instead of system keyring',
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
24
39
|
/**
|
|
25
40
|
* Wrap a command handler with authentication check.
|
|
26
41
|
* Ensures valid auth before executing the handler.
|
|
@@ -28,9 +43,10 @@ import clack from './utils/clack.js';
|
|
|
28
43
|
*/
|
|
29
44
|
function withAuth(handler) {
|
|
30
45
|
return async (argv) => {
|
|
31
|
-
|
|
46
|
+
const typedArgv = argv;
|
|
47
|
+
await applyInsecureStorage(typedArgv.insecureStorage);
|
|
48
|
+
if (!typedArgv.skipAuth)
|
|
32
49
|
await ensureAuthenticated();
|
|
33
|
-
}
|
|
34
50
|
await handler(argv);
|
|
35
51
|
};
|
|
36
52
|
}
|
|
@@ -46,6 +62,7 @@ const installerOptions = {
|
|
|
46
62
|
describe: 'Enable verbose logging',
|
|
47
63
|
type: 'boolean',
|
|
48
64
|
},
|
|
65
|
+
...insecureStorageOption,
|
|
49
66
|
// Hidden dev/automation flags (use env vars)
|
|
50
67
|
local: {
|
|
51
68
|
default: false,
|
|
@@ -110,14 +127,18 @@ const installerOptions = {
|
|
|
110
127
|
type: 'boolean',
|
|
111
128
|
},
|
|
112
129
|
};
|
|
130
|
+
// Check for updates (blocks up to 500ms)
|
|
131
|
+
await checkForUpdates();
|
|
113
132
|
yargs(hideBin(process.argv))
|
|
114
133
|
.env('WORKOS_INSTALLER')
|
|
115
|
-
.command('login', 'Authenticate with WorkOS',
|
|
134
|
+
.command('login', 'Authenticate with WorkOS', insecureStorageOption, async (argv) => {
|
|
135
|
+
await applyInsecureStorage(argv.insecureStorage);
|
|
116
136
|
const { runLogin } = await import('./commands/login.js');
|
|
117
137
|
await runLogin();
|
|
118
138
|
process.exit(0);
|
|
119
139
|
})
|
|
120
|
-
.command('logout', 'Remove stored credentials',
|
|
140
|
+
.command('logout', 'Remove stored credentials', insecureStorageOption, async (argv) => {
|
|
141
|
+
await applyInsecureStorage(argv.insecureStorage);
|
|
121
142
|
const { runLogout } = await import('./commands/logout.js');
|
|
122
143
|
await runLogout();
|
|
123
144
|
})
|
|
@@ -157,7 +178,7 @@ yargs(hideBin(process.argv))
|
|
|
157
178
|
const { handleInstall } = await import('./commands/install.js');
|
|
158
179
|
await handleInstall({ ...argv, dashboard: true });
|
|
159
180
|
}))
|
|
160
|
-
.command(['$0'], 'WorkOS AuthKit CLI', (yargs) => yargs, async () => {
|
|
181
|
+
.command(['$0'], 'WorkOS AuthKit CLI', (yargs) => yargs.options(insecureStorageOption), async (argv) => {
|
|
161
182
|
// Non-TTY: show help
|
|
162
183
|
if (isNonInteractiveEnvironment()) {
|
|
163
184
|
yargs(hideBin(process.argv)).showHelp();
|
|
@@ -171,6 +192,7 @@ yargs(hideBin(process.argv))
|
|
|
171
192
|
process.exit(0);
|
|
172
193
|
}
|
|
173
194
|
// Auth check happens HERE, after user confirms
|
|
195
|
+
await applyInsecureStorage(argv.insecureStorage);
|
|
174
196
|
await ensureAuthenticated();
|
|
175
197
|
const { handleInstall } = await import('./commands/install.js');
|
|
176
198
|
await handleInstall({ dashboard: false });
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\n// Load .env.local for local development when --local flag is used\nif (process.argv.includes('--local') || process.env.INSTALLER_DEV) {\n const { config } = await import('dotenv');\n // bin.ts compiles to dist/bin.js, so go up one level to find .env.local\n config({ path: new URL('../.env.local', import.meta.url).pathname });\n}\n\nimport { satisfies } from 'semver';\nimport { red } from './utils/logging.js';\nimport { getConfig } from './lib/settings.js';\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport chalk from 'chalk';\nimport { ensureAuthenticated } from './lib/ensure-auth.js';\n\nconst NODE_VERSION_RANGE = getConfig().nodeVersion;\n\n// Have to run this above the other imports because they are importing clack that\n// has the problematic imports.\nif (!satisfies(process.version, NODE_VERSION_RANGE)) {\n red(\n `WorkOS AuthKit installer requires Node.js ${NODE_VERSION_RANGE}. You are using Node.js ${process.version}. Please upgrade your Node.js version.`,\n );\n process.exit(1);\n}\n\nimport { isNonInteractiveEnvironment } from './utils/environment.js';\nimport clack from './utils/clack.js';\n\n// Shared options for wizard commands (default and dashboard)\n/**\n * Wrap a command handler with authentication check.\n * Ensures valid auth before executing the handler.\n * Respects --skip-auth flag for CI/testing.\n */\nfunction withAuth<T>(handler: (argv: T) => Promise<void>): (argv: T) => Promise<void> {\n return async (argv: T) => {\n if (!(argv as { skipAuth?: boolean }).skipAuth) {\n await ensureAuthenticated();\n }\n await handler(argv);\n };\n}\n\nconst installerOptions = {\n direct: {\n alias: 'D',\n default: false,\n describe: 'Use your own Anthropic API key (bypass llm-gateway)',\n type: 'boolean' as const,\n },\n debug: {\n default: false,\n describe: 'Enable verbose logging',\n type: 'boolean' as const,\n },\n // Hidden dev/automation flags (use env vars)\n local: {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n ci: {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n 'skip-auth': {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n 'api-key': {\n type: 'string' as const,\n hidden: true,\n },\n 'client-id': {\n type: 'string' as const,\n hidden: true,\n },\n inspect: {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n // User-facing flags\n 'homepage-url': {\n describe: 'App homepage URL for WorkOS (defaults to http://localhost:{port})',\n type: 'string' as const,\n },\n 'redirect-uri': {\n describe: 'Redirect URI for WorkOS callback (defaults to framework convention)',\n type: 'string' as const,\n },\n 'no-validate': {\n default: false,\n describe: 'Skip post-installation validation (includes build check)',\n type: 'boolean' as const,\n },\n 'install-dir': {\n describe: 'Directory to install WorkOS AuthKit in',\n type: 'string' as const,\n },\n integration: {\n describe: 'Integration to set up',\n choices: ['nextjs', 'react', 'tanstack-start', 'react-router', 'vanilla-js'] as const,\n type: 'string' as const,\n },\n 'force-install': {\n default: false,\n describe: 'Force install packages even if peer dependency checks fail',\n type: 'boolean' as const,\n },\n dashboard: {\n alias: 'd',\n default: false,\n describe: 'Run with visual dashboard mode',\n type: 'boolean' as const,\n },\n};\n\nyargs(hideBin(process.argv))\n .env('WORKOS_INSTALLER')\n .command('login', 'Authenticate with WorkOS', {}, async () => {\n const { runLogin } = await import('./commands/login.js');\n await runLogin();\n process.exit(0);\n })\n .command('logout', 'Remove stored credentials', {}, async () => {\n const { runLogout } = await import('./commands/logout.js');\n await runLogout();\n })\n .command(\n 'install-skill',\n 'Install bundled AuthKit skills to coding agents',\n (yargs) => {\n return yargs\n .option('list', {\n alias: 'l',\n type: 'boolean',\n description: 'List available skills without installing',\n })\n .option('skill', {\n alias: 's',\n type: 'array',\n string: true,\n description: 'Install specific skill(s)',\n })\n .option('agent', {\n alias: 'a',\n type: 'array',\n string: true,\n description: 'Target specific agent(s): claude-code, codex, cursor, goose',\n });\n },\n withAuth(async (argv) => {\n const { runInstallSkill } = await import('./commands/install-skill.js');\n await runInstallSkill({\n list: argv.list as boolean | undefined,\n skill: argv.skill as string[] | undefined,\n agent: argv.agent as string[] | undefined,\n });\n }),\n )\n .command(\n 'install',\n 'Install WorkOS AuthKit into your project',\n (yargs) => yargs.options(installerOptions),\n withAuth(async (argv) => {\n const { handleInstall } = await import('./commands/install.js');\n await handleInstall(argv);\n }),\n )\n .command(\n 'dashboard',\n false, // hidden from help\n (yargs) => yargs.options(installerOptions),\n withAuth(async (argv) => {\n const { handleInstall } = await import('./commands/install.js');\n await handleInstall({ ...argv, dashboard: true });\n }),\n )\n .command(\n ['$0'],\n 'WorkOS AuthKit CLI',\n (yargs) => yargs,\n async () => {\n // Non-TTY: show help\n if (isNonInteractiveEnvironment()) {\n yargs(hideBin(process.argv)).showHelp();\n return;\n }\n\n // TTY: ask if user wants to run installer\n const shouldInstall = await clack.confirm({\n message: 'Run the AuthKit installer?',\n });\n\n if (clack.isCancel(shouldInstall) || !shouldInstall) {\n process.exit(0);\n }\n\n // Auth check happens HERE, after user confirms\n await ensureAuthenticated();\n\n const { handleInstall } = await import('./commands/install.js');\n await handleInstall({ dashboard: false } as any);\n process.exit(0);\n },\n )\n .strict()\n .help()\n .alias('help', 'h')\n .version()\n .alias('version', 'v')\n .wrap(process.stdout.isTTY && process.stdout.columns ? process.stdout.columns : 80).argv;\n"]}
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\n// Load .env.local for local development when --local flag is used\nif (process.argv.includes('--local') || process.env.INSTALLER_DEV) {\n const { config } = await import('dotenv');\n // bin.ts compiles to dist/bin.js, so go up one level to find .env.local\n config({ path: new URL('../.env.local', import.meta.url).pathname });\n}\n\nimport { satisfies } from 'semver';\nimport { red } from './utils/logging.js';\nimport { getConfig } from './lib/settings.js';\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport chalk from 'chalk';\nimport { ensureAuthenticated } from './lib/ensure-auth.js';\nimport { checkForUpdates } from './lib/version-check.js';\n\nconst NODE_VERSION_RANGE = getConfig().nodeVersion;\n\n// Have to run this above the other imports because they are importing clack that\n// has the problematic imports.\nif (!satisfies(process.version, NODE_VERSION_RANGE)) {\n red(\n `WorkOS AuthKit installer requires Node.js ${NODE_VERSION_RANGE}. You are using Node.js ${process.version}. Please upgrade your Node.js version.`,\n );\n process.exit(1);\n}\n\nimport { isNonInteractiveEnvironment } from './utils/environment.js';\nimport clack from './utils/clack.js';\n\n/** Apply insecure storage flag if set */\nasync function applyInsecureStorage(insecureStorage?: boolean): Promise<void> {\n if (insecureStorage) {\n const { setInsecureStorage } = await import('./lib/credentials.js');\n setInsecureStorage(true);\n }\n}\n\n/** Shared insecure-storage option for commands that access credentials */\nconst insecureStorageOption = {\n 'insecure-storage': {\n default: false,\n describe: 'Store credentials in plaintext file instead of system keyring',\n type: 'boolean' as const,\n },\n} as const;\n\n/**\n * Wrap a command handler with authentication check.\n * Ensures valid auth before executing the handler.\n * Respects --skip-auth flag for CI/testing.\n */\nfunction withAuth<T>(handler: (argv: T) => Promise<void>): (argv: T) => Promise<void> {\n return async (argv: T) => {\n const typedArgv = argv as { skipAuth?: boolean; insecureStorage?: boolean };\n await applyInsecureStorage(typedArgv.insecureStorage);\n if (!typedArgv.skipAuth) await ensureAuthenticated();\n await handler(argv);\n };\n}\n\nconst installerOptions = {\n direct: {\n alias: 'D',\n default: false,\n describe: 'Use your own Anthropic API key (bypass llm-gateway)',\n type: 'boolean' as const,\n },\n debug: {\n default: false,\n describe: 'Enable verbose logging',\n type: 'boolean' as const,\n },\n ...insecureStorageOption,\n // Hidden dev/automation flags (use env vars)\n local: {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n ci: {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n 'skip-auth': {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n 'api-key': {\n type: 'string' as const,\n hidden: true,\n },\n 'client-id': {\n type: 'string' as const,\n hidden: true,\n },\n inspect: {\n default: false,\n type: 'boolean' as const,\n hidden: true,\n },\n // User-facing flags\n 'homepage-url': {\n describe: 'App homepage URL for WorkOS (defaults to http://localhost:{port})',\n type: 'string' as const,\n },\n 'redirect-uri': {\n describe: 'Redirect URI for WorkOS callback (defaults to framework convention)',\n type: 'string' as const,\n },\n 'no-validate': {\n default: false,\n describe: 'Skip post-installation validation (includes build check)',\n type: 'boolean' as const,\n },\n 'install-dir': {\n describe: 'Directory to install WorkOS AuthKit in',\n type: 'string' as const,\n },\n integration: {\n describe: 'Integration to set up',\n choices: ['nextjs', 'react', 'tanstack-start', 'react-router', 'vanilla-js'] as const,\n type: 'string' as const,\n },\n 'force-install': {\n default: false,\n describe: 'Force install packages even if peer dependency checks fail',\n type: 'boolean' as const,\n },\n dashboard: {\n alias: 'd',\n default: false,\n describe: 'Run with visual dashboard mode',\n type: 'boolean' as const,\n },\n};\n\n// Check for updates (blocks up to 500ms)\nawait checkForUpdates();\n\nyargs(hideBin(process.argv))\n .env('WORKOS_INSTALLER')\n .command('login', 'Authenticate with WorkOS', insecureStorageOption, async (argv) => {\n await applyInsecureStorage(argv.insecureStorage);\n const { runLogin } = await import('./commands/login.js');\n await runLogin();\n process.exit(0);\n })\n .command('logout', 'Remove stored credentials', insecureStorageOption, async (argv) => {\n await applyInsecureStorage(argv.insecureStorage);\n const { runLogout } = await import('./commands/logout.js');\n await runLogout();\n })\n .command(\n 'install-skill',\n 'Install bundled AuthKit skills to coding agents',\n (yargs) => {\n return yargs\n .option('list', {\n alias: 'l',\n type: 'boolean',\n description: 'List available skills without installing',\n })\n .option('skill', {\n alias: 's',\n type: 'array',\n string: true,\n description: 'Install specific skill(s)',\n })\n .option('agent', {\n alias: 'a',\n type: 'array',\n string: true,\n description: 'Target specific agent(s): claude-code, codex, cursor, goose',\n });\n },\n withAuth(async (argv) => {\n const { runInstallSkill } = await import('./commands/install-skill.js');\n await runInstallSkill({\n list: argv.list as boolean | undefined,\n skill: argv.skill as string[] | undefined,\n agent: argv.agent as string[] | undefined,\n });\n }),\n )\n .command(\n 'install',\n 'Install WorkOS AuthKit into your project',\n (yargs) => yargs.options(installerOptions),\n withAuth(async (argv) => {\n const { handleInstall } = await import('./commands/install.js');\n await handleInstall(argv);\n }),\n )\n .command(\n 'dashboard',\n false, // hidden from help\n (yargs) => yargs.options(installerOptions),\n withAuth(async (argv) => {\n const { handleInstall } = await import('./commands/install.js');\n await handleInstall({ ...argv, dashboard: true });\n }),\n )\n .command(\n ['$0'],\n 'WorkOS AuthKit CLI',\n (yargs) => yargs.options(insecureStorageOption),\n async (argv) => {\n // Non-TTY: show help\n if (isNonInteractiveEnvironment()) {\n yargs(hideBin(process.argv)).showHelp();\n return;\n }\n\n // TTY: ask if user wants to run installer\n const shouldInstall = await clack.confirm({\n message: 'Run the AuthKit installer?',\n });\n\n if (clack.isCancel(shouldInstall) || !shouldInstall) {\n process.exit(0);\n }\n\n // Auth check happens HERE, after user confirms\n await applyInsecureStorage(argv.insecureStorage);\n await ensureAuthenticated();\n\n const { handleInstall } = await import('./commands/install.js');\n await handleInstall({ dashboard: false } as any);\n process.exit(0);\n },\n )\n .strict()\n .help()\n .alias('help', 'h')\n .version()\n .alias('version', 'v')\n .wrap(process.stdout.isTTY && process.stdout.columns ? process.stdout.columns : 80).argv;\n"]}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import open from 'opn';
|
|
2
2
|
import clack from '../utils/clack.js';
|
|
3
|
-
import { saveCredentials, getCredentials, getAccessToken } from '../lib/credentials.js';
|
|
3
|
+
import { saveCredentials, getCredentials, getAccessToken, isTokenExpired, updateTokens } from '../lib/credentials.js';
|
|
4
4
|
import { getCliAuthClientId, getAuthkitDomain } from '../lib/settings.js';
|
|
5
|
+
import { refreshAccessToken } from '../lib/token-refresh-client.js';
|
|
5
6
|
/**
|
|
6
7
|
* Parse JWT payload
|
|
7
8
|
*/
|
|
@@ -45,12 +46,31 @@ export async function runLogin() {
|
|
|
45
46
|
clack.log.error('CLI auth not configured. Set WORKOS_CLI_CLIENT_ID environment variable.');
|
|
46
47
|
process.exit(1);
|
|
47
48
|
}
|
|
49
|
+
// Check if already logged in with valid token
|
|
48
50
|
if (getAccessToken()) {
|
|
49
51
|
const creds = getCredentials();
|
|
50
52
|
clack.log.info(`Already logged in as ${creds?.email ?? 'unknown'}`);
|
|
51
53
|
clack.log.info('Run `workos logout` to log out');
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
56
|
+
// Try to refresh if we have expired credentials with a refresh token
|
|
57
|
+
const existingCreds = getCredentials();
|
|
58
|
+
if (existingCreds?.refreshToken && isTokenExpired(existingCreds)) {
|
|
59
|
+
try {
|
|
60
|
+
const authkitDomain = getAuthkitDomain();
|
|
61
|
+
const result = await refreshAccessToken(authkitDomain, clientId);
|
|
62
|
+
if (result.accessToken && result.expiresAt) {
|
|
63
|
+
updateTokens(result.accessToken, result.expiresAt, result.refreshToken);
|
|
64
|
+
clack.log.info(`Already logged in as ${existingCreds.email ?? 'unknown'}`);
|
|
65
|
+
clack.log.info('(Session refreshed)');
|
|
66
|
+
clack.log.info('Run `workos logout` to log out');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Refresh failed, proceed with fresh login
|
|
72
|
+
}
|
|
73
|
+
}
|
|
54
74
|
clack.log.step('Starting authentication...');
|
|
55
75
|
const endpoints = getConnectEndpoints();
|
|
56
76
|
const authResponse = await fetch(endpoints.deviceAuthorization, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import open from 'opn';\nimport clack from '../utils/clack.js';\nimport { saveCredentials, getCredentials, getAccessToken } from '../lib/credentials.js';\nimport { getCliAuthClientId, getAuthkitDomain } from '../lib/settings.js';\n\n/**\n * Parse JWT payload\n */\nfunction parseJwt(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract expiry time from JWT token\n */\nfunction getJwtExpiry(token: string): number | null {\n const payload = parseJwt(token);\n if (!payload || typeof payload.exp !== 'number') return null;\n return payload.exp * 1000;\n}\n\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Get Connect OAuth endpoints from AuthKit domain\n */\nfunction getConnectEndpoints() {\n const domain = getAuthkitDomain();\n return {\n deviceAuthorization: `${domain}/oauth2/device_authorization`,\n token: `${domain}/oauth2/token`,\n };\n}\n\ninterface DeviceAuthResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete: string;\n expires_in: number;\n interval: number;\n}\n\ninterface ConnectTokenResponse {\n access_token: string;\n id_token: string;\n token_type: string;\n expires_in: number;\n refresh_token?: string;\n}\n\ninterface AuthErrorResponse {\n error: string;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function runLogin(): Promise<void> {\n const clientId = getCliAuthClientId();\n\n if (!clientId) {\n clack.log.error('CLI auth not configured. Set WORKOS_CLI_CLIENT_ID environment variable.');\n process.exit(1);\n }\n\n if (getAccessToken()) {\n const creds = getCredentials();\n clack.log.info(`Already logged in as ${creds?.email ?? 'unknown'}`);\n clack.log.info('Run `workos logout` to log out');\n return;\n }\n\n clack.log.step('Starting authentication...');\n\n const endpoints = getConnectEndpoints();\n\n const authResponse = await fetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n client_id: clientId,\n scope: 'openid email staging-environment:credentials:read offline_access',\n }),\n });\n\n if (!authResponse.ok) {\n clack.log.error(`Failed to start authentication: ${authResponse.status}`);\n process.exit(1);\n }\n\n const deviceAuth = (await authResponse.json()) as DeviceAuthResponse;\n const pollIntervalMs = (deviceAuth.interval || 5) * 1000;\n\n clack.log.info(`\\nOpen this URL in your browser:\\n`);\n console.log(` ${deviceAuth.verification_uri}`);\n console.log(`\\nEnter code: ${deviceAuth.user_code}\\n`);\n\n try {\n open(deviceAuth.verification_uri_complete);\n clack.log.info('Browser opened automatically');\n } catch {\n // User can open manually\n }\n\n const spinner = clack.spinner();\n spinner.start('Waiting for authentication...');\n\n const startTime = Date.now();\n let currentInterval = pollIntervalMs;\n\n while (Date.now() - startTime < POLL_TIMEOUT_MS) {\n await sleep(currentInterval);\n\n try {\n const tokenResponse = await fetch(endpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceAuth.device_code,\n client_id: clientId,\n }),\n });\n\n const data = await tokenResponse.json();\n\n if (tokenResponse.ok) {\n const result = data as ConnectTokenResponse;\n\n // Parse user info from id_token JWT\n const idTokenPayload = parseJwt(result.id_token);\n const userId = (idTokenPayload?.sub as string) || 'unknown';\n const email = (idTokenPayload?.email as string) || undefined;\n\n // Extract actual expiry from access token JWT, fallback to response or 15 min\n const jwtExpiry = getJwtExpiry(result.access_token);\n const expiresAt =\n jwtExpiry ?? (result.expires_in ? Date.now() + result.expires_in * 1000 : Date.now() + 15 * 60 * 1000);\n\n const expiresInSec = Math.round((expiresAt - Date.now()) / 1000);\n\n saveCredentials({\n accessToken: result.access_token,\n expiresAt,\n userId,\n email,\n refreshToken: result.refresh_token,\n });\n\n spinner.stop('Authentication successful!');\n clack.log.success(`Logged in as ${email || userId}`);\n clack.log.info(`Token expires in ${expiresInSec} seconds`);\n return;\n }\n\n const errorData = data as AuthErrorResponse;\n if (errorData.error === 'authorization_pending') continue;\n if (errorData.error === 'slow_down') {\n currentInterval += 5000;\n continue;\n }\n\n spinner.stop('Authentication failed');\n clack.log.error(`Authentication error: ${errorData.error}`);\n process.exit(1);\n } catch {\n continue;\n }\n }\n\n spinner.stop('Authentication timed out');\n clack.log.error('Authentication timed out. Please try again.');\n process.exit(1);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import open from 'opn';\nimport clack from '../utils/clack.js';\nimport { saveCredentials, getCredentials, getAccessToken, isTokenExpired, updateTokens } from '../lib/credentials.js';\nimport { getCliAuthClientId, getAuthkitDomain } from '../lib/settings.js';\nimport { refreshAccessToken } from '../lib/token-refresh-client.js';\n\n/**\n * Parse JWT payload\n */\nfunction parseJwt(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n return JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Extract expiry time from JWT token\n */\nfunction getJwtExpiry(token: string): number | null {\n const payload = parseJwt(token);\n if (!payload || typeof payload.exp !== 'number') return null;\n return payload.exp * 1000;\n}\n\nconst POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Get Connect OAuth endpoints from AuthKit domain\n */\nfunction getConnectEndpoints() {\n const domain = getAuthkitDomain();\n return {\n deviceAuthorization: `${domain}/oauth2/device_authorization`,\n token: `${domain}/oauth2/token`,\n };\n}\n\ninterface DeviceAuthResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete: string;\n expires_in: number;\n interval: number;\n}\n\ninterface ConnectTokenResponse {\n access_token: string;\n id_token: string;\n token_type: string;\n expires_in: number;\n refresh_token?: string;\n}\n\ninterface AuthErrorResponse {\n error: string;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function runLogin(): Promise<void> {\n const clientId = getCliAuthClientId();\n\n if (!clientId) {\n clack.log.error('CLI auth not configured. Set WORKOS_CLI_CLIENT_ID environment variable.');\n process.exit(1);\n }\n\n // Check if already logged in with valid token\n if (getAccessToken()) {\n const creds = getCredentials();\n clack.log.info(`Already logged in as ${creds?.email ?? 'unknown'}`);\n clack.log.info('Run `workos logout` to log out');\n return;\n }\n\n // Try to refresh if we have expired credentials with a refresh token\n const existingCreds = getCredentials();\n if (existingCreds?.refreshToken && isTokenExpired(existingCreds)) {\n try {\n const authkitDomain = getAuthkitDomain();\n const result = await refreshAccessToken(authkitDomain, clientId);\n if (result.accessToken && result.expiresAt) {\n updateTokens(result.accessToken, result.expiresAt, result.refreshToken);\n clack.log.info(`Already logged in as ${existingCreds.email ?? 'unknown'}`);\n clack.log.info('(Session refreshed)');\n clack.log.info('Run `workos logout` to log out');\n return;\n }\n } catch {\n // Refresh failed, proceed with fresh login\n }\n }\n\n clack.log.step('Starting authentication...');\n\n const endpoints = getConnectEndpoints();\n\n const authResponse = await fetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n client_id: clientId,\n scope: 'openid email staging-environment:credentials:read offline_access',\n }),\n });\n\n if (!authResponse.ok) {\n clack.log.error(`Failed to start authentication: ${authResponse.status}`);\n process.exit(1);\n }\n\n const deviceAuth = (await authResponse.json()) as DeviceAuthResponse;\n const pollIntervalMs = (deviceAuth.interval || 5) * 1000;\n\n clack.log.info(`\\nOpen this URL in your browser:\\n`);\n console.log(` ${deviceAuth.verification_uri}`);\n console.log(`\\nEnter code: ${deviceAuth.user_code}\\n`);\n\n try {\n open(deviceAuth.verification_uri_complete);\n clack.log.info('Browser opened automatically');\n } catch {\n // User can open manually\n }\n\n const spinner = clack.spinner();\n spinner.start('Waiting for authentication...');\n\n const startTime = Date.now();\n let currentInterval = pollIntervalMs;\n\n while (Date.now() - startTime < POLL_TIMEOUT_MS) {\n await sleep(currentInterval);\n\n try {\n const tokenResponse = await fetch(endpoints.token, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceAuth.device_code,\n client_id: clientId,\n }),\n });\n\n const data = await tokenResponse.json();\n\n if (tokenResponse.ok) {\n const result = data as ConnectTokenResponse;\n\n // Parse user info from id_token JWT\n const idTokenPayload = parseJwt(result.id_token);\n const userId = (idTokenPayload?.sub as string) || 'unknown';\n const email = (idTokenPayload?.email as string) || undefined;\n\n // Extract actual expiry from access token JWT, fallback to response or 15 min\n const jwtExpiry = getJwtExpiry(result.access_token);\n const expiresAt =\n jwtExpiry ?? (result.expires_in ? Date.now() + result.expires_in * 1000 : Date.now() + 15 * 60 * 1000);\n\n const expiresInSec = Math.round((expiresAt - Date.now()) / 1000);\n\n saveCredentials({\n accessToken: result.access_token,\n expiresAt,\n userId,\n email,\n refreshToken: result.refresh_token,\n });\n\n spinner.stop('Authentication successful!');\n clack.log.success(`Logged in as ${email || userId}`);\n clack.log.info(`Token expires in ${expiresInSec} seconds`);\n return;\n }\n\n const errorData = data as AuthErrorResponse;\n if (errorData.error === 'authorization_pending') continue;\n if (errorData.error === 'slow_down') {\n currentInterval += 5000;\n continue;\n }\n\n spinner.stop('Authentication failed');\n clack.log.error(`Authentication error: ${errorData.error}`);\n process.exit(1);\n } catch {\n continue;\n }\n }\n\n spinner.stop('Authentication timed out');\n clack.log.error('Authentication timed out. Please try again.');\n process.exit(1);\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage abstraction with keyring support and file fallback.
|
|
3
|
+
*
|
|
4
|
+
* Storage priority:
|
|
5
|
+
* 1. If --insecure-storage: use file only
|
|
6
|
+
* 2. Try keyring, fall back to file with warning if unavailable
|
|
7
|
+
*/
|
|
8
|
+
export interface StagingCache {
|
|
9
|
+
clientId: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
fetchedAt: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Credentials {
|
|
14
|
+
accessToken: string;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
userId: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
staging?: StagingCache;
|
|
19
|
+
refreshToken?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function setInsecureStorage(value: boolean): void;
|
|
22
|
+
declare function getCredentialsPath(): string;
|
|
23
|
+
export declare function hasCredentials(): boolean;
|
|
24
|
+
export declare function getCredentials(): Credentials | null;
|
|
25
|
+
export declare function saveCredentials(creds: Credentials): void;
|
|
26
|
+
export declare function clearCredentials(): void;
|
|
27
|
+
export declare function updateTokens(accessToken: string, expiresAt: number, refreshToken?: string): void;
|
|
28
|
+
export { getCredentialsPath };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage abstraction with keyring support and file fallback.
|
|
3
|
+
*
|
|
4
|
+
* Storage priority:
|
|
5
|
+
* 1. If --insecure-storage: use file only
|
|
6
|
+
* 2. Try keyring, fall back to file with warning if unavailable
|
|
7
|
+
*/
|
|
8
|
+
import { Entry } from '@napi-rs/keyring';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import { logWarn } from '../utils/debug.js';
|
|
13
|
+
const SERVICE_NAME = 'workos-cli';
|
|
14
|
+
const ACCOUNT_NAME = 'credentials';
|
|
15
|
+
let fallbackWarningShown = false;
|
|
16
|
+
let forceInsecureStorage = false;
|
|
17
|
+
export function setInsecureStorage(value) {
|
|
18
|
+
forceInsecureStorage = value;
|
|
19
|
+
}
|
|
20
|
+
function getCredentialsDir() {
|
|
21
|
+
return path.join(os.homedir(), '.workos');
|
|
22
|
+
}
|
|
23
|
+
function getCredentialsPath() {
|
|
24
|
+
return path.join(getCredentialsDir(), 'credentials.json');
|
|
25
|
+
}
|
|
26
|
+
function fileExists() {
|
|
27
|
+
return fs.existsSync(getCredentialsPath());
|
|
28
|
+
}
|
|
29
|
+
function readFromFile() {
|
|
30
|
+
if (!fileExists())
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
const content = fs.readFileSync(getCredentialsPath(), 'utf-8');
|
|
34
|
+
return JSON.parse(content);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logWarn('Failed to read credentials file:', error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function writeToFile(creds) {
|
|
42
|
+
const dir = getCredentialsDir();
|
|
43
|
+
if (!fs.existsSync(dir)) {
|
|
44
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
45
|
+
}
|
|
46
|
+
fs.writeFileSync(getCredentialsPath(), JSON.stringify(creds, null, 2), {
|
|
47
|
+
mode: 0o600,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function deleteFile() {
|
|
51
|
+
if (fileExists()) {
|
|
52
|
+
fs.unlinkSync(getCredentialsPath());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function getKeyringEntry() {
|
|
56
|
+
return new Entry(SERVICE_NAME, ACCOUNT_NAME);
|
|
57
|
+
}
|
|
58
|
+
function readFromKeyring() {
|
|
59
|
+
try {
|
|
60
|
+
const entry = getKeyringEntry();
|
|
61
|
+
const data = entry.getPassword();
|
|
62
|
+
if (!data)
|
|
63
|
+
return null;
|
|
64
|
+
return JSON.parse(data);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
logWarn('Failed to read from keyring:', error);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function writeToKeyring(creds) {
|
|
72
|
+
try {
|
|
73
|
+
const entry = getKeyringEntry();
|
|
74
|
+
entry.setPassword(JSON.stringify(creds));
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logWarn('Failed to write to keyring:', error);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function deleteFromKeyring() {
|
|
83
|
+
try {
|
|
84
|
+
const entry = getKeyringEntry();
|
|
85
|
+
entry.deletePassword();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
89
|
+
if (!msg.includes('not found') && !msg.includes('No such')) {
|
|
90
|
+
logWarn('Failed to delete from keyring:', error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function showFallbackWarning() {
|
|
95
|
+
if (fallbackWarningShown || forceInsecureStorage)
|
|
96
|
+
return;
|
|
97
|
+
fallbackWarningShown = true;
|
|
98
|
+
logWarn('Unable to store credentials in system keyring. Using file storage.', 'Credentials saved to ~/.workos/credentials.json', 'Use --insecure-storage to suppress this warning.');
|
|
99
|
+
}
|
|
100
|
+
export function hasCredentials() {
|
|
101
|
+
if (forceInsecureStorage) {
|
|
102
|
+
return fileExists();
|
|
103
|
+
}
|
|
104
|
+
return readFromKeyring() !== null || fileExists();
|
|
105
|
+
}
|
|
106
|
+
export function getCredentials() {
|
|
107
|
+
if (forceInsecureStorage)
|
|
108
|
+
return readFromFile();
|
|
109
|
+
const keyringCreds = readFromKeyring();
|
|
110
|
+
if (keyringCreds)
|
|
111
|
+
return keyringCreds;
|
|
112
|
+
const fileCreds = readFromFile();
|
|
113
|
+
if (fileCreds) {
|
|
114
|
+
// Migrate file creds to keyring if possible
|
|
115
|
+
if (writeToKeyring(fileCreds))
|
|
116
|
+
deleteFile();
|
|
117
|
+
return fileCreds;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
export function saveCredentials(creds) {
|
|
122
|
+
if (forceInsecureStorage)
|
|
123
|
+
return writeToFile(creds);
|
|
124
|
+
if (writeToKeyring(creds)) {
|
|
125
|
+
deleteFile();
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
showFallbackWarning();
|
|
129
|
+
writeToFile(creds);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export function clearCredentials() {
|
|
133
|
+
deleteFromKeyring();
|
|
134
|
+
deleteFile();
|
|
135
|
+
}
|
|
136
|
+
export function updateTokens(accessToken, expiresAt, refreshToken) {
|
|
137
|
+
const creds = getCredentials();
|
|
138
|
+
if (!creds) {
|
|
139
|
+
throw new Error('No existing credentials to update');
|
|
140
|
+
}
|
|
141
|
+
const updated = {
|
|
142
|
+
...creds,
|
|
143
|
+
accessToken,
|
|
144
|
+
expiresAt,
|
|
145
|
+
...(refreshToken && { refreshToken }),
|
|
146
|
+
};
|
|
147
|
+
saveCredentials(updated);
|
|
148
|
+
}
|
|
149
|
+
export { getCredentialsPath };
|
|
150
|
+
//# sourceMappingURL=credential-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/lib/credential-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage abstraction with keyring support and file fallback.\n *\n * Storage priority:\n * 1. If --insecure-storage: use file only\n * 2. Try keyring, fall back to file with warning if unavailable\n */\n\nimport { Entry } from '@napi-rs/keyring';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { logWarn } from '../utils/debug.js';\n\nexport interface StagingCache {\n clientId: string;\n apiKey: string;\n fetchedAt: number;\n}\n\nexport interface Credentials {\n accessToken: string;\n expiresAt: number;\n userId: string;\n email?: string;\n staging?: StagingCache;\n refreshToken?: string;\n}\n\nconst SERVICE_NAME = 'workos-cli';\nconst ACCOUNT_NAME = 'credentials';\n\nlet fallbackWarningShown = false;\nlet forceInsecureStorage = false;\n\nexport function setInsecureStorage(value: boolean): void {\n forceInsecureStorage = value;\n}\n\nfunction getCredentialsDir(): string {\n return path.join(os.homedir(), '.workos');\n}\n\nfunction getCredentialsPath(): string {\n return path.join(getCredentialsDir(), 'credentials.json');\n}\n\nfunction fileExists(): boolean {\n return fs.existsSync(getCredentialsPath());\n}\n\nfunction readFromFile(): Credentials | null {\n if (!fileExists()) return null;\n try {\n const content = fs.readFileSync(getCredentialsPath(), 'utf-8');\n return JSON.parse(content);\n } catch (error) {\n logWarn('Failed to read credentials file:', error);\n return null;\n }\n}\n\nfunction writeToFile(creds: Credentials): void {\n const dir = getCredentialsDir();\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n fs.writeFileSync(getCredentialsPath(), JSON.stringify(creds, null, 2), {\n mode: 0o600,\n });\n}\n\nfunction deleteFile(): void {\n if (fileExists()) {\n fs.unlinkSync(getCredentialsPath());\n }\n}\n\nfunction getKeyringEntry(): Entry {\n return new Entry(SERVICE_NAME, ACCOUNT_NAME);\n}\n\nfunction readFromKeyring(): Credentials | null {\n try {\n const entry = getKeyringEntry();\n const data = entry.getPassword();\n if (!data) return null;\n return JSON.parse(data);\n } catch (error) {\n logWarn('Failed to read from keyring:', error);\n return null;\n }\n}\n\nfunction writeToKeyring(creds: Credentials): boolean {\n try {\n const entry = getKeyringEntry();\n entry.setPassword(JSON.stringify(creds));\n return true;\n } catch (error) {\n logWarn('Failed to write to keyring:', error);\n return false;\n }\n}\n\nfunction deleteFromKeyring(): void {\n try {\n const entry = getKeyringEntry();\n entry.deletePassword();\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (!msg.includes('not found') && !msg.includes('No such')) {\n logWarn('Failed to delete from keyring:', error);\n }\n }\n}\n\nfunction showFallbackWarning(): void {\n if (fallbackWarningShown || forceInsecureStorage) return;\n fallbackWarningShown = true;\n logWarn(\n 'Unable to store credentials in system keyring. Using file storage.',\n 'Credentials saved to ~/.workos/credentials.json',\n 'Use --insecure-storage to suppress this warning.',\n );\n}\n\nexport function hasCredentials(): boolean {\n if (forceInsecureStorage) {\n return fileExists();\n }\n return readFromKeyring() !== null || fileExists();\n}\n\nexport function getCredentials(): Credentials | null {\n if (forceInsecureStorage) return readFromFile();\n\n const keyringCreds = readFromKeyring();\n if (keyringCreds) return keyringCreds;\n\n const fileCreds = readFromFile();\n if (fileCreds) {\n // Migrate file creds to keyring if possible\n if (writeToKeyring(fileCreds)) deleteFile();\n return fileCreds;\n }\n\n return null;\n}\n\nexport function saveCredentials(creds: Credentials): void {\n if (forceInsecureStorage) return writeToFile(creds);\n\n if (writeToKeyring(creds)) {\n deleteFile();\n } else {\n showFallbackWarning();\n writeToFile(creds);\n }\n}\n\nexport function clearCredentials(): void {\n deleteFromKeyring();\n deleteFile();\n}\n\nexport function updateTokens(accessToken: string, expiresAt: number, refreshToken?: string): void {\n const creds = getCredentials();\n if (!creds) {\n throw new Error('No existing credentials to update');\n }\n\n const updated: Credentials = {\n ...creds,\n accessToken,\n expiresAt,\n ...(refreshToken && { refreshToken }),\n };\n\n saveCredentials(updated);\n}\n\nexport { getCredentialsPath };\n"]}
|
|
@@ -1,47 +1,13 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
fetchedAt: number;
|
|
5
|
-
}
|
|
6
|
-
export interface Credentials {
|
|
7
|
-
accessToken: string;
|
|
8
|
-
expiresAt: number;
|
|
9
|
-
userId: string;
|
|
10
|
-
email?: string;
|
|
11
|
-
staging?: StagingCache;
|
|
12
|
-
refreshToken?: string;
|
|
13
|
-
}
|
|
14
|
-
export declare function getCredentialsPath(): string;
|
|
15
|
-
export declare function hasCredentials(): boolean;
|
|
16
|
-
export declare function getCredentials(): Credentials | null;
|
|
17
|
-
export declare function saveCredentials(creds: Credentials): void;
|
|
18
|
-
export declare function clearCredentials(): void;
|
|
19
|
-
/**
|
|
20
|
-
* Check if token is actually expired (hard expiry check).
|
|
21
|
-
*/
|
|
1
|
+
export type { StagingCache, Credentials } from './credential-store.js';
|
|
2
|
+
export { hasCredentials, getCredentials, saveCredentials, clearCredentials, updateTokens, getCredentialsPath, setInsecureStorage, } from './credential-store.js';
|
|
3
|
+
import type { Credentials } from './credential-store.js';
|
|
22
4
|
export declare function isTokenExpired(creds: Credentials): boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Get access token if available and not expired.
|
|
25
|
-
*/
|
|
26
5
|
export declare function getAccessToken(): string | null;
|
|
27
|
-
/**
|
|
28
|
-
* Save staging credentials to the credential cache.
|
|
29
|
-
* Staging credentials are tied to the access token lifecycle.
|
|
30
|
-
*/
|
|
31
6
|
export declare function saveStagingCredentials(staging: {
|
|
32
7
|
clientId: string;
|
|
33
8
|
apiKey: string;
|
|
34
9
|
}): void;
|
|
35
|
-
/**
|
|
36
|
-
* Get cached staging credentials if available and access token is still valid.
|
|
37
|
-
* Returns null if no cached credentials or if access token has expired.
|
|
38
|
-
*/
|
|
39
10
|
export declare function getStagingCredentials(): {
|
|
40
11
|
clientId: string;
|
|
41
12
|
apiKey: string;
|
|
42
13
|
} | null;
|
|
43
|
-
/**
|
|
44
|
-
* Atomically update tokens in credentials file.
|
|
45
|
-
* Uses write-to-temp + rename pattern for atomic updates.
|
|
46
|
-
*/
|
|
47
|
-
export declare function updateTokens(accessToken: string, expiresAt: number, refreshToken?: string): void;
|
package/dist/lib/credentials.js
CHANGED
|
@@ -1,49 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
function getCredentialsDir() {
|
|
5
|
-
return path.join(os.homedir(), '.workos');
|
|
6
|
-
}
|
|
7
|
-
export function getCredentialsPath() {
|
|
8
|
-
return path.join(getCredentialsDir(), 'credentials.json');
|
|
9
|
-
}
|
|
10
|
-
export function hasCredentials() {
|
|
11
|
-
return fs.existsSync(getCredentialsPath());
|
|
12
|
-
}
|
|
13
|
-
export function getCredentials() {
|
|
14
|
-
if (!hasCredentials())
|
|
15
|
-
return null;
|
|
16
|
-
try {
|
|
17
|
-
const content = fs.readFileSync(getCredentialsPath(), 'utf-8');
|
|
18
|
-
return JSON.parse(content);
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
export function saveCredentials(creds) {
|
|
25
|
-
const dir = getCredentialsDir();
|
|
26
|
-
if (!fs.existsSync(dir)) {
|
|
27
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
28
|
-
}
|
|
29
|
-
fs.writeFileSync(getCredentialsPath(), JSON.stringify(creds, null, 2), {
|
|
30
|
-
mode: 0o600,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
export function clearCredentials() {
|
|
34
|
-
if (hasCredentials()) {
|
|
35
|
-
fs.unlinkSync(getCredentialsPath());
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Check if token is actually expired (hard expiry check).
|
|
40
|
-
*/
|
|
1
|
+
export { hasCredentials, getCredentials, saveCredentials, clearCredentials, updateTokens, getCredentialsPath, setInsecureStorage, } from './credential-store.js';
|
|
2
|
+
import { getCredentials, saveCredentials } from './credential-store.js';
|
|
41
3
|
export function isTokenExpired(creds) {
|
|
42
4
|
return Date.now() >= creds.expiresAt;
|
|
43
5
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Get access token if available and not expired.
|
|
46
|
-
*/
|
|
47
6
|
export function getAccessToken() {
|
|
48
7
|
const creds = getCredentials();
|
|
49
8
|
if (!creds)
|
|
@@ -52,10 +11,6 @@ export function getAccessToken() {
|
|
|
52
11
|
return null;
|
|
53
12
|
return creds.accessToken;
|
|
54
13
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Save staging credentials to the credential cache.
|
|
57
|
-
* Staging credentials are tied to the access token lifecycle.
|
|
58
|
-
*/
|
|
59
14
|
export function saveStagingCredentials(staging) {
|
|
60
15
|
const creds = getCredentials();
|
|
61
16
|
if (!creds)
|
|
@@ -68,50 +23,12 @@ export function saveStagingCredentials(staging) {
|
|
|
68
23
|
},
|
|
69
24
|
});
|
|
70
25
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Get cached staging credentials if available and access token is still valid.
|
|
73
|
-
* Returns null if no cached credentials or if access token has expired.
|
|
74
|
-
*/
|
|
75
26
|
export function getStagingCredentials() {
|
|
76
27
|
const creds = getCredentials();
|
|
77
28
|
if (!creds?.staging)
|
|
78
29
|
return null;
|
|
79
|
-
// Invalidate staging credentials when access token expires
|
|
80
30
|
if (isTokenExpired(creds))
|
|
81
31
|
return null;
|
|
82
32
|
return { clientId: creds.staging.clientId, apiKey: creds.staging.apiKey };
|
|
83
33
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Atomically update tokens in credentials file.
|
|
86
|
-
* Uses write-to-temp + rename pattern for atomic updates.
|
|
87
|
-
*/
|
|
88
|
-
export function updateTokens(accessToken, expiresAt, refreshToken) {
|
|
89
|
-
const creds = getCredentials();
|
|
90
|
-
if (!creds) {
|
|
91
|
-
throw new Error('No existing credentials to update');
|
|
92
|
-
}
|
|
93
|
-
const updated = {
|
|
94
|
-
...creds,
|
|
95
|
-
accessToken,
|
|
96
|
-
expiresAt,
|
|
97
|
-
...(refreshToken && { refreshToken }),
|
|
98
|
-
};
|
|
99
|
-
// Atomic write: temp file + rename
|
|
100
|
-
const credPath = getCredentialsPath();
|
|
101
|
-
const tempPath = `${credPath}.${crypto.randomUUID()}.tmp`;
|
|
102
|
-
try {
|
|
103
|
-
fs.writeFileSync(tempPath, JSON.stringify(updated, null, 2), { mode: 0o600 });
|
|
104
|
-
fs.renameSync(tempPath, credPath);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
// Clean up temp file if rename failed
|
|
108
|
-
try {
|
|
109
|
-
fs.unlinkSync(tempPath);
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// Ignore cleanup errors
|
|
113
|
-
}
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
34
|
//# sourceMappingURL=credentials.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExE,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,SAAS,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,cAAc,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,KAAK,CAAC,WAAW,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAA6C;IAClF,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,eAAe,CAAC;QACd,GAAG,KAAK;QACR,OAAO,EAAE;YACP,GAAG,OAAO;YACV,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,cAAc,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;AAC5E,CAAC","sourcesContent":["export type { StagingCache, Credentials } from './credential-store.js';\n\nexport {\n hasCredentials,\n getCredentials,\n saveCredentials,\n clearCredentials,\n updateTokens,\n getCredentialsPath,\n setInsecureStorage,\n} from './credential-store.js';\n\nimport type { Credentials } from './credential-store.js';\nimport { getCredentials, saveCredentials } from './credential-store.js';\n\nexport function isTokenExpired(creds: Credentials): boolean {\n return Date.now() >= creds.expiresAt;\n}\n\nexport function getAccessToken(): string | null {\n const creds = getCredentials();\n if (!creds) return null;\n if (isTokenExpired(creds)) return null;\n return creds.accessToken;\n}\n\nexport function saveStagingCredentials(staging: { clientId: string; apiKey: string }): void {\n const creds = getCredentials();\n if (!creds) return;\n\n saveCredentials({\n ...creds,\n staging: {\n ...staging,\n fetchedAt: Date.now(),\n },\n });\n}\n\nexport function getStagingCredentials(): { clientId: string; apiKey: string } | null {\n const creds = getCredentials();\n if (!creds?.staging) return null;\n if (isTokenExpired(creds)) return null;\n return { clientId: creds.staging.clientId, apiKey: creds.staging.apiKey };\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { lt, valid } from 'semver';
|
|
2
|
+
import { yellow, dim } from '../utils/logging.js';
|
|
3
|
+
import { getVersion } from './settings.js';
|
|
4
|
+
const NPM_REGISTRY_URL = 'https://registry.npmjs.org/workos/latest';
|
|
5
|
+
const TIMEOUT_MS = 500;
|
|
6
|
+
let hasWarned = false;
|
|
7
|
+
/**
|
|
8
|
+
* Check npm registry for latest version and warn if outdated.
|
|
9
|
+
* Runs asynchronously, fails silently on any error.
|
|
10
|
+
* Safe to call without awaiting (fire-and-forget).
|
|
11
|
+
*/
|
|
12
|
+
export async function checkForUpdates() {
|
|
13
|
+
if (hasWarned)
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
17
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok)
|
|
20
|
+
return;
|
|
21
|
+
const data = (await response.json());
|
|
22
|
+
const latestVersion = data.version;
|
|
23
|
+
const currentVersion = getVersion();
|
|
24
|
+
// Validate both versions are valid semver
|
|
25
|
+
if (!valid(latestVersion) || !valid(currentVersion))
|
|
26
|
+
return;
|
|
27
|
+
// Only warn if current < latest
|
|
28
|
+
if (lt(currentVersion, latestVersion)) {
|
|
29
|
+
hasWarned = true;
|
|
30
|
+
yellow(`Update available: ${currentVersion} → ${latestVersion}`);
|
|
31
|
+
dim(`Run: npx workos@latest`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Silently ignore all errors (timeout, network, parse, etc.)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reset warning state (for testing).
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function _resetWarningState() {
|
|
43
|
+
hasWarned = false;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=version-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/lib/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,MAAM,gBAAgB,GAAG,0CAA0C,CAAC;AACpE,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,IAAI,SAAS,GAAG,KAAK,CAAC;AAMtB;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,SAAS;QAAE,OAAO;IAEtB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gBAAgB,EAAE;YAC7C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO;QAEzB,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAC;QACvD,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC;QACnC,MAAM,cAAc,GAAG,UAAU,EAAE,CAAC;QAEpC,0CAA0C;QAC1C,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC;YAAE,OAAO;QAE5D,gCAAgC;QAChC,IAAI,EAAE,CAAC,cAAc,EAAE,aAAa,CAAC,EAAE,CAAC;YACtC,SAAS,GAAG,IAAI,CAAC;YACjB,MAAM,CAAC,qBAAqB,cAAc,MAAM,aAAa,EAAE,CAAC,CAAC;YACjE,GAAG,CAAC,wBAAwB,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;IAC/D,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,SAAS,GAAG,KAAK,CAAC;AACpB,CAAC","sourcesContent":["import { lt, valid } from 'semver';\nimport { yellow, dim } from '../utils/logging.js';\nimport { getVersion } from './settings.js';\n\nconst NPM_REGISTRY_URL = 'https://registry.npmjs.org/workos/latest';\nconst TIMEOUT_MS = 500;\n\nlet hasWarned = false;\n\ninterface NpmPackageInfo {\n version: string;\n}\n\n/**\n * Check npm registry for latest version and warn if outdated.\n * Runs asynchronously, fails silently on any error.\n * Safe to call without awaiting (fire-and-forget).\n */\nexport async function checkForUpdates(): Promise<void> {\n if (hasWarned) return;\n\n try {\n const response = await fetch(NPM_REGISTRY_URL, {\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n\n if (!response.ok) return;\n\n const data = (await response.json()) as NpmPackageInfo;\n const latestVersion = data.version;\n const currentVersion = getVersion();\n\n // Validate both versions are valid semver\n if (!valid(latestVersion) || !valid(currentVersion)) return;\n\n // Only warn if current < latest\n if (lt(currentVersion, latestVersion)) {\n hasWarned = true;\n yellow(`Update available: ${currentVersion} → ${latestVersion}`);\n dim(`Run: npx workos@latest`);\n }\n } catch {\n // Silently ignore all errors (timeout, network, parse, etc.)\n }\n}\n\n/**\n * Reset warning state (for testing).\n * @internal\n */\nexport function _resetWarningState(): void {\n hasWarned = false;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ExecResult {
|
|
2
|
+
status: number;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ExecOptions {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Execute a command without throwing on non-zero exit codes.
|
|
13
|
+
* Returns { status, stdout, stderr } for all outcomes.
|
|
14
|
+
*/
|
|
15
|
+
export declare function execFileNoThrow(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Execute a command without throwing on non-zero exit codes.
|
|
4
|
+
* Returns { status, stdout, stderr } for all outcomes.
|
|
5
|
+
*/
|
|
6
|
+
export function execFileNoThrow(command, args, options = {}) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const child = spawn(command, args, {
|
|
9
|
+
cwd: options.cwd,
|
|
10
|
+
env: options.env ?? process.env,
|
|
11
|
+
timeout: options.timeout,
|
|
12
|
+
shell: false,
|
|
13
|
+
});
|
|
14
|
+
let stdout = '';
|
|
15
|
+
let stderr = '';
|
|
16
|
+
child.stdout?.on('data', (data) => {
|
|
17
|
+
stdout += data.toString();
|
|
18
|
+
});
|
|
19
|
+
child.stderr?.on('data', (data) => {
|
|
20
|
+
stderr += data.toString();
|
|
21
|
+
});
|
|
22
|
+
child.on('close', (code) => {
|
|
23
|
+
resolve({
|
|
24
|
+
status: code ?? 1,
|
|
25
|
+
stdout,
|
|
26
|
+
stderr,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
child.on('error', (err) => {
|
|
30
|
+
resolve({
|
|
31
|
+
status: 1,
|
|
32
|
+
stdout,
|
|
33
|
+
stderr: err.message,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=exec-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec-file.js","sourceRoot":"","sources":["../../src/utils/exec-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAc3C;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,IAAc,EAAE,UAAuB,EAAE;IACxF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACjC,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;YAC/B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;QAEH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAChC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,OAAO,CAAC;gBACN,MAAM,EAAE,IAAI,IAAI,CAAC;gBACjB,MAAM;gBACN,MAAM;aACP,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,OAAO,CAAC;gBACN,MAAM,EAAE,CAAC;gBACT,MAAM;gBACN,MAAM,EAAE,GAAG,CAAC,OAAO;aACpB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { spawn } from 'node:child_process';\n\nexport interface ExecResult {\n status: number;\n stdout: string;\n stderr: string;\n}\n\nexport interface ExecOptions {\n cwd?: string;\n timeout?: number;\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Execute a command without throwing on non-zero exit codes.\n * Returns { status, stdout, stderr } for all outcomes.\n */\nexport function execFileNoThrow(command: string, args: string[], options: ExecOptions = {}): Promise<ExecResult> {\n return new Promise((resolve) => {\n const child = spawn(command, args, {\n cwd: options.cwd,\n env: options.env ?? process.env,\n timeout: options.timeout,\n shell: false,\n });\n\n let stdout = '';\n let stderr = '';\n\n child.stdout?.on('data', (data) => {\n stdout += data.toString();\n });\n\n child.stderr?.on('data', (data) => {\n stderr += data.toString();\n });\n\n child.on('close', (code) => {\n resolve({\n status: code ?? 1,\n stdout,\n stderr,\n });\n });\n\n child.on('error', (err) => {\n resolve({\n status: 1,\n stdout,\n stderr: err.message,\n });\n });\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Official Workos CLI",
|
|
6
6
|
"repository": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
42
42
|
"@clack/core": "^0.5.0",
|
|
43
43
|
"@clack/prompts": "0.11.0",
|
|
44
|
+
"@napi-rs/keyring": "^1.2.0",
|
|
44
45
|
"chalk": "^5.6.2",
|
|
45
46
|
"diff": "^8.0.3",
|
|
46
47
|
"fast-glob": "^3.3.3",
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
"@vitest/coverage-v8": "^4.0.18",
|
|
64
65
|
"@vitest/ui": "^4.0.18",
|
|
65
66
|
"dotenv": "^17.2.3",
|
|
67
|
+
"p-limit": "^7.2.0",
|
|
66
68
|
"prettier": "^3.8.0",
|
|
67
69
|
"tsx": "^4.20.3",
|
|
68
70
|
"typescript": "^5.9.3",
|
|
@@ -86,6 +88,12 @@
|
|
|
86
88
|
"test": "vitest run",
|
|
87
89
|
"test:watch": "vitest",
|
|
88
90
|
"test:coverage": "vitest run --coverage",
|
|
89
|
-
"typecheck": "tsc --noEmit"
|
|
91
|
+
"typecheck": "tsc --noEmit",
|
|
92
|
+
"eval": "tsx tests/evals/index.ts",
|
|
93
|
+
"eval:history": "tsx tests/evals/index.ts history",
|
|
94
|
+
"eval:diff": "tsx tests/evals/index.ts diff",
|
|
95
|
+
"eval:prune": "tsx tests/evals/index.ts prune",
|
|
96
|
+
"eval:logs": "tsx tests/evals/index.ts logs",
|
|
97
|
+
"eval:show": "tsx tests/evals/index.ts show"
|
|
90
98
|
}
|
|
91
99
|
}
|
|
@@ -55,6 +55,46 @@ Next.js version?
|
|
|
55
55
|
|
|
56
56
|
Middleware/proxy code: See README for `authkitMiddleware()` export pattern.
|
|
57
57
|
|
|
58
|
+
### Existing Middleware (IMPORTANT)
|
|
59
|
+
|
|
60
|
+
If `middleware.ts` already exists with custom logic (rate limiting, logging, headers, etc.), use the **`authkit()` composable function** instead of `authkitMiddleware`.
|
|
61
|
+
|
|
62
|
+
**Pattern for composing with existing middleware:**
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
66
|
+
import { authkit, handleAuthkitHeaders } from '@workos-inc/authkit-nextjs';
|
|
67
|
+
|
|
68
|
+
export default async function middleware(request: NextRequest) {
|
|
69
|
+
// 1. Get auth session and headers from AuthKit
|
|
70
|
+
const { session, headers, authorizationUrl } = await authkit(request);
|
|
71
|
+
const { pathname } = request.nextUrl;
|
|
72
|
+
|
|
73
|
+
// 2. === YOUR EXISTING MIDDLEWARE LOGIC ===
|
|
74
|
+
// Rate limiting, logging, custom headers, etc.
|
|
75
|
+
const rateLimitResult = checkRateLimit(request);
|
|
76
|
+
if (!rateLimitResult.allowed) {
|
|
77
|
+
return new NextResponse('Too Many Requests', { status: 429 });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. Protect routes - redirect to auth if needed
|
|
81
|
+
if (pathname.startsWith('/dashboard') && !session.user && authorizationUrl) {
|
|
82
|
+
return handleAuthkitHeaders(request, headers, { redirect: authorizationUrl });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 4. Continue with AuthKit headers properly handled
|
|
86
|
+
return handleAuthkitHeaders(request, headers);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Key functions:**
|
|
91
|
+
|
|
92
|
+
- `authkit(request)` - Returns `{ session, headers, authorizationUrl }` for composition
|
|
93
|
+
- `handleAuthkitHeaders(request, headers, options?)` - Ensures AuthKit headers pass through correctly
|
|
94
|
+
- For rewrites, use `partitionAuthkitHeaders()` and `applyResponseHeaders()` (see README)
|
|
95
|
+
|
|
96
|
+
**Critical:** Always return via `handleAuthkitHeaders()` to ensure `withAuth()` works in pages.
|
|
97
|
+
|
|
58
98
|
## Step 5: Create Callback Route
|
|
59
99
|
|
|
60
100
|
Parse `NEXT_PUBLIC_WORKOS_REDIRECT_URI` to determine route path:
|
|
@@ -78,33 +118,58 @@ export const GET = handleAuth();
|
|
|
78
118
|
|
|
79
119
|
Check README for exact usage. If build fails with "cookies outside request scope", the handler is likely missing async/await.
|
|
80
120
|
|
|
81
|
-
## Step 6: Provider Setup
|
|
121
|
+
## Step 6: Provider Setup (REQUIRED)
|
|
122
|
+
|
|
123
|
+
**CRITICAL:** You MUST wrap the app in `AuthKitProvider` in `app/layout.tsx`.
|
|
124
|
+
|
|
125
|
+
This is required for:
|
|
126
|
+
|
|
127
|
+
- Client-side auth state via `useAuth()` hook
|
|
128
|
+
- Consistent auth UX across client/server boundaries
|
|
129
|
+
- Proper migration from Auth0 (which uses client-side auth)
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// app/layout.tsx
|
|
133
|
+
import { AuthKitProvider } from '@workos-inc/authkit-nextjs';
|
|
134
|
+
|
|
135
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
136
|
+
return (
|
|
137
|
+
<html lang="en">
|
|
138
|
+
<body>
|
|
139
|
+
<AuthKitProvider>{children}</AuthKitProvider>
|
|
140
|
+
</body>
|
|
141
|
+
</html>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Check README for exact import path - it may be a subpath export like `@workos-inc/authkit-nextjs/components`.
|
|
82
147
|
|
|
83
|
-
|
|
148
|
+
**Do NOT skip this step** even if using server-side auth patterns elsewhere.
|
|
84
149
|
|
|
85
150
|
## Step 7: UI Integration
|
|
86
151
|
|
|
87
152
|
Add auth UI to `app/page.tsx` using SDK functions. See README for `getUser`, `getSignInUrl`, `signOut` usage.
|
|
88
153
|
|
|
89
|
-
## Verification Checklist
|
|
154
|
+
## Verification Checklist (ALL MUST PASS)
|
|
90
155
|
|
|
91
|
-
Run these commands to confirm integration
|
|
156
|
+
Run these commands to confirm integration. **Do not mark complete until all pass:**
|
|
92
157
|
|
|
93
158
|
```bash
|
|
94
|
-
# Check middleware/proxy exists (one should match)
|
|
159
|
+
# 1. Check middleware/proxy exists (one should match)
|
|
95
160
|
ls proxy.ts middleware.ts src/proxy.ts src/middleware.ts 2>/dev/null
|
|
96
161
|
|
|
97
|
-
# Check
|
|
98
|
-
grep
|
|
162
|
+
# 2. CRITICAL: Check AuthKitProvider is in layout (REQUIRED)
|
|
163
|
+
grep "AuthKitProvider" app/layout.tsx || echo "FAIL: AuthKitProvider missing from layout"
|
|
99
164
|
|
|
100
|
-
# Check callback route exists
|
|
165
|
+
# 3. Check callback route exists
|
|
101
166
|
find app -name "route.ts" -path "*/callback/*"
|
|
102
167
|
|
|
103
|
-
# Build succeeds
|
|
168
|
+
# 4. Build succeeds
|
|
104
169
|
npm run build
|
|
105
170
|
```
|
|
106
171
|
|
|
107
|
-
|
|
172
|
+
**If check #2 fails:** Go back to Step 6 and add AuthKitProvider. This is not optional.
|
|
108
173
|
|
|
109
174
|
## Error Recovery
|
|
110
175
|
|
|
@@ -12,9 +12,9 @@ description: Integrate WorkOS AuthKit with TanStack Start applications. Full-sta
|
|
|
12
12
|
├── Extract package name from install command
|
|
13
13
|
└── README is source of truth for ALL code patterns
|
|
14
14
|
|
|
15
|
-
2.
|
|
16
|
-
├──
|
|
17
|
-
└── app
|
|
15
|
+
2. Detect directory structure
|
|
16
|
+
├── src/ (TanStack Start v1.132+, default)
|
|
17
|
+
└── app/ (legacy vinxi-based projects)
|
|
18
18
|
|
|
19
19
|
3. Follow README install/setup exactly
|
|
20
20
|
└── Do not invent commands or patterns
|
|
@@ -28,7 +28,7 @@ WebFetch: `https://github.com/workos/authkit-tanstack-start/blob/main/README.md`
|
|
|
28
28
|
|
|
29
29
|
From README, extract:
|
|
30
30
|
|
|
31
|
-
1. Package name
|
|
31
|
+
1. Package name: `@workos/authkit-tanstack-react-start`
|
|
32
32
|
2. Use that exact name for all imports
|
|
33
33
|
|
|
34
34
|
**README overrides this skill if conflict.**
|
|
@@ -37,9 +37,38 @@ From README, extract:
|
|
|
37
37
|
|
|
38
38
|
- [ ] README fetched and package name extracted
|
|
39
39
|
- [ ] `@tanstack/start` or `@tanstack/react-start` in package.json
|
|
40
|
-
- [ ] `app
|
|
40
|
+
- [ ] Identify directory structure: `src/` (modern) or `app/` (legacy)
|
|
41
41
|
- [ ] Environment variables set (see below)
|
|
42
42
|
|
|
43
|
+
## Directory Structure Detection
|
|
44
|
+
|
|
45
|
+
**Modern TanStack Start (v1.132+)** uses `src/`:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
src/
|
|
49
|
+
├── start.ts # Middleware config (CRITICAL)
|
|
50
|
+
├── router.tsx # Router setup
|
|
51
|
+
├── routes/
|
|
52
|
+
│ ├── __root.tsx # Root layout
|
|
53
|
+
│ ├── api.auth.callback.tsx # OAuth callback (flat route)
|
|
54
|
+
│ └── ...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Legacy (vinxi-based)** uses `app/`:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
app/
|
|
61
|
+
├── start.ts or router.tsx
|
|
62
|
+
├── routes/
|
|
63
|
+
│ └── api/auth/callback.tsx # OAuth callback (nested route)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Detection:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
ls src/routes 2>/dev/null && echo "Modern (src/)" || echo "Legacy (app/)"
|
|
70
|
+
```
|
|
71
|
+
|
|
43
72
|
## Environment Variables
|
|
44
73
|
|
|
45
74
|
| Variable | Format | Required |
|
|
@@ -51,56 +80,184 @@ From README, extract:
|
|
|
51
80
|
|
|
52
81
|
Generate password if missing: `openssl rand -base64 32`
|
|
53
82
|
|
|
83
|
+
Default redirect URI: `http://localhost:3000/api/auth/callback`
|
|
84
|
+
|
|
54
85
|
## Middleware Configuration (CRITICAL)
|
|
55
86
|
|
|
56
|
-
**authkitMiddleware MUST be configured or auth will fail.**
|
|
87
|
+
**authkitMiddleware MUST be configured or auth will fail silently.**
|
|
57
88
|
|
|
58
|
-
|
|
89
|
+
Create or update `src/start.ts` (or `app/start.ts` for legacy):
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { authkitMiddleware } from '@workos/authkit-tanstack-react-start';
|
|
93
|
+
|
|
94
|
+
export default {
|
|
95
|
+
requestMiddleware: [authkitMiddleware()],
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Alternative pattern with createStart:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { createStart } from '@tanstack/react-start';
|
|
103
|
+
import { authkitMiddleware } from '@workos/authkit-tanstack-react-start';
|
|
104
|
+
|
|
105
|
+
export default createStart({
|
|
106
|
+
requestMiddleware: [authkitMiddleware()],
|
|
107
|
+
});
|
|
108
|
+
```
|
|
59
109
|
|
|
60
110
|
### Verification Checklist
|
|
61
111
|
|
|
62
|
-
- [ ] `authkitMiddleware` imported from
|
|
63
|
-
- [ ]
|
|
64
|
-
- [ ]
|
|
112
|
+
- [ ] `authkitMiddleware` imported from `@workos/authkit-tanstack-react-start`
|
|
113
|
+
- [ ] Middleware in `requestMiddleware` array
|
|
114
|
+
- [ ] File exports the config (default export or named `startInstance`)
|
|
65
115
|
|
|
66
|
-
Verify: `grep "authkitMiddleware"
|
|
116
|
+
Verify: `grep -r "authkitMiddleware" src/ app/ 2>/dev/null`
|
|
117
|
+
|
|
118
|
+
## Callback Route (CRITICAL)
|
|
119
|
+
|
|
120
|
+
Path must match `WORKOS_REDIRECT_URI`. For `/api/auth/callback`:
|
|
121
|
+
|
|
122
|
+
**Modern (flat routes):** `src/routes/api.auth.callback.tsx`
|
|
123
|
+
**Legacy (nested routes):** `app/routes/api/auth/callback.tsx`
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
127
|
+
import { handleCallbackRoute } from '@workos/authkit-tanstack-react-start';
|
|
128
|
+
|
|
129
|
+
export const Route = createFileRoute('/api/auth/callback')({
|
|
130
|
+
server: {
|
|
131
|
+
handlers: {
|
|
132
|
+
GET: handleCallbackRoute(),
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
67
137
|
|
|
68
|
-
|
|
138
|
+
**Key points:**
|
|
69
139
|
|
|
70
|
-
|
|
140
|
+
- Use `handleCallbackRoute()` - do not write custom OAuth logic
|
|
141
|
+
- Route path string must match the URI path exactly
|
|
142
|
+
- This is a server-only route (no component needed)
|
|
71
143
|
|
|
72
|
-
##
|
|
144
|
+
## Protected Routes
|
|
73
145
|
|
|
74
|
-
|
|
146
|
+
Use `getAuth()` in route loaders to check authentication:
|
|
75
147
|
|
|
76
|
-
|
|
77
|
-
|
|
148
|
+
```typescript
|
|
149
|
+
import { createFileRoute, redirect } from '@tanstack/react-router';
|
|
150
|
+
import { getAuth, getSignInUrl } from '@workos/authkit-tanstack-react-start';
|
|
151
|
+
|
|
152
|
+
export const Route = createFileRoute('/dashboard')({
|
|
153
|
+
loader: async () => {
|
|
154
|
+
const { user } = await getAuth();
|
|
155
|
+
if (!user) {
|
|
156
|
+
const signInUrl = await getSignInUrl();
|
|
157
|
+
throw redirect({ href: signInUrl });
|
|
158
|
+
}
|
|
159
|
+
return { user };
|
|
160
|
+
},
|
|
161
|
+
component: Dashboard,
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Sign Out Route
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { createFileRoute, redirect } from '@tanstack/react-router';
|
|
169
|
+
import { signOut } from '@workos/authkit-tanstack-react-start';
|
|
170
|
+
|
|
171
|
+
export const Route = createFileRoute('/signout')({
|
|
172
|
+
loader: async () => {
|
|
173
|
+
await signOut();
|
|
174
|
+
throw redirect({ href: '/' });
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Client-Side Hooks (Optional)
|
|
180
|
+
|
|
181
|
+
Only needed if you want reactive auth state in components.
|
|
182
|
+
|
|
183
|
+
**1. Add AuthKitProvider to root:**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// src/routes/__root.tsx
|
|
187
|
+
import { AuthKitProvider } from '@workos/authkit-tanstack-react-start/client';
|
|
188
|
+
|
|
189
|
+
function RootComponent() {
|
|
190
|
+
return (
|
|
191
|
+
<AuthKitProvider>
|
|
192
|
+
<Outlet />
|
|
193
|
+
</AuthKitProvider>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**2. Use hooks in components:**
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { useAuth } from '@workos/authkit-tanstack-react-start/client';
|
|
202
|
+
|
|
203
|
+
function Profile() {
|
|
204
|
+
const { user, isLoading } = useAuth();
|
|
205
|
+
// ...
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Note:** Server-side `getAuth()` is preferred for most use cases.
|
|
78
210
|
|
|
79
211
|
## Error Recovery
|
|
80
212
|
|
|
81
213
|
### "AuthKit middleware is not configured"
|
|
82
214
|
|
|
83
|
-
**Cause:** `authkitMiddleware()` not
|
|
84
|
-
**Fix:**
|
|
85
|
-
**Verify:** `grep "authkitMiddleware"
|
|
215
|
+
**Cause:** `authkitMiddleware()` not in start.ts
|
|
216
|
+
**Fix:** Create/update `src/start.ts` with middleware config
|
|
217
|
+
**Verify:** `grep -r "authkitMiddleware" src/`
|
|
86
218
|
|
|
87
219
|
### "Module not found" for SDK
|
|
88
220
|
|
|
89
221
|
**Cause:** Wrong package name or not installed
|
|
90
|
-
**Fix:**
|
|
91
|
-
**Verify:** `ls node_modules
|
|
222
|
+
**Fix:** `pnpm add @workos/authkit-tanstack-react-start`
|
|
223
|
+
**Verify:** `ls node_modules/@workos/authkit-tanstack-react-start`
|
|
92
224
|
|
|
93
225
|
### Callback 404
|
|
94
226
|
|
|
95
|
-
**Cause:** Route path doesn't match WORKOS_REDIRECT_URI
|
|
96
|
-
**Fix:**
|
|
227
|
+
**Cause:** Route file path doesn't match WORKOS_REDIRECT_URI
|
|
228
|
+
**Fix:**
|
|
229
|
+
|
|
230
|
+
- URI `/api/auth/callback` → file `src/routes/api.auth.callback.tsx` (flat) or `app/routes/api/auth/callback.tsx` (nested)
|
|
231
|
+
- Route path string in `createFileRoute()` must match exactly
|
|
97
232
|
|
|
98
|
-
### getAuth returns undefined
|
|
233
|
+
### getAuth returns undefined user
|
|
99
234
|
|
|
100
|
-
**Cause:** Middleware not configured
|
|
101
|
-
**Fix:**
|
|
235
|
+
**Cause:** Middleware not configured or not running
|
|
236
|
+
**Fix:** Ensure `authkitMiddleware()` is in start.ts requestMiddleware array
|
|
102
237
|
|
|
103
238
|
### "Cookie password too short"
|
|
104
239
|
|
|
105
240
|
**Cause:** WORKOS_COOKIE_PASSWORD < 32 chars
|
|
106
241
|
**Fix:** `openssl rand -base64 32`, update .env
|
|
242
|
+
|
|
243
|
+
### Build fails with route type errors
|
|
244
|
+
|
|
245
|
+
**Cause:** Route tree not regenerated after adding routes
|
|
246
|
+
**Fix:** `pnpm dev` to regenerate `routeTree.gen.ts`
|
|
247
|
+
|
|
248
|
+
## SDK Exports Reference
|
|
249
|
+
|
|
250
|
+
**Server (main export):**
|
|
251
|
+
|
|
252
|
+
- `authkitMiddleware()` - Request middleware
|
|
253
|
+
- `handleCallbackRoute()` - OAuth callback handler
|
|
254
|
+
- `getAuth()` - Get current session
|
|
255
|
+
- `signOut()` - Sign out user
|
|
256
|
+
- `getSignInUrl()` / `getSignUpUrl()` - Auth URLs
|
|
257
|
+
- `switchToOrganization()` - Change org context
|
|
258
|
+
|
|
259
|
+
**Client (`/client` subpath):**
|
|
260
|
+
|
|
261
|
+
- `AuthKitProvider` - Context provider
|
|
262
|
+
- `useAuth()` - Auth state hook
|
|
263
|
+
- `useAccessToken()` - Token management
|