feat(auth): 身份导入默认不设密码(passwordPolicy: 'none') - #2820
Merged
Conversation
…edentials at import time Import provisions identity, not credentials. The new default 'none' policy creates credential-less accounts (better-auth optional-password create): no password material is generated, returned, or distributed. Users first sign in through a channel (phone OTP / magic link / reset link) and the Console's existing hasLocalPassword() → set-initial-password flow nudges them to set one afterwards. 'invite' also stops minting a throwaway password — better-auth's reset-password creates the credential record on first set (verified in 1.6.23 dist), so it is now 'none' + the invitation send. 'temporary' is unchanged as the no-infrastructure fallback. passwordPolicy was previously required (400 when omitted); omitted now means 'none'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016r6eiJzivw1CkwTDSGho1o
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 11, 2026 03:01
os-zhuang
pushed a commit
that referenced
this pull request
Jul 11, 2026
…Console wizard entry The detailed Bulk Import section already documents the none/invite/temporary policies, but the API-reference one-liner still said "invite or temporary" (the pre-#2820 default) and nothing pointed at the Console import wizard shipped in objectui (framework#2782). Adds none as the default to the endpoint reference and a short paragraph on the Users-list Import flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016r6eiJzivw1CkwTDSGho1o
os-zhuang
added a commit
that referenced
this pull request
Jul 11, 2026
The detailed Bulk Import section already documents the none/invite/temporary policies, but the API-reference one-liner still said "invite or temporary" (the pre-#2820 default) and nothing pointed at the Console import wizard shipped in objectui (framework#2782). Adds none as the default to the endpoint reference and a short paragraph on the Users-list Import flow. Claude-Session: https://claude.ai/code/session_016r6eiJzivw1CkwTDSGho1o Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang
pushed a commit
that referenced
this pull request
Jul 27, 2026
… and correct download filename (#3504) The approval inbox timeline showed a nameless "附件" chip that did nothing on click, and even the downloaded file was named after the opaque URL token. Two root causes, both in the framework: approvals — `sys_approval_action.attachments` (a `Field.file`) stores rich descriptors `{ id, name, url, mimeType, size }`, but `rowFromAction` mapped them with `.map(String)`, collapsing each to "[object Object]". `ApprovalActionRow. attachments` is now `ApprovalActionAttachment[]`; the descriptor carries name + url, so consumers label/open attachments without reading the system `sys_file`. storage — presigned downloads served `application/octet-stream` with no `Content-Disposition`. `getSignedUrl`/`getPresignedDownload` now take `PresignedDownloadOptions { filename, contentType, disposition }`; the REST download routes pass the `sys_file` name+mime; the local adapter carries them in the token and `_local/raw` emits an RFC 5987 Content-Disposition; S3 bakes the same into the signed URL. Default `inline` preserves in-browser preview. Verified end-to-end in app-showcase. Refs objectui #2820. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
pushed a commit
that referenced
this pull request
Jul 27, 2026
…wnload filename (#3504) (#3505) * fix(approvals+storage): surface decision attachments with name, open, and correct download filename (#3504) The approval inbox timeline showed a nameless "附件" chip that did nothing on click, and even the downloaded file was named after the opaque URL token. Two root causes, both in the framework: approvals — `sys_approval_action.attachments` (a `Field.file`) stores rich descriptors `{ id, name, url, mimeType, size }`, but `rowFromAction` mapped them with `.map(String)`, collapsing each to "[object Object]". `ApprovalActionRow. attachments` is now `ApprovalActionAttachment[]`; the descriptor carries name + url, so consumers label/open attachments without reading the system `sys_file`. storage — presigned downloads served `application/octet-stream` with no `Content-Disposition`. `getSignedUrl`/`getPresignedDownload` now take `PresignedDownloadOptions { filename, contentType, disposition }`; the REST download routes pass the `sys_file` name+mime; the local adapter carries them in the token and `_local/raw` emits an RFC 5987 Content-Disposition; S3 bakes the same into the signed URL. Default `inline` preserves in-browser preview. Verified end-to-end in app-showcase. Refs objectui #2820. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(spec): regenerate API surface snapshot for the two added interfaces Additive only (0 breaking): ApprovalActionAttachment + PresignedDownloadOptions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(approvals): correct the attachment read-shape rationale and share the id rule Follow-up on this PR's own change, after ADR-0104 D3 wave 2 landed on main. The fix in this PR is right; its stated cause was inverted. It says the `sys_approval_action.attachments` column "stores rich descriptors — a fileId is resolved to a full descriptor on write". The code says otherwise: approval-service.ts writes `input.attachments` verbatim (a fileId string[]), and there is no write-side descriptor resolution anywhere in the repo. What actually happens is that the column stores an opaque sys_file id — the stored form of every media field — and the ObjectQL read path expands it into `{ id, name, size, mimeType, url }` on the way out. That resolver was already in this PR's base commit, so the descriptors observed in listActions came from the read path, not from storage. Left as written, that inverted account would have been a false statement about storage sitting in the protocol contract, in the changeset, and in a regression test comment — and PR-5a has since made the stored form explicitly an id, so it would also have been contradicted by the schema. Corrected in all three places. Also names the three read forms the normalizer actually handles, rather than one: the expanded value (normal), a bare id (nothing to expand it into), and a legacy inline blob written before the cutover, whose keys are snake_case (`file_id`, `mime_type`). The last of those had no test; it has one now. The id-token test reuses `isFileIdToken` from @objectstack/spec/data — the platform's single arbiter of "opaque id or URL?" — so this normalizer and the engine's read resolver cannot drift apart on that question, and a non-id string is now surfaced as a url rather than mislabelled an id. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd * docs(storage): record the presigned-download options in the hand-written contract docs The drift check flagged these: both storage-service pages transcribe IStorageService by hand, and this PR adds an optional PresignedDownloadOptions argument to getSignedUrl / getPresignedDownload. Left alone they would describe a signature the code no longer has — the same declared-vs-actual gap the rest of this PR is about, one level up in the docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
设计讨论结论(#2766 后续):导入的职责是身份,不是凭据。验证码/魔法链接登录 + Console 已有的无密码检测(
hasLocalPassword()→/set-password引导,SSO 入职同款流程)意味着导入根本不需要生成密码。变更
passwordPolicy: 'none'并设为默认:建号时不传 password(better-auth 可选密码 → 无 credential 记录),零密码材料生成/返回/分发。用户经手机 OTP / 魔法链接 / 重置链接首登,Console 检测到无密码后引导set-initial-password。invite不再生成丢弃式密码:改为同样无凭据建号 + 发送邀请。依据:better-auth 1.6.23 的 reset-password 对无凭据账号会自动创建 credential 记录(api/routes/password.mjs已核实),语义完全等价且更干净。temporary不变:保留为"无邮件/短信基建部署"的显式兜底。passwordPolicy原为必填(缺失 400),现在缺失 =none。测试
tsc --noEmit干净;check-role-word通过。三种策略的最终定位
none(默认)invitetemporaryRef #2766, #2782(向导适配器将把
none作为默认选项)。🤖 Generated with Claude Code
https://claude.ai/code/session_016r6eiJzivw1CkwTDSGho1o
Generated by Claude Code