feat(signer): stop reading account.prvkey in merchant provision/rotate paths (#5)
Pre-cascade prerequisite for aiolabs/lnbits#17 (signer abstraction phase 1), which lands an m002 startup job that fail-closed NULLs the legacy `accounts.prvkey` column. This commit migrates the two sites in `views_api.py` that read `account.prvkey` so they no longer silently undo m002, and fail-closed cleanly when prvkey is missing. Scope intentionally narrow — this is the prvkey-elimination subset of aiolabs/nostrmarket#5. The full phase A (envelope-encrypt `merchants.private_key` → `signer_blob`) and phase B (route `Merchant.sign_hash` through core's `NostrSigner`) work remains tracked under that issue. ## What changed ### views_api.py — `_auto_create_merchant` Was: lazy fallback that, if `account.prvkey` was missing, generated a fresh keypair and wrote it back into the account (lines 112-118). After m002 NULLs `accounts.prvkey`, this regenerate-and-write-back path would silently undo the migration AND change the user's Nostr pubkey out from under them. Now: no longer touches the account. Asserts `account.prvkey` is present (matching the existing pubkey assertion) with a clear fail-closed message pointing at aiolabs/nostrmarket#5 for the phase A/B fix. For accounts that still carry a plaintext prvkey (pre-m002, FakeWallet local dev, etc.) the auto-provision path continues to work unchanged. For migrated accounts, the assertion fires fast with an actionable error. Removed the regenerate block entirely. Dropped now-unused imports: `update_account`, `generate_keypair`. ### views_api.py — `api_migrate_merchant_keys` Was: same `account and account.pubkey and account.prvkey` assertion with the generic message "Account has no Nostr keypair". Now: assertion updated with the same bridge-state framing — points at aiolabs/nostrmarket#5 for the phase A/B fix. ## Acceptance - [x] regenerate-and-write-back block removed (would undo m002) - [x] `account.prvkey` references in views_api.py are assertions only (fail-closed guards, not data reads) - [x] unused imports dropped (`update_account`, `generate_keypair`) - [x] error messages reference aiolabs/nostrmarket#5 for the phase A/B fix path Manual smoke / version bump / tag / catalog entry deferred until the lnbits cascade lands AND phase A's schema migration ships; this commit alone doesn't change the on-disk merchants table. ## Out of scope (per aiolabs/nostrmarket#5) - Phase A: envelope-encrypting `merchants.private_key` column. - Phase B (full): refactoring `Merchant.sign_hash` / `helpers.sign_message_hash` through core's `NostrSigner`. - Phase C: NIP-46 bunker + NIP-26 delegation variants. - Re-enabling `_create_default_merchant` on the lnbits core side. ## Cross-references - aiolabs/nostrmarket#5 — issue this is a partial step toward - aiolabs/lnbits#17 — the cascading signer-abstraction PR whose m002 fail-closed NULLs `accounts.prvkey` - aiolabs/lnbits#21 — umbrella audit (5 affected extensions) - aiolabs/events#23 / aiolabs/tasks#3 / aiolabs/restaurant#11 — sister migrations already on signer-abstraction branches Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
50f87c9970
commit
9f116ff1f8
1 changed files with 28 additions and 13 deletions
41
views_api.py
41
views_api.py
|
|
@ -4,7 +4,7 @@ from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from lnbits.core.crud import get_account, update_account
|
from lnbits.core.crud import get_account
|
||||||
from lnbits.core.services import websocket_updater
|
from lnbits.core.services import websocket_updater
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
WalletTypeInfo,
|
WalletTypeInfo,
|
||||||
|
|
@ -12,7 +12,6 @@ from lnbits.decorators import (
|
||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.utils.exchange_rates import currencies
|
from lnbits.utils.exchange_rates import currencies
|
||||||
from lnbits.utils.nostr import generate_keypair
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from . import nostr_client, nostrmarket_ext
|
from . import nostr_client, nostrmarket_ext
|
||||||
|
|
@ -101,21 +100,32 @@ async def _auto_create_merchant(
|
||||||
) -> Merchant:
|
) -> Merchant:
|
||||||
"""
|
"""
|
||||||
Lazy fallback: provision a merchant from the user's account keypair when
|
Lazy fallback: provision a merchant from the user's account keypair when
|
||||||
the LNbits-side eager provisioning didn't run (e.g., older accounts, or
|
the LNbits-side eager provisioning didn't run.
|
||||||
upstream LNbits without our signup hook).
|
|
||||||
|
|
||||||
Delegates to services.provision_merchant — the canonical implementation.
|
Delegates to services.provision_merchant — the canonical implementation.
|
||||||
|
|
||||||
|
Pre-cascade bridge state (see aiolabs/nostrmarket#5):
|
||||||
|
After aiolabs/lnbits#17 m002 lands, `accounts.prvkey` is fail-closed
|
||||||
|
NULL'd for migrated accounts (the cleartext nsec lives encrypted in
|
||||||
|
`signer_config`, owned by the core signer abstraction). Auto-provision
|
||||||
|
cannot extract that cleartext to copy into `merchants.private_key`,
|
||||||
|
so this path fails-closed when prvkey is missing. The proper fix is
|
||||||
|
phase A (envelope-encrypt `merchants.private_key` → `signer_blob`)
|
||||||
|
followed by phase B (route `Merchant.sign_hash` through core's
|
||||||
|
`NostrSigner`) per aiolabs/nostrmarket#5. Until then, migrated
|
||||||
|
accounts must explicitly provision a merchant through the future
|
||||||
|
phase-A-aware flow.
|
||||||
|
|
||||||
|
The previous regenerate-and-write-back block (generated a fresh
|
||||||
|
keypair and stored it into the account) was removed because it
|
||||||
|
would silently undo m002's NULL'ing.
|
||||||
"""
|
"""
|
||||||
account = await get_account(wallet.wallet.user)
|
account = await get_account(wallet.wallet.user)
|
||||||
assert account, "User account not found"
|
assert account, "User account not found"
|
||||||
|
assert account.pubkey and account.prvkey, (
|
||||||
# In our fork, accounts always have keypairs. Generate as fallback only
|
"Account has no plaintext Nostr keypair available for merchant "
|
||||||
# if somehow missing (e.g., upstream LNbits where this isn't auto-set).
|
"provisioning (see aiolabs/nostrmarket#5 for the phase A/B fix)"
|
||||||
if not account.pubkey or not account.prvkey:
|
)
|
||||||
private_key, public_key = generate_keypair()
|
|
||||||
account.pubkey = public_key
|
|
||||||
account.prvkey = private_key
|
|
||||||
await update_account(account)
|
|
||||||
|
|
||||||
merchant = await provision_merchant(
|
merchant = await provision_merchant(
|
||||||
user_id=wallet.wallet.user,
|
user_id=wallet.wallet.user,
|
||||||
|
|
@ -245,8 +255,13 @@ async def api_migrate_merchant_keys(
|
||||||
assert merchant.id == merchant_id, "Wrong merchant ID"
|
assert merchant.id == merchant_id, "Wrong merchant ID"
|
||||||
|
|
||||||
account = await get_account(wallet.wallet.user)
|
account = await get_account(wallet.wallet.user)
|
||||||
|
# account.prvkey is fail-closed NULL'd by aiolabs/lnbits#17 m002
|
||||||
|
# for migrated accounts. Rotation cannot copy a cleartext nsec
|
||||||
|
# into merchants.private_key until phase A lands — see
|
||||||
|
# aiolabs/nostrmarket#5 for the migration plan.
|
||||||
assert account and account.pubkey and account.prvkey, (
|
assert account and account.pubkey and account.prvkey, (
|
||||||
"Account has no Nostr keypair"
|
"Account has no plaintext Nostr keypair available for key "
|
||||||
|
"rotation (see aiolabs/nostrmarket#5 for the phase A/B fix)"
|
||||||
)
|
)
|
||||||
|
|
||||||
if account.pubkey == merchant.public_key:
|
if account.pubkey == merchant.public_key:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue