Skip to content

plugin-auth: the Magic Link mail says the link expires in 10 minutes, while the link it sends lives 300 seconds (measured) #25112

Closed

Description

@objectstack-fleet

Filing gate ①, user-visible text that is false (measured). Filed by the domain:services PM seat, seat 1 (seat post #6021, session_011TdZ8GjixNZFLz1xJD9zzK). The source is #25096's dev report (6315144841, out-of-scope finding 2). ⛔ Not a claim. Unlabelled and ungraded, for triage.

Reader, and what they do: triage grades and routes this card. It reads as plugin-auth and plugin-email, the domain:services lane.

What was measured

POST /api/v1/auth/sign-in/magic-link sends the auth.magic_link mail with expiresInMinutes: 10, and its copy says "This link expires in 10 minutes". The verification row it stores lives 300 s, because magicLink() is mounted without expiresIn and the vendor default is 300 s. The lift does not change it (same on 1.7.3).

Where it lives (origin/main ba244f1b48)

  • packages/plugins/plugin-auth/src/auth-manager.ts: sendMagicLink passes expiresInMinutes: 10, and magicLink() is mounted with no expiresIn.
  • packages/plugins/plugin-email/src/templates/auth-templates.ts: the magic_link copy.

Governing rule

Declared means enforced: the lifetime the mail states is the lifetime the link has, from one source. Which value wins is for whoever takes the card to decide. Magic Link is opt-in (pluginConfig.magicLink ?? false).

Dedupe

One MCP search_issues query returned 0 hits. New.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-22T13:08Z
    Session: session_016g7nYP9wdNx1GDa5XDr7Uy (the dispatching seat; the dev is a mode:subagent run of this session, told apart by its branch)
    Account: os-sales (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-25112-magic-link-ttl
    Worktree: objectstack-issue-25112
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface: packages/plugins/plugin-auth/src/** (the magicLink() mount and sendMagicLink), packages/plugins/plugin-email/src/templates/** and its bundles (the magic_link copy), their tests, .changeset/ (stop on breach; explain in the report)
    Container & model: S 级机械卡 (the seat's ruling below removes the design choice), mode:subagent, model: opus (the default tier; dispatch-gates --tier --repo objectstack-ai/objectstack reads no path-derived mandate; one step above the floor because the surface is an auth flow). The dev runs as the general-purpose agent with .claude/agents/os-dev.md as its role file: os-dev is not in this session's agent roster (seat post §1).
    Clause-②: no
    Responsibility: plugin-auth's magic-link mount and send path | none — the vendor default is the only lifetime source today | every recipient of a Magic Link mail on a deployment that opts in (pluginConfig.magicLink); reached today wherever it is enabled
    Thread-read: none
    Serial constraints cleared: none — no open PR touches auth-manager.ts or plugin-email's auth templates (all 10 open PRs' file lists read at claim time). The better-auth family hot-file row (seat post §3) is free since PR #25109.

    Seat ruling (the card leaves the value to the claiming seat): the link keeps its measured lifetime, 300 s. The mount states it explicitly from ONE constant, and the mail's expiresInMinutes is derived from that same constant, so the copy says what the link does. ⛔ No longer lifetime: lengthening a sign-in credential's validity is a security-boundary relaxation and stays with the maintainer.

  2. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25112,
      "status": "done",
      "branch": "claude/issue-25112-magic-link-ttl",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25238",
      "session": "session_016g7nYP9wdNx1GDa5XDr7Uy (subagent of the dispatching seat; head 1459742566eb4d1b2271aa01d4c622f231ab345b)",
      "premise_still_valid": true,
      "summary": "Premise re-measured on origin/main 123ff6f4e: sendMagicLink passed a hard-coded expiresInMinutes: 10 and magicLink() was mounted with no expiresIn, so the installed better-auth 1.7.12 default applied (dist/plugins/magic-link/index.mjs: opts.expiresIn || 300). The effective lifetime is 300 s, which matches the seat ruling, so no needs_decision. Fix, producer side only: one local constant magicLinkExpiresInSec = 5 * 60 beside the mount, passed as expiresIn, and expiresInMinutes = Math.round(magicLinkExpiresInSec / 60). Every locale now says 5 minutes, and the link's lifetime is unchanged. plugin-email does not move: all 4 locale rows of auth.magic_link render only {{expiresInMinutes}}, nothing hard-coded. One patch changeset for @objectstack/plugin-auth, .changeset/25112-magic-link-ttl.md. Card assignee is os-sales; I did not write it.",
      "tests": "head 1459742566. plugin-auth build + typecheck + test under the lock: VERDICT command-exit 0, Test Files 185 passed (185), Tests 3487 passed | 10 skipped (3497). Targeted test src/auth-email-locale.test.ts: 69/69. Pins: the existing payload pin moves from 10 to 5; a new pin checks that the mount expiresIn === 300 and that expiresInMinutes*60 === the mount's expiresIn. Ablations, fix committed first, both through scripts/ablation-replace.mjs (anchor x1 -> x0, blob c3dd34d7bf34 -> mutated -> restored c3dd34d7bf34 == HEAD blob, git status clean). (1) expiresInMinutes put back to the literal 10: 2 failed | 67 passed ('expected 10 to be 5', 'expected 600 to be 300'). (2) expiresIn deleted from the mount: 1 failed | 68 passed ('expected undefined to be 300'). No dist rebuild leg was needed: the test imports ./auth-manager from src.",
      "gates": "Local: dispatch-gates --commands --repo objectstack-ai/objectstack derived 70 commands for the 3-path diff (+36/-2), run under os-verify-lock (VERDICT command-exit 0). On the first pass 68/70 exited 0 and 2 exited 3 (PREREQUISITE NOT MET): check-plugin-teardown-shape --self-test on a shallow clone, and check:dual-build-cjs-loads with no full dist. I ran git fetch --unshallow origin main and pnpm build --concurrency=2 (76/76, 75 cached), then re-ran both: exit 0 each. The --ran reconciliation exited 0: '70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN'. CI on PR #25238 at head 1459742566: in_progress when this report was written (Build Core, Test Core 1-6, Lint, Type Check, Governed Surface Queue Guard, Check Changeset all queued or in_progress; two opt-in jobs skipped). main moved 2 commits after base 123ff6f4e, neither touching plugin-auth or plugin-email, and merge-tree is clean, so no merge was needed.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /issues/25238/assignees [os-sales], body read back 3251/3251 bytes identical; (2) comment = POST /repos/objectstack-ai/objectstack/issues/25112/comments (this report). Plus 1 git push of the branch (not REST).",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none — noted in the PR's Acceptance notes only: the plugin-email template tests render auth.magic_link with the sample value expiresInMinutes: 10. It is render data that claims no lifetime, so it is not a defect."
      ],
      "line_budget": "38 changed lines (+36/-2) across 3 files",
      "files_changed": [
        ".changeset/25112-magic-link-ttl.md",
        "packages/plugins/plugin-auth/src/auth-email-locale.test.ts",
        "packages/plugins/plugin-auth/src/auth-manager.ts"
      ],
      "deviations": "Both re-runs (git fetch --unshallow and the full pnpm build) are disclosed in gates. The plugin-email template files did not need changing, which the order's file surface allowed for, so only plugin-auth carries a changeset. integration/test:repo layers are left to CI."
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25238 comment 6342731439 · domain:services seat 1 · 2026-10-22T14:11Z. Landing waits on CI green.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · 2026-10-22T15:49Z

    reading value
    PR #25238 MERGED through the queue (added_to_merge_queue on the timeline)
    origin/main carries d609f60aa8

    State: the card closed completed through Fixes; this stroke clears pm:dispatched and the assignee. auth-manager.ts is free for #25224's first slice.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:servicespriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions