Skip to content

feat(auth): bulk import defaults to auto — invite per row, temporary only for undeliverable rows (#3236)#3307

Merged
os-zhuang merged 1 commit into
mainfrom
feat/import-users-auto-policy
Jul 19, 2026
Merged

feat(auth): bulk import defaults to auto — invite per row, temporary only for undeliverable rows (#3236)#3307
os-zhuang merged 1 commit into
mainfrom
feat/import-users-auto-policy

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3236.

背景 / 前提更正

Issue #3236 的前提是「默认策略是 temporary」,但实际默认早已是 none(#2820 改的:导入只供给身份、不发凭证、不分发任何秘密)。所以「危险默认」这一层其实已经不存在了。

Issue 里仍然成立的价值:当管理员确实想在导入时发凭证时,temporary 是个「整批一刀切」的锤子——一批里既有可投递又有不可投递的行时,invite 会把不可投递的行整个 fail 掉,temporary 又会给所有行(包括本可安全 invite 的行)都发临时密码。#3236 的真正提案是逐行决策

改动

新增第四种 passwordPolicy —— auto,并设为新默认(原为 none)。auto 逐行决定:

  • 可投递的行(真邮箱 + 已接邮件服务,或手机号 + 已接 SMS 邀请)→ 走 invite(set-your-password 邮件,或纯手机号行的邀请短信),不分发任何长期共享秘密;
  • 无可投递渠道的行(placeholder 邮箱 / 纯手机号无 SMS / 邮箱行但无邮件服务)→ 回退 temporary,临时密码在响应里返回一次并盖 must_change_password

这把临时密码的爆炸半径从「整批」收缩到「真没渠道的少数行」;而且和 invite 不同,auto 永不因缺基建而拒绝请求(无渠道的行只是降级)。逐行结果挂在 rows[].delivery(email / sms / temporary),整批分布挂在 summary.delivery,并写入运行审计。

三个原有策略行为完全不变,仍可显式选择:invite(逐行 fail 不可投递行,永不降级)/ temporary(整批临时密码)/ none(仅身份)。

重构

写入路径统一成:预校验时算好逐行 RowPlan(按身份 key,因为 createData 拿到的是 coerce 过的行副本,不能靠对象引用),写后用一个统一遍历(靠 invite / temporary 两个 map 驱动,不再按全局 policy 分支)。于是 auto 的混合批次和三个固定策略共用一条代码路径。

⚠️ 行为变更

省略 passwordPolicy 的调用方,现在拿到的是 auto(默认会主动外发邀请 / 返回临时密码),不再是 none。要恢复「只建身份」的老行为需显式传 passwordPolicy: 'none'已显式传策略的调用方不受影响,响应是严格超集(新增 delivery 字段)。按 @objectstack/plugin-authminor 发。

未纳入(单独跟进)

Issue 标注为「先行止血项」的那个 bug(temporarymust_change_password 失败时只 warn、仍以 created+临时密码 静默返回)本 PR 未修——Issue 明确说单独修。注意:auto 成为默认后,该静默失败现在落在了默认路径上,值得尽快单独补一个 PR。

测试

  • 新增 5 个 auto 测试(逐行分流 / SMS 邀请 / 无渠道全降级 / 邀请失败不回滚 / dryRun 不投递),并更新默认策略测试。
  • admin-import-users.test.ts 26 项全绿;plugin-auth 全量 465 项全绿;DTS 类型检查过;eslint 干净。

🤖 Generated with Claude Code

…y only for undeliverable rows (#3236)

The identity bulk-import endpoint (`POST /api/v1/auth/admin/import-users`)
gains a fourth `passwordPolicy`, `auto`, and it is now the default (was
`none`). `auto` decides PER ROW instead of forcing one policy on the whole
batch:

  - a row with a deliverable channel (real email + wired email service, or
    phone + wired SMS-invite) is INVITED — no shared secret leaves the server;
  - a row with no deliverable channel (placeholder email, phone-only without
    SMS, or an email row when no email service is wired) falls back to a
    temporary password, returned once with `must_change_password` stamped.

This shrinks the temporary-password blast radius from "the whole batch" to
"only the rows that genuinely can't be reached", and — unlike `invite` —
`auto` never rejects the request for missing infrastructure (undeliverable
rows just degrade). The per-row outcome is surfaced on `rows[].delivery`
(email/sms/temporary) with a batch breakdown on `summary.delivery` and in the
run audit.

The write path is refactored to a single per-row plan (resolved in
pre-validation, keyed by identity so it survives the coerced-row copy handed
to createData) and one unified post-write pass driven by the invite/temporary
maps rather than a global policy branch — so the interleaved `auto` batch and
the three fixed policies share one code path. `invite`/`temporary`/`none`
behave exactly as before.

Behavior change: callers that OMIT `passwordPolicy` now get `auto` (proactive
invitations / temporary fallback) instead of `none` (identity only). Pass
`passwordPolicy: 'none'` explicitly for the old identity-only behavior. Every
explicit-policy call is unaffected; the response is a strict superset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jul 19, 2026 5:08pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-auth.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-auth)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-auth)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/sso.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-auth)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-auth)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-auth)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 616e839 into main Jul 19, 2026
14 of 16 checks passed
@os-zhuang
os-zhuang deleted the feat/import-users-auto-policy branch July 19, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

批量导入用户:默认走 invite,temporary(临时密码)降级为不可投递行的兜底

1 participant