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

vercelBot commented Jul 19, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specBuildingBuildingPreview, CommentJul 19, 2026 5:08pm

Request Review

@github-actionsgithub-actionsBot 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 mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant

@os-zhuang