Skip to content

feat(cli): seed auth.json from env-supplied credentials (2/3)#537

Open
moranshe-max wants to merge 1 commit into
feature/scaffold-sharedfrom
feature/env-token-auth
Open

feat(cli): seed auth.json from env-supplied credentials (2/3)#537
moranshe-max wants to merge 1 commit into
feature/scaffold-sharedfrom
feature/env-token-auth

Conversation

@moranshe-max

@moranshe-max moranshe-max commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Note

Description

Adds support for seeding authentication from environment-supplied credentials so non-interactive flows (CI, agents, provisioning tools like the Stripe Projects CLI) can run the CLI with no manual `base44 login`. When `BASE44_ACCESS_TOKEN` and `BASE44_REFRESH_TOKEN` are present, the `ensureAuth` middleware decodes the access-token JWT and writes a standard `~/.base44/auth/auth.json`, so all downstream code uses one file-based auth path. Credentials are also loaded from project-local `.env`/`.env.local` at startup, with normalization of the Stripe Projects CLI's `BASE44_PROJECTS_BASE44_` variables to their bare `BASE44_` names.

Related Issue

None

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Other (please describe):

Changes Made

  • Added `seedAuthFromEnv()` in `core/auth/config.ts`: decodes the `BASE44_ACCESS_TOKEN` JWT (`sub` -> email, `exp` -> expiry; no signature verification, the server validates), pairs it with `BASE44_REFRESH_TOKEN`, and writes a standard auth file. No-ops unless the env vars form a complete record.
  • Wired `seedAuthFromEnv()` into the `ensureAuth` middleware before the login check, so env tokens satisfy auth without an interactive login.
  • Added `loadProjectEnvFiles()` in `core/utils/env.ts` to load `.env.local` then `.env` (ambient `process.env` always wins; `.env.local` beats `.env`), and `normalizeBase44Env()` to copy `BASE44_PROJECTS_BASE44_` vars to their bare `BASE44_` names when unset.
  • Added `cli/bootstrap-env.ts`, imported first in `cli/index.ts`, so env files load before the HTTP clients capture `getBase44ApiUrl()` at module load.
  • Documented the env-credential flow in `docs/api-patterns.md`.
  • Added a `givenEnv()` helper to the CLI testkit for injecting env vars into runs.

Testing

  • I have tested these changes locally
  • I have added/updated tests as needed
  • All tests pass (`npm test`)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (if applicable)
  • My changes generate no new warnings
  • I have updated `docs/` (AGENTS.md) if I made architectural changes

Additional Notes

The seeded file behaves like a normal login: a 401 triggers the usual refresh, and if refresh fails the file is deleted and self-heals on the next command (re-seeded from the still-present env vars). Adds a new dependency on `jsonwebtoken` for decoding the access token.


Generated by Claude | 2026-06-10 12:32 UTC | b493243

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/cli@0.0.54-pr.537.b493243

Prefer not to change any import paths? Install using npm alias so your code still imports base44:

npm i "base44@npm:@base44-preview/cli@0.0.54-pr.537.b493243"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "base44": "npm:@base44-preview/cli@0.0.54-pr.537.b493243"
  }
}

Preview published to npm registry — try new features instantly!

@moranshe-max moranshe-max force-pushed the feature/env-token-auth branch 5 times, most recently from dd70d92 to e502594 Compare June 9, 2026 12:40
@moranshe-max moranshe-max changed the title feat(cli): env-supplied credentials — .env auto-load + access token (2/3) feat(cli): seed auth.json from env-supplied credentials (2/3) Jun 9, 2026
@moranshe-max moranshe-max force-pushed the feature/env-token-auth branch 3 times, most recently from ab0b4ea to 54f3fe5 Compare June 9, 2026 13:56
@moranshe-max moranshe-max changed the title feat(cli): seed auth.json from env-supplied credentials (2/3) feat(cli): seed auth.json from env-supplied credentials (2/4) Jun 9, 2026
@moranshe-max moranshe-max changed the title feat(cli): seed auth.json from env-supplied credentials (2/4) feat(cli): seed auth.json from env-supplied credentials (2/3) Jun 10, 2026
Comment thread docs/api-patterns.md
`BASE44_API_URL`): for each, if the bare name is unset and exactly one
`<PREFIX>_<KEY>` variable exists, its value is copied to the bare name. This is
prefix-agnostic and leaves the bare key unset when ambiguous.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that this is too verbose, and we can minimize it. Not sure what's the value for the agent (not that i know if the AGENTS.md helps at all...)

Comment thread packages/cli/src/core/auth/config.ts Outdated
*
* @returns true if an auth file was written.
*/
export async function seedAuthFromEnv(): Promise<boolean> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this returned boolean is not used anywhere

/**
* Decodes a JWT payload's claims WITHOUT verifying the signature (display/expiry
* use only — the server still validates the token). Returns null for non-JWTs.
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this comment

Comment thread packages/cli/src/core/auth/config.ts Outdated
* form a standard record (not a JWT with `exp`, or no refresh token).
*
* @returns true if an auth file was written.
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove / minimize comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really should add a rule to AGENTS.md or something

}

normalizeBase44Env();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this can be replaced with a call to config() function from dotenv

process.env[bareKey] = process.env[matches[0]];
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider removing it if we expect specific env vars for now. even make the BASE44_ENV_KEYS to hold the specific STRIPE keys if they are static

@@ -1,3 +1,5 @@
// Must stay first — see bootstrap-env.js.
import "@/cli/bootstrap-env.js";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok lets remove this, and add an import to import { loadProjectEnvFiles } from "@/core/utils/env.js"; and inside runCLI function lets use findProjectRoot and pass it into loadProjectEnvFiles?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notice that loadProjectEnvFiles may return empty route if .app.jsonc not found

// Must run before any module reads env-derived config at import time — notably
// the HTTP clients, which capture getBase44ApiUrl() when ky.create() runs at
// module load. Imported first in cli/index.ts so it initializes ahead of them.
loadProjectEnvFiles();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented in index.ts file, i think this file should be removed

Comment thread packages/cli/src/core/auth/config.ts Outdated
* Decodes a JWT payload's claims WITHOUT verifying the signature (display/expiry
* use only — the server still validates the token). Returns null for non-JWTs.
*/
function decodeJwtClaims(token: string): Record<string, unknown> | null {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Artem added jsonwebtoken package for the dev server, can we use it here as well? no need to decode ourselved

* never overridden). Synchronous so it can run during bootstrap, before the HTTP
* clients capture `getBase44ApiUrl()`.
*/
export function loadProjectEnvFiles(cwd: string = process.cwd()): void {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change cwd to be "projectRoot" because we want to support the user running either from the project root, or from ~/my-project/base44 .. we don't want to force the user to be on the project root for this to work

const b64url = (obj: Record<string, unknown>) =>
Buffer.from(JSON.stringify(obj)).toString("base64url");
return `${b64url({ alg: "none", typ: "JWT" })}.${b64url(claims)}.sig`;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider of jsonwebtoken package can resolve this for you?

@kfirstri kfirstri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments, and i think we can tell the AI to chill with the comments :P

@moranshe-max moranshe-max force-pushed the feature/scaffold-shared branch from a74b402 to 9ee4aa1 Compare June 10, 2026 12:06
@moranshe-max moranshe-max force-pushed the feature/env-token-auth branch from 54f3fe5 to 8cc7307 Compare June 10, 2026 12:13
@moranshe-max moranshe-max force-pushed the feature/scaffold-shared branch from 9ee4aa1 to d77abe1 Compare June 10, 2026 12:29
Enables non-interactive credential handoff (CI, agents, provisioning tools) by
seeding the standard auth file from the environment, instead of special-casing
env vars throughout the token flow:

- The `ensureAuth` middleware calls `seedAuthFromEnv()` before the login check:
  when BASE44_ACCESS_TOKEN is set it decodes the JWT (sub -> email, exp ->
  expiresAt), reads BASE44_REFRESH_TOKEN, and writes a standard
  ~/.base44/auth/auth.json. readAuth / base44Client / isLoggedIn / whoami /
  refresh then all use one plain file-based path (no per-call-site special-casing;
  base44-client.ts and whoami.ts are untouched).
- Overwrites an existing login when env vars are present (env = source of truth);
  no-ops when the creds can't form a standard record (not a JWT with exp, or no
  refresh token).
- Loads .env/.env.local at startup via cli/bootstrap-env.ts (first import, before
  the HTTP clients capture getBase44ApiUrl()).
- Normalizes prefix-namespaced vars (e.g. <PREFIX>_BASE44_APP_ID) to the bare
  BASE44_* names; prefix-agnostic and left unset when ambiguous.

Adds a givenEnv testkit helper plus env-seeding and env-normalization specs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@moranshe-max moranshe-max force-pushed the feature/env-token-auth branch from 8cc7307 to 9daf582 Compare June 10, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants