| """Provisioning — turn a freshly-logged-in Google user into a billed account. |
| |
| One entry point: provision(user_id, email, username). |
| 1. derive a unique deposit address from the wallet xpub |
| 2. mint a personal API key (dr_u_<hex>) |
| 3. create the per-user gist (5 files, seeded with the free grant) |
| 4. register the user in the master registry |
| 5. return the full record (caller caches the key->user map) |
| |
| Idempotent: if the user is already registered, returns the existing record. |
| """ |
| from __future__ import annotations |
|
|
| import hashlib |
| from typing import Optional |
|
|
| from .. import wallet |
| from . import registry, user_store |
|
|
| FREE_CREDITS = 500 |
|
|
|
|
| def _build_key(deposit_address: str) -> tuple[str, str, str]: |
| """Build a user's API key FROM their deposit address. |
| |
| The key is deterministic: it's `sk-` + the checksummed ETH deposit address. |
| Because it's derived from the (always-visible) deposit address, it can be |
| recovered at any time — there's no "lost key" problem. Returns |
| (full_key, display_prefix, sha256_hex_hash). |
| """ |
| full = f"sk-{deposit_address}" |
| a = deposit_address.lower() |
| prefix = f"sk-{a[:6]}…{a[-4:]}" |
| digest = hashlib.sha256(full.encode()).hexdigest() |
| return full, prefix, digest |
|
|
|
|
| async def provision(user_id: str, email: str, username: str) -> Optional[dict]: |
| """Provision a new user OR return their existing record. Returns None if disabled.""" |
| |
| existing = registry.find_user(user_id) |
| if existing: |
| return existing |
| |
| if not wallet.is_configured(): |
| return None |
| _, reg = await registry.get_or_create() |
| index = reg["wallet"]["next_index"] |
| deposit = wallet.derive_deposit_address(index) |
| full_key, prefix, digest = _build_key(deposit) |
| identity = { |
| "user_id": user_id, |
| "email": email, |
| "username": username, |
| "deposit_address": deposit, |
| "derivation_index": index, |
| "api_keys": [ |
| {"key_id": "k1", "prefix": prefix, "hash": digest, "created": registry._now(), "active": True} |
| ], |
| } |
| gist_id = await user_store.provision_gist(identity, free_credits=FREE_CREDITS) |
| record = { |
| "user_id": user_id, |
| "email": email, |
| "username": username, |
| "gist_id": gist_id, |
| "deposit_address": deposit, |
| "derivation_index": index, |
| "status": "active", |
| "created_at": registry._now(), |
| "api_key_prefix": prefix, |
| "api_key_hash": digest, |
| "_plaintext_key": full_key, |
| } |
| await registry.register_user(record) |
| return record |
|
|