feat(accounts): manage Constructive accounts and API keys from the vault - #21
Merged
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dcrypt becomes an account manager for real Constructive accounts: sign up / sign in through
@constructive-io/sdk's generated auth client, and mint and revoke API keys — with every session token and key secret living in the encrypted vault rather than anaccount.jsonguarded by whatever the OS happens to offer. Slices 1+2 of https://github.com/constructive-io/constructive-planning/issues/1520; the harness credential provider, biometric unlock and passkeys follow.@decryption/accountsis the whole seam.AccountManagertakes aVaultand an injectable client factory, so the tests exercise the real vault against a fake server and no test needs credentials:Two details that matter:
createApiKeyreturnsapiKeyexactly once from the server, and the manager writes it straight to a concealed field; the returnedApiKeyRecorddeliberately carries onlykeyId/name/expiresAt. Nothing logs it, nothing puts it in a.env.accessToken()returnsnullpast its expiry rather than sending a dead bearer, andsignOutdeletes the token field after the server call, so a locked vault means no credentials at all.Accounts and keys are new item kinds, so they inherit search, audit, backup and rebuild for free:
That is a new pgpm change (
schemas/dcrypt_vault/account_types) with a real revert — Postgres can't drop an enum value, so the revert rebuilds both types without them and lets the column cast fail if a row still uses one. A vault file is deployed once and thereafter restored from its snapshot, soVault.openalso runsensureSchema()(threeADD VALUE IF NOT EXISTSstatements mirroring that deploy file), which is what carries an existing vault forward without a rebuild.CLI (
dcrypt account list|signup|signin|signout|forget,dcrypt account key list|create|reveal|revoke) follows the existing conventions: the account password is refused in argv exactly like the master passphrase (--password-file,--password-stdin,DCRYPT_ACCOUNT_PASSWORD), the endpoint comes from--endpointorDCRYPT_AUTH_ENDPOINT, and normal output is metadata only — the secret appears solely from an explicitkey reveal. Because this is the first command in dcrypt that opens a socket, the top-level usage and the README no longer claim otherwise.The desktop gets an Accounts tab (Blocks UI) for the same operations, with copy-to-clipboard going through the existing 30s-clearing helper.
Unrelated one-liner, included because it made
pnpm typecheckinapps/desktopfail outright:appstash()returns anAppStashResult, not a path, soappDataPath()now returns.root— which also meanseraseAll()'sfs.rm(appDataPath())gets a string instead of an object.Tests: 13 in
@decryption/accounts(fake client + real vault, including lock/reopen and endpoint isolation) and 6 CLI tests covering the argv refusal, missing endpoint and not-found paths.Link to Devin session: https://app.devin.ai/sessions/04636534e07048089ffb6b78142e12cd
Requested by: @pyramation