[DIT-13378] Add ability to sign in through OAuth to Ditto CLI - #147
Conversation
There was a problem hiding this comment.
just as reminder we'll need proper release note in changelog for 5.7 update: https://github.com/dittowords/cli/releases
| get apiToken() { | ||
| return this.#apiToken; | ||
| } |
There was a problem hiding this comment.
would this be better renamed to authToken?
| import appContext from "../utils/appContext"; | ||
| import logger from "../utils/logger"; | ||
|
|
||
| /** Forgets the local session. Doesn't revoke it at Auth0. */ |
There was a problem hiding this comment.
any reason we don't revoke the auth0 connection as well?
There was a problem hiding this comment.
Was trying to keep the scope tidy but a best effort attempt is a pretty small lift. I added it
| const toSession = (data: any): OAuthSession => ({ | ||
| accessToken: data.access_token, | ||
| refreshToken: data.refresh_token, | ||
| expiresAt: | ||
| Date.now() + | ||
| ((data.expires_in ?? DEFAULT_EXPIRY_SECONDS) - EXPIRY_MARGIN_SECONDS) * | ||
| 1000, | ||
| }); |
There was a problem hiding this comment.
this is an AI flagged defensive nit but seems reasonable, we shouldn't try creating a session if access token is invalid:
a. Don't build a session out of a response you didn't check — loopbackFlow.ts:53:
const toSession = (data: any): OAuthSession => {
if (typeof data?.access_token !== "string" || !data.access_token) {
throw authError("We couldn't finish the login. The response didn't include an access token.");
}
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresAt: Date.now() + ((data.expires_in ?? DEFAULT_EXPIRY_SECONDS) - EXPIRY_MARGIN_SECONDS) * 1000,
};
};
refreshSession then needs to treat that throw as "refresh failed" rather than letting it escape mid-pull:
try {
return { ...toSession(response.data), refreshToken: response.data.refresh_token || refreshToken };
} catch {
return null;
}
|
|
||
| const base64url = (bytes: Buffer) => bytes.toString("base64url"); | ||
|
|
||
| // PKCE stands in for a client secret, which a published CLI can't keep. |
There was a problem hiding this comment.
| // PKCE stands in for a client secret, which a published CLI can't keep. | |
| // Creates Proof Key for Code Exchange (PKCE) challenger + verifier for oauth connection | |
| // PKCE stands in for a client secret, which a published CLI can't keep. |
|
Another AI-ism but it recommends wrapping file in Take it with an AI grain of salt but might be worth for security's sake? |
JWhite30515
left a comment
There was a problem hiding this comment.
Don't have the greatest low-level understanding of everything we're doing but high-level LGTM, just left some nits, we should definitely handle the warned node error for url.parse I highlighted
…en revocation on logout


CLI: Add OAuth login
Overview
Adds
ditto loginandditto logoutso you can authenticate in a browser instead of pasting an API key.Login uses the OAuth Authorization Code flow with PKCE. The session is saved to
~/.config/dittonext to any existing API keys.Credential precedence:
DITTO_TOKENset in environment (so as not to break CI)Other notes:
httpandcryptofrom Node, andopen, which the CLI already used.--legacymode, which requires atokenkey on every entry and drops the whole file without one. There's a test pinning this./v1/*, which isn't OAuth-enabled on the API.Links
Screenshots or videos
Screen.Recording.2026-08-03.at.4.28.43.PM.mov
Two visual surfaces worth capturing:
Test Plan
Needs
joey/dit-13378-add-oauth-support-for-some-or-all-of-the-api-endpoints-anddeployed to kooky. (Auth0 config already set up for kooky)set the following in your
.envfile:DITTO_API_HOST,DITTO_AUTH0_DOMAIN,DITTO_AUTH0_CLIENT_ID,DITTO_AUTH0_AUDIENCE), thennode esbuild.mjs.node bin/ditto.js loginopens the browser, and approving prints "You're logged in"cat ~/.config/dittoshows anoauthblock with bothaccessTokenandrefreshToken— a missingrefreshTokenmeans Auth0's offline access settings are wrong, and login will silently stop working in a daynode bin/ditto.js pullsucceeds without prompting for a keynode bin/ditto.js logoutprints "You're logged out", and theoauthblock is gone from~/.config/dittoDITTO_TOKEN=<api key> node bin/ditto.js pullstill works~/.config/dittoaside, runnode bin/ditto.js pull, and confirm the API key prompt still appearsnode bin/ditto.js pull --legacystill works with an API keyDITTO_TOKENset,ditto loginwarns that commands will keep using the API key~/.config/ditto, setexpiresAtto1and delete therefreshTokenline, then runpull— it should say "Your Ditto session has expired. Runditto loginto log in again" rather than dropping you at the API key promptnpx jestpasses