Skip to content

No admin action has an author #497

Open

Description

@Miracle656

Background

The entire admin surface is guarded by one shared secret:

const adminToken = process.env.ADMIN_TOKEN

Everyone who can mint an API key holds the same string. So there is no way to
answer "who minted this key", no way to revoke one person's access without
rotating the secret for everyone, and no way to tell an operator apart from
whoever else has seen the value.

The token is also in the environment of every running instance, which means it
is visible to anything that can read the process environment or a crash dump.

What to build

This needs a decision before code, so propose one in the PR:

  • Multiple named admin credentials — a set of tokens each with an
    identifier, so an action can be attributed and one can be revoked alone.
  • Or signed requests, where an admin holds a key pair and the server keeps
    only public keys, so the secret never sits in the server's environment at all.

Whichever you choose:

  • An authenticated admin request must carry an identity the server can name,
    and that identity is what L234 records.
  • Rotating or removing one admin must not invalidate the others.
  • A single ADMIN_TOKEN should keep working during migration, with a clear
    deprecation path — do not lock the operator out of their own deployment.

Acceptance criteria

  • An admin request resolves to a named identity
  • One admin's access can be removed without affecting others
  • Existing ADMIN_TOKEN configuration continues to work during migration
  • The migration path is documented
  • No admin secret is logged, echoed or returned

Drips Wave · Complexity: Advanced · 200 points


Repo: Divinity-Labs/Lens. Auth is src/api/auth.ts, key minting
src/api/admin.ts, usage reporting src/api/usage.ts, metering
src/x402/metering.ts, the ApiKey model in prisma/schema.prisma.

Run the tests: npm test. The tests are vitest, not jest. A single file
is npx vitest run src/__tests__/auth.test.ts.

Load test (prices) is already failing on main. Not your PR.

Do not weaken what is already right. Three things are correct today and
should survive every change here:

  • generateApiKey() is lens_ plus randomBytes(24) — 192 bits. Do not
    shorten it, and do not make it guessable or sequential.
  • hashApiKey() is an unsalted SHA-256. That is correct for a
    high-entropy random key: it must stay fast and deterministic so a lookup is
    one indexed query. Do not "improve" it into bcrypt or argon2 — those are for
    low-entropy human passwords, and here they would only make every request slow.
  • Only the hash is ever stored. The plaintext is returned once, at creation,
    and never again.

Admin routes carry config: { public: true }. That does not mean public —
it means "skip the API-key hook", because they enforce ADMIN_TOKEN instead.
Removing that flag locks the admin surface behind the keys it exists to mint.

Never elide an address, a key or a token in code, comments, tests or
documentation. Write it in full or not at all — and never write a real key
anywhere.



Required: Before submitting, join the contributor Telegram so your work can be tracked and counted toward the Stellar Wave: https://t.me/+fxHXq8f1SwlkZDBk

Activity

  1. Miracle656 commented on Oct 18, 2026

    @Miracle656
    CollaboratorAuthor

    ⏸️ Not funded in the current Wave — please do not start yet

    This issue is real and the work is wanted, but it is not currently part of the Drips Wave programme, so a PR against it right now would earn no points. I would rather say that plainly up front than have anyone find out after writing the code.

    The repo's Wave budget for this round is exhausted — 25,000 points across 271 labelled issues — and this batch was published just after it ran out. That is my error in pacing, not a reflection on the issue.

    What happens next: it will be added to the Wave when the next round opens, at the points value in its label. The description, the acceptance criteria and the code references all stay as they are, so nothing here is wasted.

    If you want something to work on today, pick an issue that is already in the Wave — the Stellar Wave label plus an unassigned state is the signal, and Drips will let you apply for it. If Drips will not let you apply for an issue, that is the budget, not you.

    If you have already started on this one, say so here and I will make sure you are first in line for it when the next round opens.

  2. Daveeee-tech commented on Oct 18, 2026

    @Daveeee-tech

    @Daveeee-tech has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    Hi maintainer pls assign

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @Daveeee-tech to this issue.

  3. aliyuHabibu commented on Oct 18, 2026

    @aliyuHabibu
    Contributor

    @aliyuHabibu has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    Hi maintainer, i reviewed the issue and this is exactly the type of work i enjoy, i can deliver a clean, well-tested PR promptly, please assign it to me.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @aliyuHabibu to this issue.

  4. mysterio123865 commented on Oct 18, 2026

    @mysterio123865

    @mysterio123865 has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    Application: #497

    Decision: multiple named admin credentials. Signed requests would remove the secret from the server entirely, but they need client tooling, a canonical request format, and replay protection. That is a lot of new surface for this API. Named credentials meet every acceptance criterion with primitives the repo already gets right. Signed requests could be a follow-up.

    Approach

    • Add an AdminCredential model (id, name, tokenHash, createdAt, revokedAt, lastUsedAt).
    • Generate tokens like generateApiKey() (randomBytes(24)) and store only the existing SHA-256 hash. The plaintext is returned once at creation. I'm not touching the key generator or the hash.
    • In auth.ts, hash the bearer token and look it up among non-revoked credentials. On a hit, attach request.admin = { id, name }, which is the identity L234 records.
    • If there is no match, fall back to ADMIN_TOKEN with timingSafeEqual. It resolves to legacy-admin and logs a deprecation warning that never includes the token.
    • In admin.ts, add create, list (no hashes), and revoke-by-id endpoints. Revoking one admin leaves the others valid.
    • Keep config: { public: true } on the admin routes.

    Migration: ADMIN_TOKEN keeps working. The operator creates a named credential with it, then unsets it. I'll document this in docs/admin-auth-migration.md, including a removal target for the legacy path.

    Tests (vitest):

    • A named token resolves to its identity.
    • Revoking one admin leaves the others working.
    • The legacy token still works.
    • Revoked and unknown tokens return 401.
    • No secret appears in logs or responses.

    I've joined the contributor Telegram and am ready to start once assigned.

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @mysterio123865 to this issue.

  5. drips-wave commented on Oct 18, 2026

    @drips-wave

    Congratulations, @mysterio123865! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.

    🧑‍💻 @mysterio123865: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions