@timekast/factory 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/provision.js +11 -1
- package/dist/lib/env-file.js +5 -0
- package/dist/lib/vercel-env.js +20 -0
- package/package.json +1 -1
|
@@ -28,7 +28,7 @@ import { CLIError } from '../lib/cli-error.js';
|
|
|
28
28
|
import { apexZoneOf, CloudflareApiError, CLOUDFLARE_API_BASE, ensureCnameRecord, findCnameRecord, resolveZoneId, resolveZoneIdForName, CNAME_VERCEL_TARGET, TIMEKAST_ZONE_NAME, provisionDomains, } from '../lib/cloudflare-api.js';
|
|
29
29
|
import { createDevelopBranch, createNeonProject, createProjectApiKey, deleteNeonApiKeysByName, deleteNeonProject, getConnectionUri, listNeonBranches, listNeonProjects, NeonApiError, NEON_API_BASE, NEON_DEVELOP_BRANCH, } from '../lib/neon-api.js';
|
|
30
30
|
import { defaultEnvPath, readVercelToken, runProvisionPreflight, } from '../lib/provision-preflight.js';
|
|
31
|
-
import { addVercelDomain, buildEnvPlan, createVercelProject, generateAuthSecret, getLinkedRepoId, getVercelDomainStatus, GitHubAppNotInstalledError, linkGitRepo, listVercelProjects, patchProjectSettings, pushEnvPlan, removeVercelDomain, triggerDeployment, upsertEnvVar, listEnvVars, VercelApiError, writeVercelProjectJson, } from '../lib/vercel-env.js';
|
|
31
|
+
import { addVercelDomain, buildEnvPlan, createVercelProject, generateAuthSecret, generateMfaEncryptionKey, getLinkedRepoId, getVercelDomainStatus, GitHubAppNotInstalledError, linkGitRepo, listVercelProjects, patchProjectSettings, pushEnvPlan, removeVercelDomain, triggerDeployment, upsertEnvVar, listEnvVars, VercelApiError, writeVercelProjectJson, } from '../lib/vercel-env.js';
|
|
32
32
|
import { writeEnvLocal } from '../lib/env-file.js';
|
|
33
33
|
import { adoptState, ADOPT_UNKNOWN, clearProvisionState, clearState, hasProvisionState, isServiceComplete, PROVISION_DIR, PROVISION_STATE_FILE, readDestroyTarget, readProvisionState, writeProvisionState, } from '../lib/provision-state.js';
|
|
34
34
|
/**
|
|
@@ -652,11 +652,21 @@ export async function provisionVercel(rootDir, state, teamId, deps = {}) {
|
|
|
652
652
|
writeVercelProjectJson(rootDir, { projectId, orgId: teamId, teamId }, { force: deps.force });
|
|
653
653
|
// Mint an INDEPENDENT AUTH_SECRET per environment (issue §4 — never shared).
|
|
654
654
|
// POOLED strings → the Vercel runtime DATABASE_URL (serverless/concurrent).
|
|
655
|
+
// WebAuthn rpID = the EXACT host per env (provision's interim domains). A passkey
|
|
656
|
+
// dies if the rpID changes, so a later custom-domain swap (--domain / PROV-006)
|
|
657
|
+
// must also re-point WEBAUTHN_RP_ID — tracked as a follow-up. MFA_ENCRYPTION_KEY
|
|
658
|
+
// is minted independently per env (base64url) so a derivative with MFA enabled
|
|
659
|
+
// never boots broken (P-1).
|
|
660
|
+
const rpDomains = provisionDomains(state.projectName);
|
|
655
661
|
const plan = buildEnvPlan({
|
|
656
662
|
mainDatabaseUrl: neonSecrets.mainPooled,
|
|
657
663
|
developDatabaseUrl: neonSecrets.developPooled,
|
|
658
664
|
authSecretProd: generateAuthSecret(),
|
|
659
665
|
authSecretPreview: generateAuthSecret(),
|
|
666
|
+
mfaEncryptionKeyProd: generateMfaEncryptionKey(),
|
|
667
|
+
mfaEncryptionKeyPreview: generateMfaEncryptionKey(),
|
|
668
|
+
webauthnRpIdProd: rpDomains.prod,
|
|
669
|
+
webauthnRpIdPreview: rpDomains.dev,
|
|
660
670
|
shared: readSharedVercelEnv(envPath),
|
|
661
671
|
});
|
|
662
672
|
let result;
|
package/dist/lib/env-file.js
CHANGED
|
@@ -25,6 +25,11 @@ export const PROVISION_OWNED = new Set([
|
|
|
25
25
|
'DATABASE_URL_MAIN',
|
|
26
26
|
'AUTH_SECRET',
|
|
27
27
|
'NEXT_PUBLIC_APP_ENV',
|
|
28
|
+
// Provision mints these per-env too (P-1): the MFA key (base64url) + the
|
|
29
|
+
// WebAuthn rpID (the exact host). A shared day-2 push would break passkeys
|
|
30
|
+
// (rpID must match the env's host) or the at-rest TOTP crypto.
|
|
31
|
+
'MFA_ENCRYPTION_KEY',
|
|
32
|
+
'WEBAUTHN_RP_ID',
|
|
28
33
|
]);
|
|
29
34
|
/**
|
|
30
35
|
* Replace the value of `KEY=...` in an env-file body, preserving every other line.
|
package/dist/lib/vercel-env.js
CHANGED
|
@@ -369,6 +369,17 @@ export async function upsertEnvVar(projectId, token, teamId, plan, existing, dep
|
|
|
369
369
|
export function generateAuthSecret() {
|
|
370
370
|
return randomBytes(32).toString('hex');
|
|
371
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Generate a fresh `MFA_ENCRYPTION_KEY` — the AES-256 key the runtime uses to
|
|
374
|
+
* encrypt TOTP secrets at rest (`src/lib/auth/totp-crypto.ts`). It MUST be
|
|
375
|
+
* base64url of EXACTLY 32 bytes — NOT hex like `AUTH_SECRET` (the runtime rejects
|
|
376
|
+
* a wrong-length/format key). Minted independently per environment so prod and
|
|
377
|
+
* preview never share it. Without this var a derivative with MFA enabled boots
|
|
378
|
+
* broken (P-1).
|
|
379
|
+
*/
|
|
380
|
+
export function generateMfaEncryptionKey() {
|
|
381
|
+
return randomBytes(32).toString('base64url');
|
|
382
|
+
}
|
|
372
383
|
/** Absolute path to `.vercel/project.json` under a repo root. */
|
|
373
384
|
export function vercelProjectPath(rootDir) {
|
|
374
385
|
return path.join(rootDir, VERCEL_DIR, VERCEL_PROJECT_FILE);
|
|
@@ -447,6 +458,15 @@ export function buildEnvPlan(input) {
|
|
|
447
458
|
// Auth secret — distinct value per environment (never shared).
|
|
448
459
|
{ key: 'AUTH_SECRET', value: input.authSecretProd, target: ['production'] },
|
|
449
460
|
{ key: 'AUTH_SECRET', value: input.authSecretPreview, target: ['preview'] },
|
|
461
|
+
// MFA encryption key — distinct per env (base64url 32 bytes). Without it a
|
|
462
|
+
// derivative with MFA enabled boots broken; minted independently so prod and
|
|
463
|
+
// preview never share it (P-1).
|
|
464
|
+
{ key: 'MFA_ENCRYPTION_KEY', value: input.mfaEncryptionKeyProd, target: ['production'] },
|
|
465
|
+
{ key: 'MFA_ENCRYPTION_KEY', value: input.mfaEncryptionKeyPreview, target: ['preview'] },
|
|
466
|
+
// WebAuthn relying-party id — the EXACT host per env (a passkey dies if the
|
|
467
|
+
// rpID changes), so passkeys work without manual Vercel setup (P-1 / PASSKEY-002 §8).
|
|
468
|
+
{ key: 'WEBAUTHN_RP_ID', value: input.webauthnRpIdProd, target: ['production'] },
|
|
469
|
+
{ key: 'WEBAUTHN_RP_ID', value: input.webauthnRpIdPreview, target: ['preview'] },
|
|
450
470
|
// Deployment identity — drives the env-aware PWA manifest (see .env.example).
|
|
451
471
|
{ key: 'NEXT_PUBLIC_APP_ENV', value: 'production', target: ['production'] },
|
|
452
472
|
{ key: 'NEXT_PUBLIC_APP_ENV', value: 'preview', target: ['preview'] },
|