Closed
Conversation
Agent-Logs-Url: https://github.com/poly-workshop/identra/sessions/9b24b8cd-ff6e-4728-b793-023ebc129420 Co-authored-by: slhmy <31381093+slhmy@users.noreply.github.com>
Agent-Logs-Url: https://github.com/poly-workshop/identra/sessions/9b24b8cd-ff6e-4728-b793-023ebc129420 Co-authored-by: slhmy <31381093+slhmy@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Add identity model documentation file for Identra
docs: add IDENTITY_MODEL.md
Apr 15, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new authoritative identity-model document to make Identra’s auth/merge/conflict semantics easier to understand without reading service code.
Changes:
- Introduces
docs/IDENTITY_MODEL.mddescribing identities, uniqueness/normalization, verification semantics, and auth flows. - Documents conflict scenarios with expected gRPC/HTTP codes.
- Documents rate limiting defaults and security considerations.
Comment on lines
+35
to
+42
| | `email` | string | The user's canonical email address. May be empty for OAuth-only accounts created without an email. Globally unique when non-empty. | | ||
| | `hashed_password`| string (nullable) | bcrypt hash of the user's password. `null` means password login is not set up for this account. | | ||
| | `github_id` | string (nullable) | Provider user ID for the GitHub OAuth identity. Globally unique when non-empty. | | ||
| | `last_login_at` | timestamp (nullable) | Updated on every successful authentication. | | ||
| | `created_at` | timestamp | Set on creation. | | ||
| | `updated_at` | timestamp | Updated on every write. | | ||
|
|
||
| A user record may exist with **no email** (OAuth-only user whose provider did not supply one) or with an email but **no password** (email-code-only or OAuth-only account). |
Comment on lines
+354
to
+371
| | Parameter | Default | Config key | | ||
| |------------------------|---------|------------------------| | ||
| | Max failed attempts | 5 | `LoginMaxAttempts` | | ||
| | Lockout window | 15 min | `LoginLockoutDuration` | | ||
|
|
||
| The counter key is the **email address** being authenticated. On each **failed** attempt the counter is incremented. On a **successful** login the counter is reset. The limiter is checked before any credential verification; if the limit is already exceeded the request is rejected immediately with `RESOURCE_EXHAUSTED`. | ||
|
|
||
| The limiter fails **open**: if Redis is unavailable the check is skipped and login proceeds normally. This prioritizes availability over perfect rate-limit enforcement. | ||
|
|
||
| ### Send-Code Rate Limiter | ||
|
|
||
| Applied to: `POST /email/code` | ||
|
|
||
| | Parameter | Default | Config key | | ||
| |------------------------|---------|-------------------------| | ||
| | Max sends per window | 5 | `SendCodeMaxAttempts` | | ||
| | Window duration | 1 hour | `SendCodeWindow` | | ||
|
|
| | Provider name | Lowercased and whitespace-stripped. | `" GitHub "` → `"github"` | | ||
| | Phone number | (Planned) Normalized to E.164 format. | `"(555) 867-5309"` → `"+15558675309"` | | ||
|
|
||
| > **Note on email case:** The current implementation stores and matches email addresses exactly as normalized (trimmed, original case). Two addresses that differ only in case (e.g., `alice@example.com` vs `Alice@example.com`) are treated as **different** identities. Callers should canonicalize email addresses to lowercase before submitting them if case-insensitive uniqueness is required. |
Comment on lines
+216
to
+219
| 3. Generate a cryptographically random 6-digit code (zero-padded). | ||
| 4. Store `(email → code)` in Redis with a 10-minute TTL (overwrites any previous code). | ||
| 5. Send the code to the email address via SMTP. | ||
| 6. Record the send attempt in the rate limiter. |
| | `user.github_id` | Global | Unique when non-empty. One GitHub account maps to at most one User. | | ||
| | Phone number | Global | (Planned) Unique when non-empty, stored in E.164 format. | | ||
|
|
||
| Uniqueness is enforced at the database layer (unique index). The application layer checks for existing records before insert and maps duplicate-key errors to `ALREADY_EXISTS` gRPC status. |
Comment on lines
+102
to
+107
| | OAuth provider email | The email address returned by the OAuth provider is accepted as verified **by the provider**. Identra trusts it without additional checks. | Provider asserts the email in the user-info response. | | ||
| | Email-code (`/email/login`) | The user controls the inbox for the submitted email address. | A 6-digit one-time code (valid 10 minutes) is sent and must be consumed to complete login. | | ||
| | Password (`/password/login`) | The user knows the password stored for that email address. | bcrypt hash comparison. | | ||
| | SMS-code (planned) | The user controls the phone number. | A one-time code sent via SMS must be consumed. | | ||
|
|
||
| A provider email used in the OAuth auto-merge flow (Section 5.1) is treated as verified by the provider; no additional Identra-level email verification is performed. |
Comment on lines
+143
to
+151
| | Provider ID found? | Email provided? | Email matches existing user? | Action | Result | | ||
| |--------------------|-----------------|------------------------------|------------------------------------------------------------------------|-------------------------------| | ||
| | Yes | (any) | (any) | Update user's email to provider email if it has changed (non-empty email only). | Existing user returned. | | ||
| | No | Yes | Yes | Link provider ID to the existing email-matched user (auto-merge). | Existing user returned. | | ||
| | No | Yes | No | Create new user with `email` + provider ID. | New user created and returned.| | ||
| | No | No | N/A | Create new user with provider ID only; email is empty. | New user created and returned.| | ||
|
|
||
| **Auto-merge rule:** When a provider presents an email address that already belongs to a local user, Identra automatically links the provider identity to that local user *without asking the user to confirm*. This relies entirely on the provider having verified the email. See [Section 8](#8-security-notes) for the security rationale and limitations. | ||
|
|
Comment on lines
+348
to
+349
| Identra uses a Redis-backed sliding-window counter for two independent rate limits. | ||
|
|
Comment on lines
+396
to
+399
| | Token | Default TTL | Config key | | ||
| |--------------|-------------|----------------------------------| | ||
| | Access token | 15 minutes | `AccessTokenExpirationDuration` | | ||
| | Refresh token| 7 days | `RefreshTokenExpirationDuration` | |
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.
Identra lacked a single authoritative reference for its identity model, making it hard to reason about merge rules, conflict semantics, and error codes without reading the source.
What's included
ensureOAuthUser: find by provider ID → auto-merge by email → create)RegisterByPasswordvsLoginByPassword— explicit separation,NOT_FOUND/FAILED_PRECONDITIONdistinctions documentedOriginal prompt
Create an English identity model documentation file for Identra.
Context:
Task:
docs/IDENTITY_MODEL.md.Acceptance criteria:
The following is the prior conversation context from the user's chat exploration (may be truncated):
User: Identra 值得继续提升的地方
Assistant: 可以,Identra 仍然很有继续提升的空间。如果你是想评估
poly-workshop/identra这个仓库“接下来还能优化什么”,我会优先从这几个方向看:1. 产品定位是否足够清晰
先问三个问题:
如果仓库首页、文档、示例不能让人 30 秒内明白这些,第一优先级就是补强 README、示例、使用场景、对比说明。
2. 文档和上手体验
很多项目不是技术不行,而是别人用不起来。可重点检查:
3. 代码结构与可维护性
值得提升的典型点:
如果项目已经能跑,但后续开发很慢,通常瓶颈就在这里。
4. 测试覆盖
如果测试少,后续迭代成本会越来越高。建议看:
5. 工程化能力
一个项目是否“成熟”,往往看工程细节:
6. 性能与稳定性
如果 Identra 有身份识别、检索、匹配、推理之类能力,这部分尤其关键:
7. 安全与权限设计
如果涉及 identity / auth / 用户数据,这一块优先级很高:
8. 社区与协作友好度
如果你希望它持续发展,而不只是“自己能用”:
如果你想要更具体一点,我会把“值得继续提升的地方”分成这 3 个优先级:
P0:最先做
P1:接着做
P2:长期做
如果你愿意,我下一步可以直接基于
poly-workshop/identra仓库做一次具体诊断,例如:如果要的话,我可以直接开始。
User: 开始
Assistant: 我已经开始看了。当前最值得继续提升的,是测试与CI、认证语义、安全边界、文档完整度,以及服务拆分。
下面是基于
poly-workshop/identra当前仓库状态给出的第一次具体诊断。先给结论:我会按这个优先级改
P0:最该先做
P1:接着做
service.go,降低维护成本P2:长期优化
1) CI 太薄,只有 lint,没有 test
这是我认为最明显的短板之一。
仓库现在有两个 workflow:
lint.ymldocker.yml但
lint.yml只跑golangci-lint,没有go test。而且
docker.yml只在打 tag 或手动触发时构建镜像。也就是说,当前主分支和 PR 并没有通过 CI 持续验证核心行为是否正确。为什么这是问题
项目本身已经有一些测试文件,例如:
internal/infrastructure/security/tokens_test.gointernal/infrastructure/persistence/gorm/db_test.gointernal/application/identra/service_oauth_email_test.gointernal/application/identra/service_ensure_oauth_user_test.go说明你们已经有“测试意识”,但没有把它接到质量闸门上。这会导致:
建议
直接加:
go test ./...2) “登录即注册”的语义需要重新审视
这点非常值得尽快明确,因为它不只是代码问题,更是产品与安全边界问题。
在
LoginByPassword里,如果用...This pull request was created from Copilot chat.