<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinos-server.git/routes/users.ts, branch master</title>
<subtitle>coinos server — legacy coinos.io backend (Lightning, on-chain, Liquid)</subtitle>
<id>https://git.coinos.io/coinos-server.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinos-server.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/'/>
<updated>2026-10-13T23:23:37Z</updated>
<entry>
<title>users: let a password unlock the encrypted nostr key for any session</title>
<updated>2026-10-13T23:23:37Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-13T23:23:37Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=12349761c03e769e1d54e878746a10d0a464b38d'/>
<id>urn:sha1:12349761c03e769e1d54e878746a10d0a464b38d</id>
<content type='text'>
/me only reports hasnsec now, so sessions that didn't start with a
password login had no way to reveal or sign with their key. POST /nsec
returns it after re-checking the password, under the login per-IP
failure limit.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01UjHzoBmW1iPpEe5qC2LDYB
</content>
</entry>
<entry>
<title>login: drop the unused per-username failure counter</title>
<updated>2026-10-13T14:52:33Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-13T14:52:33Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=d1a12bc0585aa583263362304218803ebf0afa9a'/>
<id>urn:sha1:d1a12bc0585aa583263362304218803ebf0afa9a</id>
<content type='text'>
The counter was incremented on every failed login and read but never
acted on; the per-IP limits do the throttling. Its keys never expired
and had grown to most of the keyspace.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01UjHzoBmW1iPpEe5qC2LDYB
</content>
</entry>
<entry>
<title>users: keep the encrypted nostr key and 2FA seed away from bearer tokens</title>
<updated>2026-10-08T19:15:15Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-08T19:15:15Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=5df9bc39ac48fdf1b19b00b9b457f9ebc1ee157c'/>
<id>urn:sha1:5df9bc39ac48fdf1b19b00b9b457f9ebc1ee157c</id>
<content type='text'>
/me and user emits return a hasnsec flag instead of the encrypted key, which
is now only sent with a password login or registration. /otpsecret refuses
once 2FA is enabled, and disabling 2FA rotates the seed.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>me: stop reporting archived balance as locked for frozen accounts</title>
<updated>2026-10-08T19:03:38Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-08T19:03:38Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=fbf99d4d212baeae1750cec12244d0f904dfe669'/>
<id>urn:sha1:fbf99d4d212baeae1750cec12244d0f904dfe669</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Evicted accounts can't receive or log in; limit errors say what's happening</title>
<updated>2026-10-04T20:38:24Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-04T20:38:24Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=41018c30a2e03f5ab05be64d419542be0cff7943'/>
<id>urn:sha1:41018c30a2e03f5ab05be64d419542be0cff7943</id>
<content type='text'>
An evicted account no longer gets lightning-address invoices (stops a
scam page's address from minting new ones) or a login token. A send
over the server limit now says withdrawals are temporarily limited
instead of a generic "Problem sending payment" that read as a frozen
account. (Already live since the 2026-10-01 restart.)

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_011bvzKaZosXGHkigcGcSzAR
</content>
</entry>
<entry>
<title>users: reserve service names on registration and rename</title>
<updated>2026-09-22T02:23:57Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-20T05:06:16Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=38f3baf94d08b0e3ec40e6bacc4e330459e2a4d3'/>
<id>urn:sha1:38f3baf94d08b0e3ec40e6bacc4e330459e2a4d3</id>
<content type='text'>
A username is also a lightning address, so a name that reads as the service
collects payments meant for it. "mint" was registered days after the ecash
redesign freed it and received payments addressed to mint@coinos.io; the
reserved list held only "ecash", and was duplicated in two places.

One shared list now, checked on both paths, covering the service-sounding
names an account should never be able to take.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>security: drop the admin endpoint surface and land pending hardening</title>
<updated>2026-09-14T03:44:48Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T03:44:48Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=d8a361bdcfaa8cc5b39d9675e74b0e1d2e42be2c'/>
<id>urn:sha1:d8a361bdcfaa8cc5b39d9675e74b0e1d2e42be2c</id>
<content type='text'>
Removes the admin-gated endpoints and the account flags they set, plus a
batch of hardening that had accumulated uncommitted in the tree. These were
deployed and verified together, so they land as one change.

admin surface:
  - drop POST /hidepay, POST /unlimit and GET /users, their handlers, and
    the now-unused admin preValidation export
  - `unlimited` was only ever written, never read; `hidepay` only appeared
    in output projections. Both flags go, along with their whitelist,
    public-fields and type entries.

request log:
  - redactBody now recurses. It only masked top-level keys, so a body that
    nests credentials one level down wrote them to the log in plaintext,
    and pin fields were not on the list at all.

payments:
  - sendOnchain/build gate `aid` through requireAccount, so a caller can
    only spend from an account it owns
  - sendOnchain signs only a tx we composed, matched against a short-lived
    per-account build receipt
  - raise xpay retry_for to 60s; route-finding preferred a drained zero-fee
    hop and burned the whole budget on it. check()'s reversal window keeps
    its 2x margin.

ecash / invoices / webhooks:
  - resolve a token's mint through the IP-pinning fetcher instead of plain
    fetch, closing an unauthenticated SSRF path into the internal network
  - reject non-positive amounts before the balance write
  - /sign requires the caller to own the address, rather than signing under
    any address the hot wallet controls
  - webhook delivery no longer disables TLS verification; the body carries
    the merchant's shared secret

registration:
  - refuse a username the v3 registrar holds unless the caller's own nostr
    key is the holder (lib/names.ts)

liquid:
  - honour the deposit kill switch at invoice generation and at credit

elements image:
  - pin 23.3.4 and verify the release tarball checksum

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_0112TGi9GGNWqji1XQ5jZRzc
</content>
</entry>
<entry>
<title>flash: build each config image in its own temp dir</title>
<updated>2026-09-05T06:37:10Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-05T06:37:10Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=54419060c1255f06d3c119b3f9c5334e1fa8bc07'/>
<id>urn:sha1:54419060c1255f06d3c119b3f9c5334e1fa8bc07</id>
<content type='text'>
The handler wrote every request's config.txt and littlefs.img to the same
paths, so two overlapping requests could hand one user the other's wifi
key and API token. Use a per-request mkdtemp dir, read the image into
memory, and remove the dir when done.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01VMi2Y1oKPZ3TeeFmLej47y
</content>
</entry>
<entry>
<title>flash: create printer dir and return errors instead of unhandled 500</title>
<updated>2026-09-05T02:03:14Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-05T02:03:03Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=671a114060ceaa7922affa00f90857bf99317d0a'/>
<id>urn:sha1:671a114060ceaa7922affa00f90857bf99317d0a</id>
<content type='text'>
The web flasher config endpoint wrote ./printer/config.txt without
creating the directory, so on a fresh deploy it threw ENOENT and the
JSON error body was flashed to devices as the LittleFS partition.
Create the directory, validate inputs, and route errors through bail.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_014gVYGi29FjTeLdNTiutkZx
</content>
</entry>
<entry>
<title>login: fail-open recaptcha verify on infrastructure errors</title>
<updated>2026-08-26T05:30:35Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-26T05:30:35Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=be1f54473273a97d5201948433a53216da26398f'/>
<id>urn:sha1:be1f54473273a97d5201948433a53216da26398f</id>
<content type='text'>
verifyRecaptcha failed closed on ANY error from the Google siteverify call, so
a transient coinos&lt;-&gt;Google network blip (or a Google slowdown) turned every
non-admin login into a "failed captcha" 401 until connectivity recovered — a
self-inflicted login outage from an external dependency.

Distinguish a real verdict from an infrastructure failure: fail CLOSED only on
a genuine success:false from Google; on a thrown error (network, timeout, 5xx)
fail OPEN and log it. Reaching Google is not part of authentication — the
password and the per-IP login rate limits still gate the request, and captcha
is only bot-mitigation, so allowing a login through when the verifier is
unreachable is the safer failure mode. Also bound the call with a 6s timeout so
a slow verifier can't hang the login.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XiwgmQKwKnRxUmVDd4xSf7
</content>
</entry>
</feed>
