2026-05-20 07:01:08 +00:00
# Identity / Member / Permission <20> Ҳճ]<5D> p<EFBFBD> <70> <EFBFBD> Z
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
> **<2A> <> <EFBFBD> A**<2A> GDraft<66> ]<5D> <> Review<65> ^
> **<2A> A<EFBFBD> α M<CEB1> <4D> **<2A> GPortal API Gateway<61> ]PGW<47> ^
> **<2A> Ѧҹ<D1A6> <D2B9> @**<2A> G[app-cloudep-permission-server](https://code.30cm.net/digimon/app-cloudep-permission-server)<29> ]Casbin RBAC<41> BPermission Tree<65> BRole/RolePermission<6F> ^
> **<2A> ̫<EFBFBD> <CCAB> <EFBFBD> <EFBFBD> s**<2A> G2026-05-19
> **<2A> e<EFBFBD> <65> **<2A> G<EFBFBD> <47> <EFBFBD> s Gateway module<6C> A<EFBFBD> <41> <EFBFBD> Ҽ{<7B> ª<EFBFBD> member-server <20> E<EFBFBD> <45> <EFBFBD> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> y<EFBFBD> z Gateway <20> <> **auth** <EFBFBD> B**member**<2A> B**permission** <20> T<EFBFBD> ӷ~<7E> ȼҲժ<D2B2> <D5AA> ؼЬ [<5B> c<EFBFBD> A<EFBFBD> <41> <EFBFBD> X **ZITADEL** <EFBFBD> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> ^<5E> B**LDAP**<2A> ]<5D> <> <EFBFBD> ~<7E> ؿ<EFBFBD> <D8BF> ^<5E> B**SCIM 2.0**<2A> ]<5D> <> <EFBFBD> ~ provisioning<6E> ^<5E> A<EFBFBD> 䴩 ** <EFBFBD> h<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ** <20> P ** <EFBFBD> ʸU<EFBFBD> ŷ|<7C> <> **<2A> ]<5D> t<EFBFBD> 毲<EFBFBD> <E6AFB2> 50 <20> U<EFBFBD> ^<5E> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> Ҳդ<EFBFBD> <EFBFBD> h<EFBFBD> P<EFBFBD> {<7B> <> <EFBFBD> X<EFBFBD> <58> <EFBFBD> g<EFBFBD> W<EFBFBD> d<EFBFBD> <64> [model.md ](./model.md )<29> C
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## <20> ؿ<EFBFBD>
1. [<5B> ]<5D> p<EFBFBD> ؼлP<D0BB> <50> <EFBFBD> h](#1-<2D> ]<5D> p<EFBFBD> ؼлP<D0BB> <50> <EFBFBD> h)
2. [<EFBFBD> Ҳե<EFBFBD> <EFBFBD> <EFBFBD> ](#2-<2D> Ҳե<D2B2> <D5A5> <EFBFBD> )
3. [<EFBFBD> ~<7E> <> <EFBFBD> t<EFBFBD> Τ <EFBFBD> <CEA4> u ](#3-<2D> ~<7E> <> <EFBFBD> t<EFBFBD> Τ <EFBFBD> <CEA4> u )
4. [auth <20> Ҳ<EFBFBD> ](#4-auth-<2D> Ҳ<EFBFBD> )
5. [member <20> Ҳ<EFBFBD> ](#5-member-<2D> Ҳ<EFBFBD> )
6. [permission <20> Ҳա ]B2B <20> ۩w<DBA9> q<EFBFBD> ^](#6-permission-<2D> Ҳ<EFBFBD> b2b-<2D> ۩w<DBA9> q)
7. [API <20> W<EFBFBD> <57> ](#7-api-<2D> W<EFBFBD> <57> )
8. [Middleware <20> <> ](#8-middleware-<2D> <> )
9. [<EFBFBD> ֤߬y<EFBFBD> { ](#9-<2D> ֤߬y<DFAC> { )
10. [LDAP <20> P SCIM ](#10-ldap-<2D> P-scim )
2026-05-19 17:04:26 +00:00
11. [Notification Module ](#11-notification-module )
2026-05-20 07:01:08 +00:00
12. [<5B> iŪ UID <20> ]<5D> p](#12-<2D> iŪ-uid-<2D> ]<5D> p<EFBFBD> w<EFBFBD> M<EFBFBD> <4D> )
13. [<EFBFBD> <EFBFBD> <EFBFBD> Ƽ ҫ<EFBFBD> <EFBFBD> P<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ](#13-<2D> <> <EFBFBD> Ƽ ҫ<C6BC> <D2AB> P<EFBFBD> <50> <EFBFBD> <EFBFBD> )
14. [Redis Key <20> R<EFBFBD> W ](#14-redis-key-<2D> R<EFBFBD> W )
15. [<5B> W<EFBFBD> һ P<D2BB> ʯ<EFBFBD> <CAAF> ]100 <20> U+ / <20> 毲<EFBFBD> <E6AFB2> 50 <20> U<EFBFBD> ^](#15-<2D> W<EFBFBD> һ P<D2BB> ʯ<EFBFBD> 100-<2D> U--<2D> 毲<EFBFBD> <E6AFB2> -50-<2D> U)
16. [<EFBFBD> ؿ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> c ](#16-<2D> ؿ<EFBFBD> <D8BF> <EFBFBD> <EFBFBD> c )
17. [<5B> ]<5D> w<EFBFBD> <77> ](#17-<2D> ]<5D> w<EFBFBD> <77> )
18. [<EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ](#18-<2D> <> <EFBFBD> I<EFBFBD> <49> <EFBFBD> <EFBFBD> )
19. [<EFBFBD> w<EFBFBD> M<EFBFBD> <EFBFBD> <EFBFBD> ƶ<EFBFBD> ](#19-<2D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ƶ<EFBFBD> )
20. [Audit Log <20> P Rate Limit ](#20-audit-log-<2D> P-rate-limit )
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 1. <20> ]<5D> p<EFBFBD> ؼлP<D0BB> <50> <EFBFBD> h
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 1.1 <20> ؼ<EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> ؼ<EFBFBD> | <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
|------|------|
2026-05-20 07:01:08 +00:00
| <20> Τ @<40> <> <EFBFBD> <EFBFBD> | ZITADEL <20> @<40> <> IdP<64> ]<5D> t LDAP IdP<64> BSocial Login<69> ^ |
| <20> ~<7E> ȷ|<7C> <> | Gateway `member` <20> Ҳպz tenant-scoped profile |
| <20> Ӳɫױ<C9AB> <D7B1> v | Gateway `permission` <20> Ҳա ]**Casbin RBAC + Permission Tree**<2A> ^<5E> F**<2A> C<EFBFBD> <43> B2B <20> <> <EFBFBD> <EFBFBD> <EFBFBD> i<EFBFBD> ۩w<DBA9> q Role <20> äĿ<C3A4> Permission** |
| Token | go-zero JWT <20> <> <EFBFBD> <EFBFBD> + Redis <20> ¦W<C2A6> <57> <EFBFBD> ]<5D> u<EFBFBD> ¦W<C2A6> <57> JWT<57> ^ |
| <20> <> <EFBFBD> ~<7E> <> <EFBFBD> X | SCIM 2.0 + LDAP Directory Sync<6E> ]AD + OpenLDAP<41> ^ |
| <20> W<EFBFBD> <57> | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> x 100 <20> U+ <20> |<7C> <> <EFBFBD> F<EFBFBD> 毲<EFBFBD> <E6AFB2> <EFBFBD> i<EFBFBD> F 50 <20> U |
| UID | <20> H<EFBFBD> <48> <EFBFBD> iŪ<69> B<EFBFBD> a<EFBFBD> <61> <EFBFBD> <EFBFBD> <EFBFBD> e<EFBFBD> <65> <EFBFBD> A<EFBFBD> p `AMEX-10000000` <EFBFBD> F<EFBFBD> ߤ@<40> ʥH `tenant_id + uid` <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 1.2 <20> ֤߭<D6A4> <DFAD> h
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
1. ** ¾<EFBFBD> d<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> **
- `auth` <EFBFBD> G<EFBFBD> A<EFBFBD> O<EFBFBD> ֡]Authentication<6F> ^
- `member` <EFBFBD> G<EFBFBD> A<EFBFBD> <EFBFBD> <EFBFBD> ~<7E> ȸ<EFBFBD> <C8B8> ƬO<C6AC> <4F> <EFBFBD> <EFBFBD> <EFBFBD> ]Profile<6C> ^
- `permission` <EFBFBD> G<EFBFBD> A<EFBFBD> వ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]Authorization<6F> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
2. **LDAP <20> <> <EFBFBD> <EFBFBD> <EFBFBD> n<EFBFBD> J bind**
- <20> n<EFBFBD> J<EFBFBD> <4A> <EFBFBD> ҥ<EFBFBD> ZITADEL LDAP IdP <20> B<EFBFBD> z
- Gateway <20> <> LDAP client <20> Ȩ<EFBFBD> Directory Sync<6E> ]read-only<6C> ^
2026-05-19 13:56:59 +00:00
3. **Token Exchange**
2026-05-20 07:01:08 +00:00
- <20> <> <EFBFBD> ~ API <20> u<EFBFBD> <75> <EFBFBD> <EFBFBD> Gateway ñ<> o<EFBFBD> <6F> CloudEP JWT
- ZITADEL OIDC token <20> Ȧb `/auth/token/exchange` <20> ϥΤ @<40> <>
4. ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> j<EFBFBD> <EFBFBD> **
- <20> Ҧ<EFBFBD> <D2A6> <EFBFBD> <EFBFBD> [<5B> Ƹ<EFBFBD> <C6B8> ƥH `tenant_id` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
- JWT `tenant_id` <20> P<EFBFBD> ШD<D0A8> 귽<EFBFBD> <EAB7BD> <EFBFBD> <EFBFBD> <EFBFBD> @<40> P
5. **B2B <20> v<EFBFBD> <76> <EFBFBD> ۩w<DBA9> q** <EFBFBD> ]<5D> Ѧ<EFBFBD> [app-cloudep-permission-server ](https://code.30cm.net/digimon/app-cloudep-permission-server )<29> ^
- <20> <> <EFBFBD> x seed <20> <> <EFBFBD> <EFBFBD> Permission Tree<65> ]<5D> t `http_path` / `http_method` <EFBFBD> ^
- <20> <> <EFBFBD> <EFBFBD> <EFBFBD> إߦۭq Role<6C> A<EFBFBD> q Tree ** <EFBFBD> Ŀ<EFBFBD> ** Permission<6F> ]`RolePermission` + <20> ۰ ʸ<DBB0> parent<6E> ^
- API <20> <> <EFBFBD> v<EFBFBD> <76> **Casbin** <20> <> <EFBFBD> <EFBFBD> `(tenant_id, role_key, path, method)` <EFBFBD> A<EFBFBD> קK<EFBFBD> <EFBFBD> <EFBFBD> P<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> P<EFBFBD> W<EFBFBD> <EFBFBD> <EFBFBD> ⤬ <EFBFBD> ۦìV
- B2C <20> <> <EFBFBD> <EFBFBD> **<2A> <> Ū** seed <20> ҪO<D2AA> A**<2A> <> <EFBFBD> i**<2A> ۩w<DBA9> q Role<6C> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
6. ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> vs <20> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> Ҥ<EFBFBD> <D2A4> h**<2A> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
- **ZITADEL = <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> G<EFBFBD> n<EFBFBD> J MFA<46> ]TOTP / WebAuthn / SMS<4D> ^<5E> B<EFBFBD> <42> <EFBFBD> U email <20> <> <EFBFBD> ҡB<D2A1> ѰO<D1B0> K<EFBFBD> X<EFBFBD> B<EFBFBD> b<EFBFBD> <62> <EFBFBD> <EFBFBD> <EFBFBD> w
- **Gateway member = <20> ~<7E> ȯ<EFBFBD> <C8AF> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> G<EFBFBD> ~<7E> <> email / phone <20> j<EFBFBD> w OTP<54> BStep-up MFA
- Gateway ** <EFBFBD> <EFBFBD> **<2A> ̿<EFBFBD> `ZITADEL email_verified` <20> <> <EFBFBD> ~<7E> Ȧu<C8A6> <75> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> FLogic <20> h<EFBFBD> <68> Ū `BusinessEmailVerified` <20> <> member <20> X<EFBFBD> <58>
- **Email / SMS OTP <20> <> Gateway <20> ۰ e**<2A> ]<5D> <> <EFBFBD> <EFBFBD> ZITADEL Notification<6F> ^
- **MFA <20> j<EFBFBD> <EFBFBD> <EEB5A6> **<2A> Gadmin <20> <> role <20> <> ZITADEL Org Policy <20> j<EFBFBD> <6A> TOTP<54> F<EFBFBD> @<40> <> user <20> w<EFBFBD> ]<5D> <> <EFBFBD> j<EFBFBD> <6A> <EFBFBD> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> ާ@<40> <> Gateway Step-up
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 2. <20> Ҳե<D2B2> <D5A5> <EFBFBD>
```
<EFBFBD> z<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> {
<EFBFBD> x Portal API Gateway (go-zero) <20> x
<EFBFBD> u<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> t
<EFBFBD> x generate/api/ <20> x
<EFBFBD> x auth.api <20> P member.api <20> P permission.api <20> P tenant.api <20> P scim.api<70> x
<EFBFBD> u<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> t
<EFBFBD> x internal/middleware/ <20> x
<EFBFBD> x jwt_revoke <20> P casbin_rbac <20> P scim_auth <20> P tenant_context <20> x
<EFBFBD> u<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> t
<EFBFBD> x internal/model/ <20> x
<EFBFBD> x auth/ <20> <> Token ñ<> o<EFBFBD> B<EFBFBD> <42> <EFBFBD> <EFBFBD> <EFBFBD> B<EFBFBD> n<EFBFBD> X<EFBFBD> B<EFBFBD> ¦W<C2A6> <57> <EFBFBD> Bauth_gen<65> Bstep-up<75> x
<EFBFBD> x member/ <20> <> Profile<6C> BIdentity<74> BTenant<6E> BUID<49> BSync<6E> BTOTP<54> B<EFBFBD> <42> <EFBFBD> Ңx
<EFBFBD> x permission/ <20> <> Casbin RBAC<41> BPermission Tree<65> BRole<6C> ]B2B <20> ۩w<DBA9> q<EFBFBD> ^<5E> x
<EFBFBD> x notification/ <20> <> Email/SMS/Push <20> Τ @<40> o<EFBFBD> e<EFBFBD> B<EFBFBD> ҪO<D2AA> B<EFBFBD> <42> <EFBFBD> ա Baudit <20> x
<EFBFBD> u<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> t
<EFBFBD> x internal/library/ <20> x
<EFBFBD> x zitadel/ <20> P ldap/ <20> P uid/ <20> P casbin/ <20> x
<EFBFBD> x notification/email <20> P notification/sms <20> P notification/push <20> x
<EFBFBD> u<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> t
<EFBFBD> x internal/worker/ <20> x
<EFBFBD> x directory_sync/ <20> P notification_retry/ <20> P member_anonymize/ <20> x
<EFBFBD> |<7C> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> }
<20> x <20> x <20> x
<20> <> <20> <> <20> <>
2026-05-19 13:56:59 +00:00
MongoDB Redis ZITADEL
(profile/role) (cache/blacklist) (identity/LDAP IdP)
2026-05-19 17:04:26 +00:00
+
Email / SMS Provider
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### 2.1 <20> Ҳը̿<D5A8> <CCBF> <EFBFBD> <EFBFBD> V
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
handler <20> <> logic <20> <> model/{auth|member|permission|notification}/usecase<73> ]interface<63> ^
<20> <>
repository <20> <> MongoDB / Redis
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
logic <20> <> import entity / repository<72> ]<5D> <> model.md<6D> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
auth <20> <> member<65> ]EnsureFromOIDC / EnsureFromLDAP / EnsureFromSCIM<49> ^
auth <20> <> permission<6F> ]SyncRolesFromClaims<6D> ^
auth <20> <> member.TOTPUseCase<73> ]step-up TOTP <20> <> <EFBFBD> ҡ^
member <20> <> auth<74> ]<5D> <> <EFBFBD> v<EFBFBD> <76> RevokeAllForUser<65> ^
member <20> <> notification<6F> ]<5D> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> / step-up OTP <20> H<EFBFBD> e<EFBFBD> ^
permission <20> <> member<65> ]<5D> i<EFBFBD> <69> <EFBFBD> G<EFBFBD> <47> <EFBFBD> <EFBFBD> uid <20> s<EFBFBD> b<EFBFBD> ^
notification <20> <> library/notification/{email,sms,push}<7D> ]provider <20> <> <EFBFBD> @<40> ^
2026-05-19 13:56:59 +00:00
```
---
2026-05-20 07:01:08 +00:00
## 3. <20> ~<7E> <> <EFBFBD> t<EFBFBD> Τ <EFBFBD> <CEA4> u
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> O | ZITADEL | Gateway auth | Gateway member | Gateway permission | Gateway notification |
2026-05-19 17:04:26 +00:00
|------|---------|--------------|----------------|-------------------|----------------------|
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> U / <20> n<EFBFBD> J<EFBFBD> ]OIDC / LDAP / SCIM<49> ^ | ? | <20> <> <EFBFBD> <EFBFBD> | EnsureFromOIDC/LDAP/SCIM | SyncRoles | <20> X |
| <20> <> <EFBFBD> x<EFBFBD> <78> <EFBFBD> ͵<EFBFBD> <CDB5> U<EFBFBD> ]<5D> <> <EFBFBD> ӡA<D3A1> t email OTP<54> ^ | <20> ]local user<65> ^| <20> X | LifecycleUseCase + OTPUseCase | <20> X | <20> H OTP |
| <20> K<EFBFBD> X / <20> <> <EFBFBD> <EFBFBD> MFA / <20> ѰO<D1B0> K<EFBFBD> X | ? | <20> X | <20> X | <20> X | <20> X |
| <20> <> <EFBFBD> <EFBFBD> MFA <20> j<EFBFBD> <EFBFBD> <EEB5A6> | ? Org Policy | <20> X | <20> X | <20> X | <20> X |
| Google / LINE / Apple | ? IdP | <20> X | <20> X | <20> X | <20> X |
| LDAP <20> n<EFBFBD> J | ? LDAP IdP | <20> X | <20> X | Group<75> <70> Role <20> M<EFBFBD> g | <20> X |
| Access / Refresh Token<65> ]<5D> <> <EFBFBD> ~<7E> ^ | <20> X | ? CloudEP JWT | <20> X | <20> X | <20> X |
| Step-up Token<65> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> ާ@<40> ^ | <20> X | ? ñ step_up_token | OTP / TOTP <20> <> <EFBFBD> <EFBFBD> | Logic <20> u<EFBFBD> <75> | OTP <20> H<EFBFBD> e |
| <20> ~<7E> <> TOTP<54> ]Authenticator<6F> ^ | <20> X | <20> X | ? secret <20> [<5B> K<EFBFBD> x<EFBFBD> s + <20> <> <EFBFBD> <EFBFBD> | <20> X | <20> X |
| JWT <20> ¦W<C2A6> <57> | <20> X | ? Redis | <20> X | <20> X | <20> X |
| <20> ~<7E> <> UID | <20> X | <20> X | ? | <20> X | <20> X |
| Profile | <20> X | <20> X | ? | <20> X | <20> X |
| <20> ~<7E> <> Email / Phone <20> <> <EFBFBD> <EFBFBD> | <20> X | <20> X | ? Verification <20> y<EFBFBD> { | <20> X | ? OTP <20> H<EFBFBD> e |
| Email / SMS / Push <20> o<EFBFBD> e | <20> X | <20> X | <20> X | <20> X | ? <20> Τ @<40> J<EFBFBD> f + <20> ҪO + <20> <> <EFBFBD> <EFBFBD> |
| <20> |<7C> <> <EFBFBD> C<EFBFBD> <43> / <20> <> <EFBFBD> A | <20> X | <20> X | ? | <20> ݱ<EFBFBD> <DDB1> v | <20> ܧ<EFBFBD> <DCA7> q<EFBFBD> <71> <EFBFBD> ]<5D> <> <EFBFBD> B<EFBFBD> ^ |
| API <20> Ӳɫ<D3B2> <C9AB> v<EFBFBD> <76> | <20> ʲɫ<CAB2> Role | <20> X | <20> X | **Casbin RBAC** <EFBFBD> ]path + method<6F> ^ | <20> X |
| SCIM Users/Groups | <20> i<EFBFBD> P<EFBFBD> B | <20> X | ? <20> ~<7E> ȼg<C8BC> J | ? Group<75> <70> Role | <20> X |
| LDAP Directory Sync | <20> X | <20> X | ? Worker | ? Group<75> <70> Role | <20> P<EFBFBD> B<EFBFBD> <42> <EFBFBD> `<60> iĵ |
### 3.1 <20> h<EFBFBD> <68> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
```
1 CloudEP Tenant = 1 ZITADEL Organization = 1 <20> <> <EFBFBD> ƹj<C6B9> <6A> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
```
| <20> <> <EFBFBD> <EFBFBD> | <20> ӷ<EFBFBD> | <20> γ ~ |
2026-05-19 13:56:59 +00:00
|------|------|------|
2026-05-20 07:01:08 +00:00
| `tenant_id` | ZITADEL `org_id` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> B<EFBFBD> <42> <EFBFBD> v<EFBFBD> <76> <EFBFBD> <EFBFBD> |
| `identity_id` | ZITADEL `sub` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> M<EFBFBD> g |
| `uid` | Member <20> Ҳղ<D2B2> <D5B2> <EFBFBD> | <20> ~<7E> ȷ|<7C> <> ID<49> ]<5D> p `AMEX-10000000` <EFBFBD> ^ |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### Tenant <20> إ߶<D8A5> <DFB6> ǡ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> GGateway <20> <> <EFBFBD> د<EFBFBD> <D8AF> Z<EFBFBD> ^
2026-05-19 17:04:26 +00:00
```
1. POST /api/v1/admin/tenants { slug, uid_prefix, type, ... }
2026-05-20 07:01:08 +00:00
<20> <> Mongo upsert tenants {status: "provisioning", org_id: ""}
2026-05-19 17:04:26 +00:00
2. ZITADEL Mgmt.CreateOrganization(name=slug)
2026-05-20 07:01:08 +00:00
<20> <> <20> <> <EFBFBD> <EFBFBD> org_id
2026-05-19 17:04:26 +00:00
3. UPDATE tenants {org_id, status: "active"}
2026-05-20 07:01:08 +00:00
4. seed <20> w<EFBFBD> ] Role + Casbin policy reload
5. <20> ^<5E> <> tenant payload
<EFBFBD> <EFBFBD> <EFBFBD> Ѹ<EFBFBD> <EFBFBD> v<EFBFBD> G
- <20> B<EFBFBD> J 2 <20> <> <EFBFBD> <EFBFBD> <20> <> status = "failed"<22> Acron <20> <> <EFBFBD> ա ]<5D> <> <EFBFBD> ưh<C6B0> ס A3 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> H<EFBFBD> u<EFBFBD> <75> <EFBFBD> J<EFBFBD> ^
- <20> B<EFBFBD> J 3 <20> <> <EFBFBD> <EFBFBD> <20> <> status = "orphan_zitadel_org"<22> Acron <20> <> <EFBFBD> <EFBFBD> <EFBFBD> øɸj
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
> Saga <20> <> <EFBFBD> <EFBFBD> <EFBFBD> GGateway <20> <> <EFBFBD> D<EFBFBD> BZITADEL <20> <> <EFBFBD> q<EFBFBD> F<EFBFBD> <46> <EFBFBD> v cron <20> C 5 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> `status in ("failed", "orphan_zitadel_org")` <20> <> <EFBFBD> թΧ iĵ<69> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 3.2 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> | <20> n<EFBFBD> J | LDAP | <20> v<EFBFBD> <76> |
2026-05-19 13:56:59 +00:00
|------|------|------|------|
2026-05-20 07:01:08 +00:00
| **B2C** | Email / Social | <20> L | <20> t<EFBFBD> ι w<CEB9> ] Role<6C> ]<5D> <> <EFBFBD> i<EFBFBD> Τ <EFBFBD> <CEA4> `<60> ۩w<DBA9> q<EFBFBD> ^ |
| **B2B** | ZITADEL <20> <> LDAP IdP | <20> <> | ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ۩w<EFBFBD> q Role + Permission** |
| **Hybrid** | Social + LDAP | <20> <> | B2B <20> ۩w<DBA9> q<EFBFBD> F<EFBFBD> ~<7E> <> <EFBFBD> Ȥ<EFBFBD> <C8A4> <EFBFBD> B2C <20> <> Ū<EFBFBD> ҪO |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 3.3 ZITADEL <20> <> <EFBFBD> p<EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> GSelf-hosted<65> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- **<2A> <> <EFBFBD> p<EFBFBD> 覡**<2A> GSelf-hosted<65> ]<5D> ۫ء^<5E> A<EFBFBD> P Gateway / Mongo / Redis <20> P<EFBFBD> <50> <EFBFBD> ҩΦP VPC
- **LDAP <20> <> <EFBFBD> <EFBFBD> **<2A> GZITADEL <20> <> <EFBFBD> һ ݯઽ<DDAF> s<EFBFBD> <73> <EFBFBD> ~ AD / OpenLDAP<41> ]<5D> `<60> <> <EFBFBD> GVPN<50> B<EFBFBD> M<EFBFBD> u<EFBFBD> B<EFBFBD> <42> DMZ <20> <> <EFBFBD> o<EFBFBD> ^
- **Management API / JWKS**<2A> GGateway <20> z<EFBFBD> L<EFBFBD> <4C> <EFBFBD> <EFBFBD> URL <20> s<EFBFBD> <73> <EFBFBD> A<EFBFBD> <41> <EFBFBD> g<EFBFBD> <67> <EFBFBD> <EFBFBD>
- **<2A> ]<5D> w**<2A> G`etc/gateway.yaml` <20> <> `Zitadel.Issuer` / `MgmtURL` <20> <> <EFBFBD> V self-hosted <20> <> <EFBFBD> I
2026-05-19 17:04:26 +00:00
2026-05-21 06:45:35 +00:00
### 3.4 <20> <> <EFBFBD> U<EFBFBD> <55> <EFBFBD> |<7C> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> GGateway <20> Τ @<40> <> <EFBFBD> U BFF<46> ^
2026-05-19 17:04:26 +00:00
2026-05-21 06:45:35 +00:00
> **<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> W<EFBFBD> <57> **<2A> G[auth-unified-registration.md](./auth-unified-registration.md)<29> ]2026-05-21 <20> _<EFBFBD> <5F> <EFBFBD> ǡF<C7A1> <46> <EFBFBD> `<60> <> <EFBFBD> K<EFBFBD> n<EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-21 06:45:35 +00:00
Gateway ** <EFBFBD> <EFBFBD> <EFBFBD> S** `/api/v1/auth/register*` <20> @<40> <> B2C <20> Τ @<40> <> <EFBFBD> U<EFBFBD> J<EFBFBD> f<EFBFBD> FZITADEL <20> @<40> <> identity <20> <> <EFBFBD> ݡ]<5D> b<EFBFBD> K<EFBFBD> BOIDC<44> ^<5E> A**<2A> <> <EFBFBD> A**<2A> n<EFBFBD> D<EFBFBD> ϥΪ̸<CEAA> <CCB8> <EFBFBD> ZITADEL Hosted Register UI<55> C
2026-05-19 17:04:26 +00:00
2026-05-21 06:45:35 +00:00
| <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> | <20> <> <EFBFBD> U<EFBFBD> <55> <EFBFBD> | | <20> <> <EFBFBD> <EFBFBD> |
|---------|----------|------|
| **B2C Email** | `POST /auth/register` <20> <> OTP <20> <> `POST /auth/register/confirm` | Logic <20> s<EFBFBD> ơGinvite consume <20> <> `zitadel.CreateHumanUser` <20> <> `Lifecycle.CreateUnverified` <20> <> registration OTP <20> <> `Activate` <20> <> CloudEP JWT |
| **B2C Social<61> ]Google<6C> ^** | `POST /auth/register/social/start` <20> <> OAuth <20> <> `GET /auth/register/social/callback` | OAuth ** <EFBFBD> e**<2A> j<EFBFBD> w invite session<6F> ]Redis<69> ^<5E> Fcallback <20> <> <EFBFBD> <EFBFBD> invite <20> <> `EnsureFromOIDC` <20> <> registration metadata <20> <> JWT |
| **B2B<32> ]LDAP<41> ^** | <20> <> IT <20> b AD / OpenLDAP <20> رb<D8B1> FDirectory Sync <20> w provision | <20> n<EFBFBD> J<EFBFBD> <4A> LDAP IdP <20> <> `EnsureFromLDAP` JIT<49> F**<2A> <> <EFBFBD> g** register API |
| **B2B<32> ]SCIM<49> ^** | HR / Okta / Entra <20> <> SCIM Create User | SCIM endpoint <20> g ZITADEL + Gateway<61> F**<2A> <> <EFBFBD> g** register API |
**<2A> ӰȳW<C8B3> h<EFBFBD> ]Logic <20> h<EFBFBD> A<EFBFBD> D usecase<73> ^<5E> G**
- Invite code ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> ]`Member.Registration.RequireInviteCode`<60> A<EFBFBD> w<EFBFBD> ] `true` <EFBFBD> ^
- <20> <> <EFBFBD> ڪ<EFBFBD> <DAAA> <EFBFBD> `accept_terms_version` <20> <> <EFBFBD> <EFBFBD>
- <20> <> <EFBFBD> U<EFBFBD> <55> <EFBFBD> <EFBFBD> <EFBFBD> e ** <EFBFBD> <EFBFBD> <EFBFBD> o** CloudEP JWT<57> Fconfirm / social callback <20> <> <EFBFBD> ~ `IssuePair`
- Invite <20> <> <EFBFBD> ӫ<EFBFBD> <D3AB> Y ZITADEL / member <20> <> <EFBFBD> <EFBFBD> <20> <> ** <EFBFBD> <EFBFBD> <EFBFBD> ^<5E> u invite**<2A> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> F<EFBFBD> <46> auth-unified-registration <20> <> 9<EFBFBD> ^
- Social <20> n<EFBFBD> J<EFBFBD> ]<5D> D<EFBFBD> <44> <EFBFBD> U<EFBFBD> ^<5E> <> ** `/auth/login/social/*` **<2A> A<EFBFBD> P register session ** <EFBFBD> <EFBFBD> state <20> e<EFBFBD> <65> **<2A> ]`login:` vs `reg:` <EFBFBD> ^
**<2A> n<EFBFBD> J<EFBFBD> ]<5D> D<EFBFBD> <44> <EFBFBD> U<EFBFBD> ^** <20> <> [auth-unified-registration.md <20> <> 3.3 ](./auth-unified-registration.md#33-<2D> n<EFBFBD> J<EFBFBD> D<EFBFBD> <44> <EFBFBD> U )<29> G`/auth/login`<60> B`/auth/token/refresh`<60> B`/auth/token/exchange`<60> BSocial login<69> C
> ZITADEL <20> <> <EFBFBD> <EFBFBD> email <20> <> <EFBFBD> ҥΩ<D2A5> **<2A> <> <EFBFBD> <EFBFBD> ** <20> n<EFBFBD> J<EFBFBD> <4A> <EFBFBD> e<EFBFBD> F<EFBFBD> <46> <EFBFBD> x<EFBFBD> <78> <EFBFBD> ͵<EFBFBD> <CDB5> U<EFBFBD> t<EFBFBD> H Gateway registration OTP<54> ]`OTPPurposeRegistrationEmail`<60> ^<5E> T<EFBFBD> {<7B> <> <EFBFBD> ~ `Activate`<60> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 3.5 <20> <> <EFBFBD> x MFA <20> j<EFBFBD> <6A> <EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- ZITADEL Org Policy <20> ]<5D> w<EFBFBD> G**<2A> <> <EFBFBD> <EFBFBD> admin <20> <> role**<2A> ]`tenant_owner` / `tenant_admin` / `platform_super_admin` <EFBFBD> ^<5E> n<EFBFBD> J<EFBFBD> ɱj<C9B1> <6A> TOTP / WebAuthn
- <20> @<40> <> user <20> w<EFBFBD> ]<5D> <> <EFBFBD> j<EFBFBD> <6A> <EFBFBD> ]<5D> קK B2C <20> y<EFBFBD> <79> <EFBFBD> ^
- <20> <> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> ~<7E> Ⱦާ@ <20> <> <20> <> Gateway Step-up MFA<46> ]<5D> <> 5.6<EFBFBD> ^<5E> A<EFBFBD> P ZITADEL <20> <> <EFBFBD> <EFBFBD> MFA ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> N**
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 4. auth <20> Ҳ<EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> <EFBFBD> <EFBFBD> |<7C> G`internal/model/auth/`
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 4.1 ¾<> d
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
- <20> <> <EFBFBD> <EFBFBD> ZITADEL OIDC token<65> ]id_token / authorization_code + PKCE<43> ^
- <20> s<EFBFBD> <73> `member.EnsureFromOIDC` / `EnsureFromLDAP` / `EnsureFromSCIM` <20> P `permission.SyncRolesFromClaims`
- ñ<> o CloudEP JWT<57> ]access + refresh<73> ^
- **ñ<> o Step-up Token**<2A> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> ާ@<40> Ρ A<CEA1> u<EFBFBD> ةR 5min<69> F<EFBFBD> <46> <20> <> 5.6<EFBFBD> ^
- <20> n<EFBFBD> X<EFBFBD> Gjti <20> ¦W<C2A6> <57>
- <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> ġG`auth_gen`<60> ]<5D> <> <EFBFBD> v / <20> <> <EFBFBD> K<EFBFBD> X / <20> v<EFBFBD> <76> <EFBFBD> j<EFBFBD> <6A> <EFBFBD> <EFBFBD> <EFBFBD> s<EFBFBD> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 4.2 UseCase <20> <> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
```go
type TokenUseCase interface {
Exchange(ctx context.Context, req *ExchangeRequest) (*TokenPair, error)
Refresh(ctx context.Context, req *RefreshRequest) (*TokenPair, error)
Logout(ctx context.Context, req *LogoutRequest) error
RevokeAllForUser(ctx context.Context, tenantID, uid string) error
}
2026-05-19 17:04:26 +00:00
type StepUpTokenUseCase interface {
Issue(ctx context.Context, tenantID, uid, action string) (stepUpToken string, err error)
Verify(ctx context.Context, token, expectedAction, tenantID, uid string) (jti string, err error)
2026-05-20 07:01:08 +00:00
MarkUsed(ctx context.Context, jti string) error // <20> 榸<EFBFBD> <E6A6B8>
2026-05-19 17:04:26 +00:00
}
2026-05-19 13:56:59 +00:00
```
### 4.3 CloudEP JWT Claims
```go
type Claims struct {
2026-05-20 07:01:08 +00:00
jwt.RegisteredClaims // <20> t jti, exp, iat
2026-05-19 13:56:59 +00:00
TenantID string `json:"tenant_id"`
UID string `json:"uid"`
2026-05-19 17:04:26 +00:00
Typ string `json:"typ"` // access | refresh | step_up
2026-05-20 07:01:08 +00:00
AuthGen int64 `json:"auth_gen"` // <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> ĥN<C4A5> <4E> <EFBFBD> ]ñ<> o<EFBFBD> <6F> = redis.GET <20> <> <EFBFBD> e<EFBFBD> ȡF<C8A1> <46> <EFBFBD> s<EFBFBD> b<EFBFBD> <62> <EFBFBD> <EFBFBD> 0<> ^
Action string `json:"action,omitempty"` // typ=step_up <20> ɥ<EFBFBD> <C9A5> <EFBFBD> <EFBFBD> A<EFBFBD> <41> <EFBFBD> w<EFBFBD> <77> <EFBFBD> \<5C> <> <EFBFBD> 檺<EFBFBD> <E6AABA> <EFBFBD> <EFBFBD> <EFBFBD> I action
2026-05-19 13:56:59 +00:00
}
```
2026-05-20 07:01:08 +00:00
> **JWT <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> role / permission <20> ַ<EFBFBD> **<2A> CMiddleware <20> C<EFBFBD> <43> <EFBFBD> q `perm:user_roles:{tenant_id}:{uid}` cache Ū<> <C5AA> <EFBFBD> <EFBFBD> <EFBFBD> e role keys <20> A enforce<63> F<EFBFBD> קK<D7A7> u<EFBFBD> <75> <EFBFBD> W / <20> M<EFBFBD> <4D> / <20> ܧ<EFBFBD> <DCA7> v<EFBFBD> <76> <EFBFBD> v<EFBFBD> <76> <EFBFBD> <EFBFBD> token <20> ٯ<EFBFBD> <D9AF> Ρ C
> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ܧ<EFBFBD> <EFBFBD> ߧY<EFBFBD> ͮľa `auth_gen` + cache invalidate<74> F<EFBFBD> <46> <EFBFBD> ̿<EFBFBD> token <20> <> <EFBFBD> e<EFBFBD> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 4.4 JWT <20> ]<5D> w<EFBFBD> ]go-zero<72> ^+ Secret Rotation<6F> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 13:56:59 +00:00
```yaml
Auth:
2026-05-20 07:01:08 +00:00
AccessExpire: 900 # 15 <20> <> <EFBFBD> <EFBFBD>
ActiveKID: v2 # <20> <> <EFBFBD> eñ<65> o<EFBFBD> <6F> kid
Keys: # <20> <> <EFBFBD> ҥi<D2A5> <69> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> kid <20> W<EFBFBD> <57> <EFBFBD> ]<5D> t<EFBFBD> <74> <EFBFBD> b<EFBFBD> h<EFBFBD> Ъ<EFBFBD> <D0AA> ^
2026-05-19 17:04:26 +00:00
- kid: v1
Secret: ${JWT_ACCESS_SECRET_V1}
- kid: v2
Secret: ${JWT_ACCESS_SECRET_V2}
2026-05-19 13:56:59 +00:00
RefreshAuth:
2026-05-20 07:01:08 +00:00
AccessExpire: 604800 # 7 <20> <>
2026-05-19 17:04:26 +00:00
ActiveKID: v2
Keys:
- kid: v1
Secret: ${JWT_REFRESH_SECRET_V1}
- kid: v2
Secret: ${JWT_REFRESH_SECRET_V2}
StepUp:
TokenTTLSeconds: 300
ActiveKID: v1
Keys:
- kid: v1
Secret: ${JWT_STEPUP_SECRET_V1}
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
**Rotation <20> y<EFBFBD> {<7B> G**
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
1. <20> s<EFBFBD> W v(N+1) key <20> <> Keys<79> ]<5D> <> <EFBFBD> <EFBFBD> ActiveKID<49> ^<5E> <> rolling deploy
2. <20> <> ActiveKID = v(N+1) <20> <> <20> s token <20> ηs kid ñ<> F<EFBFBD> <46> kid token <20> <> <EFBFBD> i<EFBFBD> <69>
3. <20> <> <EFBFBD> <EFBFBD> token <20> <> <EFBFBD> <EFBFBD> <EFBFBD> L<EFBFBD> <4C> <EFBFBD> ]access 15min / refresh 7d<37> ^
4. <20> q Keys <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> kid <20> <> rolling deploy
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
- JWT header <20> <> <EFBFBD> a `kid` <EFBFBD> A<EFBFBD> <EFBFBD> <EFBFBD> Ү ɨ<EFBFBD> `kid` <20> <> secret<65> F<EFBFBD> 䤣<EFBFBD> <E4A4A3> <20> <> `401 invalid_kid`
- go-zero <20> <> <EFBFBD> <EFBFBD> JWT middleware <20> ȦY<C8A6> <59> secret<65> A**<2A> ۼg `JwtMultiKeyMiddleware` ** <20> <> <EFBFBD> N<EFBFBD> Ϋe<CEAB> m<EFBFBD> ]<5D> b `JwtRevokeMiddleware` <20> <> <EFBFBD> e<EFBFBD> ^
- ZITADEL Token Exchange<67> BStep-up <20> @<40> Φ<EFBFBD> <CEA6> [<5B> c
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
`.api` <20> <> <EFBFBD> O<EFBFBD> @<40> <> <EFBFBD> ѡ G
2026-05-19 13:56:59 +00:00
```api
2026-05-19 17:04:26 +00:00
@server (jwt: Auth, middleware: JwtMultiKeyMiddleware,JwtRevokeMiddleware)
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### 4.5 <20> ¦W<C2A6> 浦<EFBFBD> <E6B5A6> <EFBFBD> ]<5D> u<EFBFBD> ¦W<C2A6> <57> JWT<57> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
#### Issue Token Pair <20> ɰO<C9B0> <4F> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> logout <20> <> <EFBFBD> <EFBFBD> <EFBFBD> a refresh<73> ^
2026-05-19 17:04:26 +00:00
```
SET auth:jwt:pair:{access_jti} = refresh_jti TTL = access TTL
SET auth:jwt:pair:{refresh_jti} = access_jti TTL = refresh TTL
```
2026-05-20 07:01:08 +00:00
#### <20> <> Token <20> M<EFBFBD> P<EFBFBD> ]<5D> n<EFBFBD> X<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```
Key: auth:jwt:bl:{jti}
Value: 1
2026-05-20 07:01:08 +00:00
TTL: token <20> Ѿl<D1BE> <6C> <EFBFBD> Įɶ<C4AE> <C9B6> ]exp - now<6F> ^
2026-05-19 13:56:59 +00:00
```
2026-05-19 17:04:26 +00:00
```
POST /auth/logout (Bearer access_jwt)
2026-05-20 07:01:08 +00:00
1. <20> <> access_jti <20> <> SET auth:jwt:bl:{access_jti}
2. GET auth:jwt:pair:{access_jti} <20> <> refresh_jti<74> ]<5D> Y<EFBFBD> s<EFBFBD> b<EFBFBD> ^
2026-05-19 17:04:26 +00:00
3. SET auth:jwt:bl:{refresh_jti}
4. DEL auth:jwt:pair:{access_jti} / auth:jwt:pair:{refresh_jti}
```
2026-05-20 07:01:08 +00:00
#### Refresh Token <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^+ Reuse Detection
2026-05-19 17:04:26 +00:00
```
POST /auth/token/refresh
2026-05-20 07:01:08 +00:00
1. <20> <> <EFBFBD> <EFBFBD> refresh_jwt<77> ]typ=refresh<73> B<EFBFBD> <42> <EFBFBD> L<EFBFBD> <4C> <EFBFBD> Bauth_gen <20> <> <EFBFBD> ġ^
2. <20> Y refresh_jti <20> w<EFBFBD> b<EFBFBD> ¦W<C2A6> <57> <EFBFBD> G
<20> <> <EFBFBD> <EFBFBD> <EFBFBD> Q<EFBFBD> ѩέ<D1A9> <CEAD> <EFBFBD> <20> <> INCR auth:gen:{tenant_id}:{uid}<7D> ]<5D> M<EFBFBD> P<EFBFBD> <50> <EFBFBD> <EFBFBD> chain<69> ^
<20> ^ 401<30> A<EFBFBD> üg audit log
3. ñ<> o<EFBFBD> s access_jwt + <20> s refresh_jwt<77> ]<5D> s jti<74> ^
4. <20> <> <EFBFBD> <EFBFBD> refresh_jti<74> F<EFBFBD> Y<EFBFBD> <59> access <20> <> <EFBFBD> <EFBFBD> jti <20> <> <EFBFBD> <EFBFBD> <EFBFBD> L<EFBFBD> <4C> <EFBFBD> A<EFBFBD> @<40> ֶ¦W<C2A6> <57>
5. <20> g<EFBFBD> J<EFBFBD> s<EFBFBD> <73> auth:jwt:pair
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
- <20> C<EFBFBD> <43> refresh <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]Refresh Token Rotation<6F> ^
- **Reuse detection**<2A> G<EFBFBD> <47> refresh <20> Q<EFBFBD> ĤG<C4A4> <47> <EFBFBD> ϥ<EFBFBD> <20> <> <20> <> <EFBFBD> P<EFBFBD> s<EFBFBD> Ρ A<CEA1> ߧY<DFA7> <59> <EFBFBD> q<EFBFBD> M<EFBFBD> P<EFBFBD> <50> user
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
#### Token Exchange <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
```
POST /auth/token/exchange { tenant_slug, id_token }
2026-05-20 07:01:08 +00:00
1. zitadel.VerifyIDToken<65> ]<5D> <> aud<75> Biss<73> Bexp<78> Bsignature<72> ^
2. <20> j<EFBFBD> <6A> <EFBFBD> ˬd id_token.iat <20> b<EFBFBD> ̪<EFBFBD> 5 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
3. SETNX auth:exchange:nonce:{id_token.jti}=1 TTL 10min<69> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <20> <> 409 <20> w<EFBFBD> ϥ<EFBFBD>
4. <20> <> <EFBFBD> <EFBFBD> tenant_slug <20> <> tenant.org_id == id_token.org_id
2026-05-19 17:04:26 +00:00
5. EnsureFromOIDC / SyncRoles / IssueTokenPair
```
2026-05-20 07:01:08 +00:00
#### Step-up Token<65> ]<5D> 榸<EFBFBD> ʡB<CAA1> <42> action<6F> ^
2026-05-19 17:04:26 +00:00
```
Key: auth:stepup:used:{jti} SETNX TTL = step_up_token TTL
Value: 1
```
2026-05-20 07:01:08 +00:00
- TTL<54> G5 <20> <> <EFBFBD> <EFBFBD>
- Claims<6D> G`typ=step_up` + `action` <EFBFBD> ]<5D> p `change_business_email` <EFBFBD> ^
- Logic <20> h<EFBFBD> u<EFBFBD> <75> <EFBFBD> G
1. <20> <> step_up JWT <20> <> <20> <> `typ == "step_up"` <EFBFBD> B`tenant_id`<60> B`uid`<60> B`action == expected`
2. `SETNX auth:stepup:used:{jti}=1` <EFBFBD> A<EFBFBD> w<EFBFBD> s<EFBFBD> b <20> <> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> ڵ<EFBFBD>
3. <20> q<EFBFBD> L<EFBFBD> <4C> <EFBFBD> <EFBFBD> <EFBFBD> 氪<EFBFBD> <E6B0AA> <EFBFBD> I<EFBFBD> ާ@<40> Ftoken <20> Y<EFBFBD> @<40> o
- Step-up token ** <EFBFBD> <EFBFBD> **<2A> i jti <20> ¦W<C2A6> <57> <EFBFBD> t<EFBFBD> Ρ F<CEA1> 榸<EFBFBD> ʾ a `auth:stepup:used` <20> Y<EFBFBD> i
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> ġ]<5D> <> <EFBFBD> v / <20> <> <EFBFBD> K<EFBFBD> X / SCIM deactivate / **<2A> v<EFBFBD> <76> <EFBFBD> ܧ<EFBFBD> **<2A> ^
2026-05-19 13:56:59 +00:00
```
Key: auth:gen:{tenant_id}:{uid}
2026-05-20 07:01:08 +00:00
Value: <20> <> <EFBFBD> ơA<C6A1> w<EFBFBD> ] 1<> F<EFBFBD> ƥ<EFBFBD> <C6A5> o<EFBFBD> ͮ<EFBFBD> INCR
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Middleware <20> ˬd<CBAC> G`token.auth_gen >= redis.auth_gen`<60> A<EFBFBD> _<EFBFBD> h 401<30> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
> **<2A> w<EFBFBD> M<EFBFBD> <4D> **<2A> GUserRole <20> <> <EFBFBD> <EFBFBD> /<2F> M<EFBFBD> P<EFBFBD> B<EFBFBD> ~<7E> <> Group <20> M<EFBFBD> g<EFBFBD> ɭP<C9AD> <50> user role <20> ܧ<EFBFBD> <20> <> **`INCR auth_gen`**<2A> ]<5D> <> <EFBFBD> ı j<C4B1> <6A> <EFBFBD> <EFBFBD> <EFBFBD> s<EFBFBD> A<EFBFBD> ϥΪ̻ݭ<CCBB> <DDAD> s exchange/refresh <20> <> <EFBFBD> o<EFBFBD> s auth_gen<65> ^<5E> C
2026-05-19 17:04:26 +00:00
>
2026-05-20 07:01:08 +00:00
> RolePermission <20> ܧ<DCA7> <F3A4A3A7> ܡu<DCA1> ϥΪ̦<CEAA> <CCA6> <EFBFBD> <EFBFBD> Ǩ<EFBFBD> <C7A8> <EFBFBD> <EFBFBD> v<EFBFBD> A<EFBFBD> u<EFBFBD> <75> `LoadPolicy(tenant_id)` + <20> v<EFBFBD> <76> <EFBFBD> ֨<EFBFBD> <D6A8> <EFBFBD> <EFBFBD> ġF<C4A1> Y<EFBFBD> <59> <EFBFBD> ӧ令<D3A7> <EFA6A8> <EFBFBD> <EFBFBD> <EFBFBD> H<EFBFBD> <48> JWT <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> /<2F> v<EFBFBD> <76> <EFBFBD> ַӡA<D3A1> ~<7E> ݭn<DDAD> P<EFBFBD> B `INCR auth_gen`<60> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> JWT <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> permission<6F> ]<5D> קK token <20> L<EFBFBD> j<EFBFBD> ^<5E> F<EFBFBD> <46> <EFBFBD> q<EFBFBD> <71> <EFBFBD> ĥ<EFBFBD> `auth_gen`<60> A<EFBFBD> 榸<EFBFBD> n<EFBFBD> X<EFBFBD> <58> jti <20> ¦W<C2A6> <57> <EFBFBD> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 4.6 Middleware <20> ˬd<CBAC> <64> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
0. Platform Admin allowlist <20> R<EFBFBD> <52> <EFBFBD> ]platform tenant + platform_super_admin role <20> <> break-glass UID<49> ^
<20> <> audit.LogPlatformBypass <20> <> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
1. go-zero JWT <20> <> ñ + exp
2. typ == "access"<22> ]<5D> <> <EFBFBD> O<EFBFBD> @ API<50> ^
2026-05-19 13:56:59 +00:00
3. NOT EXISTS auth:jwt:bl:{jti}
4. claims.auth_gen >= redis auth:gen:{tenant}:{uid}
2026-05-20 07:01:08 +00:00
- redis key <20> <> <EFBFBD> s<EFBFBD> b <20> <> <20> <> <EFBFBD> <EFBFBD> 0
- ñ<> o token <20> <> claims.auth_gen = redis.GET <20> <> 0
5. <20> `<60> J context<78> Gtenant_id, uid<69> ]role keys <20> ѤU<D1A4> @<40> h CasbinRBACMiddleware <20> q cache <20> <> <EFBFBD> J<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```
---
2026-05-20 07:01:08 +00:00
## 5. member <20> Ҳ<EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> <EFBFBD> <EFBFBD> |<7C> G`internal/model/member/`
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 5.1 ¾<> d
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
- <20> |<7C> <> Profile CRUD<55> ]tenant-scoped<65> ^
- Identity <20> M<EFBFBD> g<EFBFBD> ]`zitadel_sub` ? `uid` <EFBFBD> ^
- Tenant metadata <20> P LDAP <20> P<EFBFBD> B<EFBFBD> ]<5D> w
- UID <20> <> <EFBFBD> ͡]<5D> iŪ<69> 榡<EFBFBD> ^
- SCIM <20> ~<7E> ȼg<C8BC> J<EFBFBD> ]SCIM `id` / Gateway UID + <20> Ȥ<EFBFBD> <C8A4> <EFBFBD> `externalId` <EFBFBD> ^
- Directory Sync Worker<65> ]AD + OpenLDAP<41> ^
- <20> |<7C> <> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> ]active / suspended / deleted<65> ^<5E> <> <20> q<EFBFBD> <71> auth <20> M<EFBFBD> P token
- **<2A> ~<7E> ȯ<EFBFBD> <C8AF> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> Gbusiness email / phone <20> j<EFBFBD> w + OTP <20> ۰ e
- **Step-up MFA OTP <20> <> <EFBFBD> <EFBFBD> **<2A> ]<5D> f<EFBFBD> t auth <20> Ҳ<EFBFBD> ñ step_up_token<65> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 5.2 UseCase <20> <> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
> **<2A> ]<5D> p<EFBFBD> <70> <EFBFBD> h<EFBFBD> ]<5D> I<EFBFBD> <49> model.md<6D> ^**<2A> G<EFBFBD> C<EFBFBD> <43> UseCase <20> O**<2A> <> <EFBFBD> l<EFBFBD> ~<7E> Ⱦާ@**<2A> A**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> e<EFBFBD> <65> <EFBFBD> B<EFBFBD> J<EFBFBD> s<EFBFBD> b**<2A> C<EFBFBD> y<EFBFBD> {<7B> s<EFBFBD> ơ]<5D> p<EFBFBD> u<EFBFBD> <75> <EFBFBD> U <20> <> <20> H<EFBFBD> <48> <EFBFBD> ҫH <20> <> <20> ҥΡ v<CEA1> ^<5E> <> **logic <20> h**<2A> Φh<CEA6> <68> UseCase <20> <> <EFBFBD> ˡF<CBA1> <46> <EFBFBD> h<EFBFBD> u<EFBFBD> t<EFBFBD> d<EFBFBD> <64> <EFBFBD> @<40> ʧ@ + <20> Ƨ @<40> Ρ C
2026-05-19 17:04:26 +00:00
>
2026-05-20 07:01:08 +00:00
> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> h<EFBFBD> G
> 1. **Atomic primitives**<2A> G<EFBFBD> º骺<C2BA> <E9AABA> <EFBFBD> @<40> ʧ@<40> ]<5D> <> member<65> B<EFBFBD> <42> OTP<54> B<EFBFBD> <42> OTP<54> B<EFBFBD> H notification<6F> ^<5E> CLogic <20> i<EFBFBD> <69> <EFBFBD> N<EFBFBD> զ X<D5A6> A<EFBFBD> <41> <EFBFBD> y<EFBFBD> {<7B> @<40> Ρ C
2026-05-20 13:03:59 +00:00
> 2. ~~**Composite**~~<7E> G<EFBFBD> 쥻<EFBFBD> ]<5D> Q<EFBFBD> <51> <EFBFBD> u<EFBFBD> <75> <EFBFBD> X<EFBFBD> <58> atomic <20> w<EFBFBD> <77> <EFBFBD> զ n<D5A6> <6E> <EFBFBD> ֱ<EFBFBD> <D6B1> զ X<D5A6> v**<2A> w<EFBFBD> o<EFBFBD> <6F> **<2A> C
> - <20> P [model.md <20> <> 6.1](./model.md) <20> <> IJ<EFBFBD> Gusecase <20> <> <EFBFBD> <EFBFBD> <EFBFBD> T<EFBFBD> <EFBFBD> I<EFBFBD> s<EFBFBD> C
> - <20> ثe<D8AB> <65> <EFBFBD> @<40> u<EFBFBD> <75> <EFBFBD> <EFBFBD> atomic<69> ]`OTPUseCase`<60> B`TOTPUseCase`<60> B`ProfileUseCase` <20> <> <EFBFBD> ^<5E> A<EFBFBD> h<EFBFBD> B<EFBFBD> J<EFBFBD> y<EFBFBD> {<7B> ]<5D> p verify-email = `OTP.Generate` <20> <> `Notifier.Send` <20> <> `Profile.SetBusinessEmailVerified`<60> ^<5E> @<40> ߦb **logic <20> h**<2A> s<EFBFBD> ơAlogic handler <20> ۤv<DBA4> <76> <EFBFBD> <EFBFBD> <EFBFBD> h<EFBFBD> <68> usecase interface<63> C
> - <20> U<EFBFBD> <55> <EFBFBD> <EFBFBD> 5.2.2 <20> `<60> O<EFBFBD> d `VerificationUseCase` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> w<EFBFBD> q<EFBFBD> Ȭ<EFBFBD> <C8AC> u**<2A> <EFBFBD> <DEBF> y<EFBFBD> <79> <EFBFBD> y<EFBFBD> z<EFBFBD> Ѧ<EFBFBD> **<2A> v<EFBFBD> A<EFBFBD> <41> <EFBFBD> |<7C> b `domain/usecase/` <20> X<EFBFBD> {<7B> C
2026-05-19 17:04:26 +00:00
>
2026-05-20 07:01:08 +00:00
> <EFBFBD> ~<7E> <> <EFBFBD> <EFBFBD> <DEBF> ]API<50> Bhandler<65> B<EFBFBD> y<EFBFBD> {<7B> s<EFBFBD> ơ^<5E> ثe**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> @**<2A> F<EFBFBD> <46> <EFBFBD> T<EFBFBD> Ƥ<EFBFBD> <C6A4> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> C
2026-05-19 17:04:26 +00:00
#### 5.2.1 Atomic primitives
2026-05-19 13:56:59 +00:00
2026-05-19 17:04:26 +00:00
```go
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
// Profile<6C> GŪ<47> g member <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> <EFBFBD> t<EFBFBD> ҥ<EFBFBD> / <20> <> <EFBFBD> v<EFBFBD> <76> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> ܾE<DCBE> ^
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 13:56:59 +00:00
type ProfileUseCase interface {
GetByUID(ctx context.Context, req *GetMemberRequest) (*MemberDTO, error)
Update(ctx context.Context, req *UpdateMemberRequest) (*MemberDTO, error)
List(ctx context.Context, req *ListMembersRequest) (*ListMembersResponse, error)
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
// <20> ~<7E> <> email / phone <20> X<EFBFBD> Ф<EFBFBD> <D0A4> <EFBFBD> <EFBFBD> ]<5D> Q Verification <20> Υ ~<7E> <> <EFBFBD> y<EFBFBD> {<7B> ϥΡ ^
2026-05-19 17:04:26 +00:00
SetBusinessEmailVerified(ctx context.Context, tenantID, uid, email string) error
SetBusinessPhoneVerified(ctx context.Context, tenantID, uid, phone string) error
}
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
// Lifecycle<6C> G<EFBFBD> <47> <EFBFBD> A<EFBFBD> ܾE<DCBE> <45> <EFBFBD> <EFBFBD> <EFBFBD> @<40> ʧ@<40> F<EFBFBD> <46> <EFBFBD> H<EFBFBD> H<EFBFBD> B<EFBFBD> <42> ñ token
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 17:04:26 +00:00
type LifecycleUseCase interface {
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> x<EFBFBD> <78> <EFBFBD> ͵<EFBFBD> <CDB5> U<EFBFBD> G<EFBFBD> إ<EFBFBD> unverified member<65> ]<5D> <> <EFBFBD> H OTP<54> A<EFBFBD> <41> <EFBFBD> o token<65> ^
2026-05-19 17:04:26 +00:00
CreateUnverified(ctx context.Context, req *CreatePlatformMemberRequest) (*MemberDTO, error)
2026-05-20 07:01:08 +00:00
// <20> ҥΡ Gunverified <20> <> active<76> Fcaller <20> <> <EFBFBD> <EFBFBD> <EFBFBD> T<EFBFBD> O<EFBFBD> Ҧ<EFBFBD> <D2A6> e<EFBFBD> m<EFBFBD> <6D> <EFBFBD> Ҥw<D2A4> q<EFBFBD> L
2026-05-19 17:04:26 +00:00
Activate(ctx context.Context, tenantID, uid string) error
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> v<EFBFBD> Gactive <20> <> suspended<65> F<EFBFBD> <46> <EFBFBD> M token<65> ]<5D> M token <20> <> auth <20> Ҳհ <D2B2> <D5B0> ^
2026-05-19 17:04:26 +00:00
Suspend(ctx context.Context, tenantID, uid, reason string) error
2026-05-20 07:01:08 +00:00
// <20> _<EFBFBD> v<EFBFBD> Gsuspended <20> <> active
2026-05-19 17:04:26 +00:00
Reactivate(ctx context.Context, tenantID, uid string) error
2026-05-20 07:01:08 +00:00
// <20> n<EFBFBD> R<EFBFBD> Gactive|suspended <20> <> deleted<65> ]<5D> <> <EFBFBD> |<7C> ߨ<EFBFBD> <DFA8> ΦW<CEA6> ơF30 <20> ѫ<EFBFBD> <D1AB> <EFBFBD> worker <20> B<EFBFBD> z <20> <> 5.7<EFBFBD> ^
2026-05-19 17:04:26 +00:00
SoftDelete(ctx context.Context, tenantID, uid string) error
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> <EFBFBD> ҥε<D2A5> <CEB5> U<EFBFBD> ]<5D> O<EFBFBD> ɲM<C9B2> z<EFBFBD> F<EFBFBD> u<EFBFBD> <75> <EFBFBD> <EFBFBD> unverified <20> Ρ ^
2026-05-19 17:04:26 +00:00
AbortPending(ctx context.Context, tenantID, uid string) error
}
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
// Provisioning<6E> G<EFBFBD> ~<7E> <> <EFBFBD> ӷ<EFBFBD> <20> <> Gateway member <20> <> JIT / sync upsert
// <20> C<EFBFBD> Өӷ<D3A8> <D3B7> W<EFBFBD> ߤ@<40> Ӱʧ@<40> Femail <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ӷ<EFBFBD> IdP <20> w<EFBFBD> <77> <EFBFBD> ҡA<D2A1> <41> <EFBFBD> A<EFBFBD> <41> OTP
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 17:04:26 +00:00
type ProvisioningUseCase interface {
2026-05-20 07:01:08 +00:00
// ZITADEL OIDC token exchange<67> G<EFBFBD> <47> id_token claims <20> W upsert<72> ]B2C / Social IdP<64> ^
2026-05-19 17:04:26 +00:00
EnsureFromOIDC(ctx context.Context, req *EnsureFromOIDCRequest) (*MemberDTO, error)
2026-05-20 07:01:08 +00:00
// ZITADEL LDAP IdP <20> n<EFBFBD> J<EFBFBD> <4A> JIT<49> F<EFBFBD> <46> Directory Sync worker <20> <> <EFBFBD> e
2026-05-19 17:04:26 +00:00
EnsureFromLDAP(ctx context.Context, req *EnsureFromLDAPRequest) (*MemberDTO, error)
// SCIM Create / Update User
EnsureFromSCIM(ctx context.Context, req *EnsureFromSCIMRequest) (*MemberDTO, error)
2026-05-19 13:56:59 +00:00
}
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
// OTP<54> Gatomic<69> Bpurpose-agnostic <20> @<40> <> <EFBFBD> ʱK<CAB1> X
// <20> <> <EFBFBD> H<EFBFBD> H<EFBFBD> B<EFBFBD> <42> <EFBFBD> <EFBFBD> <EFBFBD> s member<65> Fcaller <20> <> code <20> <> <EFBFBD> ۦ<EFBFBD> <DBA6> z<EFBFBD> L NotifierUseCase <20> 뻼
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 17:04:26 +00:00
type OTPUseCase interface {
2026-05-20 07:01:08 +00:00
// <20> ͦ<EFBFBD> <CDA6> Gbcrypt <20> s redis<69> A<EFBFBD> ^ challenge_id + <20> <> <EFBFBD> X code<64> ]<5D> @<40> <> <EFBFBD> ʦ^<5E> ǡ^
2026-05-19 17:04:26 +00:00
Generate(ctx context.Context, req *GenerateOTPRequest) (*OTPChallengeDTO, error)
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> ҡG<D2A1> <47> <EFBFBD> \<5C> h invalidate<74> Fpurpose <20> <> <EFBFBD> <EFBFBD> <EFBFBD> P challenge <20> إ߮ɤ@<40> P
2026-05-19 17:04:26 +00:00
Verify(ctx context.Context, req *VerifyOTPRequest) error
2026-05-20 07:01:08 +00:00
// <20> D<EFBFBD> ʥ<EFBFBD> <CAA5> ġ]<5D> <> challenge / <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> U<EFBFBD> ^
2026-05-19 17:04:26 +00:00
Invalidate(ctx context.Context, tenantID, challengeID string) error
2026-05-19 13:56:59 +00:00
}
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
// TOTP<54> ]Authenticator App<70> ^<5E> G<EFBFBD> <47> <20> <> 5.8
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 17:04:26 +00:00
type TOTPUseCase interface {
StartEnroll(ctx context.Context, tenantID, uid string) (*EnrollStartDTO, error)
ConfirmEnroll(ctx context.Context, tenantID, uid, code string) (backupCodes []string, err error)
VerifyCode(ctx context.Context, tenantID, uid, code string) error
Disable(ctx context.Context, tenantID, uid string) error
RegenerateBackupCodes(ctx context.Context, tenantID, uid string) ([]string, error)
}
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 17:04:26 +00:00
// Tenant
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 13:56:59 +00:00
type TenantUseCase interface {
Create(ctx context.Context, req *CreateTenantRequest) (*TenantDTO, error)
ResolveBySlug(ctx context.Context, slug string) (*TenantDTO, error)
ConfigureLDAP(ctx context.Context, req *ConfigureLDAPRequest) error
}
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 17:04:26 +00:00
// SCIM Resource handlers
2026-05-20 07:01:08 +00:00
// <20> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w<EFBFBD> w
2026-05-19 13:56:59 +00:00
type ScimUseCase interface {
CreateUser(ctx context.Context, req *ScimCreateUserRequest) (*ScimUserDTO, error)
GetUser(ctx context.Context, req *ScimGetUserRequest) (*ScimUserDTO, error)
PatchUser(ctx context.Context, req *ScimPatchUserRequest) (*ScimUserDTO, error)
DeleteUser(ctx context.Context, req *ScimDeleteUserRequest) error
PatchGroup(ctx context.Context, req *ScimPatchGroupRequest) error
}
type DirectorySyncUseCase interface {
SyncTenant(ctx context.Context, tenantID string) (*SyncResult, error)
}
```
2026-05-20 07:01:08 +00:00
#### 5.2.2 Composite<74> ]<5D> i<EFBFBD> <69> <EFBFBD> F<EFBFBD> `<60> βզ X<D5A6> <58> <EFBFBD> K<EFBFBD> Q<EFBFBD> ]<5D> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> Composite <20> <> <EFBFBD> <EFBFBD> <EFBFBD> u<EFBFBD> I<EFBFBD> s Atomic primitives + library / notifier<65> A**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> i<F3A4A3A5> <69> atomic <20> <> <EFBFBD> X<EFBFBD> <58> <EFBFBD> Ƨ @<40> <> **<2A> C
> Logic <20> i<EFBFBD> <69> <EFBFBD> ܥ<EFBFBD> composite<74> ]²<> 污<EFBFBD> p<EFBFBD> ^<5E> Ϊ<EFBFBD> <CEAA> <EFBFBD> <EFBFBD> <EFBFBD> atomic<69> ]<5D> S<EFBFBD> <53> <EFBFBD> ݨD<DDA8> ^<5E> C
2026-05-19 17:04:26 +00:00
```go
2026-05-20 07:01:08 +00:00
// <20> ~<7E> <> email / phone <20> <> <EFBFBD> <EFBFBD> = OTP.Generate + Notifier.Send + Profile.SetXxxVerified
2026-05-19 17:04:26 +00:00
type VerificationUseCase interface {
StartEmailVerify(ctx context.Context, tenantID, uid, target string) (*OTPChallengeDTO, error)
ConfirmEmailVerify(ctx context.Context, tenantID, uid, challengeID, code string) error
StartPhoneVerify(ctx context.Context, tenantID, uid, target string) (*OTPChallengeDTO, error)
ConfirmPhoneVerify(ctx context.Context, tenantID, uid, challengeID, code string) error
}
2026-05-20 07:01:08 +00:00
// Step-up = (TOTP.VerifyCode <20> <> OTP.Generate+Notifier.Send/OTP.Verify) + auth.StepUpToken.Issue
2026-05-19 17:04:26 +00:00
type StepUpUseCase interface {
Start(ctx context.Context, tenantID, uid string, req *StepUpStartRequest) (*StepUpChallengeDTO, error)
Confirm(ctx context.Context, tenantID, uid string, req *StepUpConfirmRequest) (stepUpToken string, err error)
}
```
2026-05-20 07:01:08 +00:00
#### 5.2.3 Request / DTO <20> <> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
```go
// Provisioning
type EnsureFromOIDCRequest struct {
TenantID string
ZitadelSub string
Email string
2026-05-20 07:01:08 +00:00
EmailVerified bool // <20> Ӧ<EFBFBD> id_token claim<69> FOIDC <20> q<EFBFBD> ` true
2026-05-19 17:04:26 +00:00
DisplayName string
Locale string
RawClaims map[string]any
}
type EnsureFromLDAPRequest struct {
TenantID string
ExternalID string // objectGUID / entryUUID
LDAPDN string
Username string
Email string
DisplayName string
Groups []string
Source enum.RoleSource // ldap_sync | ldap_jit
}
type EnsureFromSCIMRequest struct {
TenantID string
2026-05-20 07:01:08 +00:00
ExternalID string // SCIM externalId<49> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> UID<49> ^
2026-05-19 17:04:26 +00:00
UserName string
Email string
DisplayName string
Active bool
RawPayload map[string]any
}
// Platform registration
type CreatePlatformMemberRequest struct {
TenantID string
Email string
2026-05-20 07:01:08 +00:00
PasswordHash string // <20> Y<EFBFBD> ϥ<EFBFBD> ZITADEL local user<65> A<EFBFBD> d<EFBFBD> š]<5D> <> ZITADEL <20> ޡ^
2026-05-19 17:04:26 +00:00
DisplayName string
Language string
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> |<7C> ߧY active<76> F<EFBFBD> s<EFBFBD> <73> member.status = unverified
2026-05-19 17:04:26 +00:00
}
// OTP
type GenerateOTPRequest struct {
TenantID string
Purpose enum.OTPPurpose // registration_email | business_email | business_phone | step_up | password_reset | ...
2026-05-20 07:01:08 +00:00
Identifier string // <20> q<EFBFBD> `<60> O uid<69> F<EFBFBD> <46> <EFBFBD> U<EFBFBD> <55> uid <20> |<7C> <> <EFBFBD> s<EFBFBD> b<EFBFBD> ɥi<C9A5> <69> hash(email)
Length int // 0 = <20> <> config <20> w<EFBFBD> ]<5D> ]6<> ^
TTLSeconds int // 0 = <20> <> config <20> w<EFBFBD> ]<5D> ]300<30> ^
2026-05-19 17:04:26 +00:00
}
type OTPChallengeDTO struct {
ChallengeID string
2026-05-20 07:01:08 +00:00
Code string // <20> <> Generate <20> ɦ^<5E> Ǥ@<40> <> <EFBFBD> ]<5D> <> <EFBFBD> X<EFBFBD> ^<5E> Fcaller <20> ۭt<DBAD> 뻼
2026-05-19 17:04:26 +00:00
ExpiresIn int
}
type VerifyOTPRequest struct {
TenantID string
ChallengeID string
Code string
2026-05-20 07:01:08 +00:00
Purpose enum.OTPPurpose // <20> <> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> <41> challenge <20> Q<EFBFBD> ɥΨ<C9A5> <CEA8> <EFBFBD> <EFBFBD> L<EFBFBD> γ ~
2026-05-19 17:04:26 +00:00
}
// Step-up
type StepUpStartRequest struct {
TenantID string
UID string
Action enum.StepUpAction
2026-05-20 07:01:08 +00:00
PreferChannel enum.Channel // <20> i<EFBFBD> <69> <EFBFBD> Gtotp | sms | email<69> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> w<EFBFBD> h<EFBFBD> <68> <20> <> 5.6 <20> u<EFBFBD> <75> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
}
type StepUpChallengeDTO struct {
2026-05-20 07:01:08 +00:00
ChallengeID string // TOTP <20> L challenge_id <20> ]<5D> i<EFBFBD> ^<5E> T<EFBFBD> w<EFBFBD> ȡFConfirm <20> ɤ<EFBFBD> <C9A4> |<7C> h<EFBFBD> <68> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
Channel enum.Channel
ExpiresIn int
}
type StepUpConfirmRequest struct {
TenantID string
UID string
ChallengeID string
Code string
Action enum.StepUpAction
}
```
2026-05-20 07:01:08 +00:00
#### 5.2.4 Enum <20> <> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
```go
// member/enum/otp_purpose.go
type OTPPurpose string
const (
OTPPurposeRegistrationEmail OTPPurpose = "registration_email"
OTPPurposeBusinessEmail OTPPurpose = "business_email"
OTPPurposeBusinessPhone OTPPurpose = "business_phone"
OTPPurposeStepUp OTPPurpose = "step_up"
2026-05-20 07:01:08 +00:00
OTPPurposePasswordReset OTPPurpose = "password_reset" // <20> w<EFBFBD> d
2026-05-19 17:04:26 +00:00
)
2026-05-20 07:01:08 +00:00
// auth/enum/step_up_action.go<67> ]<5D> w<EFBFBD> s<EFBFBD> b<EFBFBD> <62> <20> <> 5.6<EFBFBD> A<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ŧi<EFBFBD> ^
2026-05-19 17:04:26 +00:00
type StepUpAction string
const (
StepUpChangeBusinessEmail StepUpAction = "change_business_email"
StepUpChangeBusinessPhone StepUpAction = "change_business_phone"
StepUpDeleteMember StepUpAction = "delete_member"
StepUpTenantAdminForceStatus StepUpAction = "tenant_admin_force_status"
StepUpRevokeAllSessions StepUpAction = "revoke_all_sessions"
StepUpDisableTOTP StepUpAction = "disable_totp"
)
```
2026-05-20 07:01:08 +00:00
### 5.3 <20> |<7C> <> <EFBFBD> ͩR<CDA9> g<EFBFBD> <67> <EFBFBD> <EFBFBD> <EFBFBD> A
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> A | <20> y<EFBFBD> N | <20> Ƨ @<40> <> |
2026-05-19 13:56:59 +00:00
|------|------|--------|
2026-05-20 07:01:08 +00:00
| `unverified` | ** <EFBFBD> ȥ<EFBFBD> <EFBFBD> x<EFBFBD> <EFBFBD> <EFBFBD> ͵<EFBFBD> <EFBFBD> U**<2A> |<7C> X<EFBFBD> {<7B> Gmember <20> w<EFBFBD> إߡA<DFA1> <41> <EFBFBD> <EFBFBD> <EFBFBD> U email <20> |<7C> <> <EFBFBD> q<EFBFBD> L OTP <20> <> <EFBFBD> <EFBFBD> | <20> <> ñ token<65> B<EFBFBD> <42> <EFBFBD> i<EFBFBD> n<EFBFBD> J<EFBFBD> F<EFBFBD> O<EFBFBD> <4F> <EFBFBD> <EFBFBD> cron `AbortPending` <20> M<EFBFBD> z |
| `active` | <20> <> <EFBFBD> `<60> ϥ<EFBFBD> | <20> X |
| `suspended` | <20> <> <EFBFBD> v<EFBFBD> ]<5D> z<DEB2> <7A> <EFBFBD> ާ@ / <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ^ | `auth.RevokeAllForUser` <EFBFBD> ]`INCR auth_gen`<60> ^ |
| `deleted` | <20> n<EFBFBD> R<EFBFBD> <52> | <20> M cache<68> B<EFBFBD> M<EFBFBD> P token<65> BZITADEL disable<6C> F30 <20> ѫ<EFBFBD> <D1AB> ΦW<CEA6> ơ]<5D> <> 5.7<EFBFBD> ^ |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> <EFBFBD> Ӧ<EFBFBD> OIDC / LDAP / SCIM <20> <> member **<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> ج<EFBFBD> `active`**<2A> ]email <20> Ѩӷ<D1A8> IdP <20> w<EFBFBD> <77> <EFBFBD> ҡ^<5E> F<EFBFBD> u<EFBFBD> <75> platform-native <20> <> <EFBFBD> U<EFBFBD> |<7C> g<EFBFBD> L `unverified`<60> C
> <EFBFBD> ~<7E> <> email / phone <20> <> <EFBFBD> ҥH<D2A5> W<EFBFBD> ߺ X<DFBA> С ]`BusinessEmailVerified` / `BusinessPhoneVerified`<60> ^<5E> <> <EFBFBD> ܡA<DCA1> P<EFBFBD> ͩR<CDA9> g<EFBFBD> <67> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> ѽ<EFBFBD> <D1BD> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### Member <20> <> <EFBFBD> <EFBFBD> Source of Truth<74> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> O | <20> d<EFBFBD> <64> | SoT | <20> 欰 |
2026-05-19 17:04:26 +00:00
|---------|------|-----|------|
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ѧO | `zitadel_sub` <EFBFBD> B`ZitadelEmail`<60> B`DisplayName`<60> ]IdP<64> ^<5E> BZITADEL `status` | **ZITADEL** | <20> C<EFBFBD> <43> token exchange / webhook <20> P<EFBFBD> B<EFBFBD> FGateway <20> <> <EFBFBD> i<EFBFBD> <69> <EFBFBD> g |
| <20> ~<7E> ȸ<EFBFBD> <C8B8> <EFBFBD> | `BusinessEmail/Phone(+Verified)` <EFBFBD> B`Language`<60> B`Currency`<60> B`Avatar`<60> B`Preferences` | **Gateway** | <20> ~<7E> <> API <20> g<EFBFBD> F<EFBFBD> <46> <EFBFBD> ^<5E> <> ZITADEL |
| Provisioning <20> ӷ<EFBFBD> | `external_id` <EFBFBD> B`ldap_dn`<60> BSCIM <20> s<EFBFBD> զ <EFBFBD> <D5A6> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> | ** <EFBFBD> ӷ<EFBFBD> <EFBFBD> t<EFBFBD> <EFBFBD> **<2A> ]LDAP/SCIM<49> ^ | sync replace<63> FGateway <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> s<EFBFBD> <73> |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> <EFBFBD> <EFBFBD> <EFBFBD> ס G`Member.Origin` <20> Х D<D0A5> ӷ<EFBFBD> <D3B7> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> uProvisioning<6E> v<EFBFBD> <76> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> O<EFBFBD> <4F> <EFBFBD> i<EFBFBD> g<EFBFBD> d<EFBFBD> <64> <EFBFBD> CGateway UI <20> <> <EFBFBD> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> û<EFBFBD> <C3BB> i<EFBFBD> <69> <EFBFBD> F<EFBFBD> 鶴<EFBFBD> <EFA8AD> /Provisioning <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ݨ<EFBFBD> <DDA8> ӷ<EFBFBD> <D3B7> t<EFBFBD> Ρ C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 5.4 <20> ~<7E> ȯ<EFBFBD> <C8AF> <EFBFBD> <EFBFBD> Ҽҫ<D2BC> <D2AB> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
```go
2026-05-20 07:01:08 +00:00
// Member <20> J<EFBFBD> <4A> + <20> <> <EFBFBD> `<60> s<EFBFBD> W<EFBFBD> <57> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
type Member struct {
TenantID string
UID string
2026-05-20 07:01:08 +00:00
ZitadelUserID string // ZITADEL sub<75> ]OIDC / LDAP IdP / platform local user <20> <> <EFBFBD> |<7C> <> <EFBFBD> ^
ZitadelEmail string // <20> ӷ<EFBFBD> IdP <20> <> <EFBFBD> Ѫ<EFBFBD> <D1AA> n<EFBFBD> J email
2026-05-19 17:04:26 +00:00
DisplayName string
Avatar string
Phone string
Language string
Currency string
Status enum.MemberStatus // unverified | active | suspended | deleted
Origin enum.MemberOrigin // platform_native | oidc | ldap | scim
2026-05-20 07:01:08 +00:00
PasswordHash string // <20> <> <EFBFBD> x<EFBFBD> <78> <EFBFBD> ͥB<CDA5> <42> <EFBFBD> <EFBFBD> ZITADEL local user <20> ɤ~<7E> <> <EFBFBD> F<EFBFBD> <46> <EFBFBD> l<EFBFBD> d<EFBFBD> <64>
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
BusinessEmail string // <20> ~<7E> <> email<69> ]<5D> i<EFBFBD> P ZitadelEmail <20> <> <EFBFBD> P<EFBFBD> ^
2026-05-19 17:04:26 +00:00
BusinessEmailVerified bool
BusinessEmailVerifiedAt int64
BusinessPhone string
BusinessPhoneVerified bool
BusinessPhoneVerifiedAt int64
TOTPEnrolled bool
TOTPSecretCipher string
TOTPEnrolledAt int64
TOTPBackupCodesHash []string
CreateAt int64
UpdateAt int64
2026-05-20 07:01:08 +00:00
DeletedAt int64 // soft delete <20> ɶ<EFBFBD>
AnonymizedAt int64 // <20> ΦW<CEA6> Ʈɶ<C6AE>
2026-05-19 17:04:26 +00:00
}
```
2026-05-20 07:01:08 +00:00
> **Origin** <20> <> <EFBFBD> ȡG
> - `platform_native`<60> GGateway <20> <> <EFBFBD> x<EFBFBD> <78> <EFBFBD> ͵<EFBFBD> <CDB5> U<EFBFBD> ]<5D> f<EFBFBD> t ZITADEL local user <20> <> Gateway <20> ۺޱK<DEB1> X<EFBFBD> ^
> - `oidc`<60> GSocial / ZITADEL Hosted UI <20> <> IdP <20> Ӫ<EFBFBD>
> - `ldap`<60> G<EFBFBD> z<EFBFBD> L ZITADEL LDAP IdP <20> <> Directory Sync
> - `scim`<60> GHR / Entra / Okta <20> <> <EFBFBD> e
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> `Member.Origin` <20> M<EFBFBD> w Profile <20> <> <EFBFBD> <EFBFBD> UI <20> i<EFBFBD> g<EFBFBD> d<EFBFBD> <64> <EFBFBD> G
> - `zitadel_local`<60> G<EFBFBD> <47> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]IdP email/name<6D> ^<5E> <> Ū<EFBFBD> A<EFBFBD> ݨ<EFBFBD> ZITADEL UI <20> <> <EFBFBD> F<EFBFBD> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> i<EFBFBD> g
> - `ldap`<60> G<EFBFBD> <47> <EFBFBD> <EFBFBD> + provisioning <20> <> <EFBFBD> <EFBFBD> <EFBFBD> Ұ<EFBFBD> Ū<EFBFBD> ]<5D> <> Directory Sync <20> <> <EFBFBD> @<40> ^<5E> F<EFBFBD> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> i<EFBFBD> g
> - `scim`<60> G<EFBFBD> <47> <EFBFBD> <EFBFBD> + provisioning <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> SCIM Provider <20> <> <EFBFBD> e<EFBFBD> A<EFBFBD> <41> Ū<EFBFBD> F<EFBFBD> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> i<EFBFBD> g
2026-05-19 17:04:26 +00:00
>
2026-05-20 07:01:08 +00:00
> `UserRole.Source` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> `manual / zitadel / ldap / scim`<60> A<EFBFBD> v<EFBFBD> T sync replace <20> d<EFBFBD> <64> <EFBFBD> ]<5D> <> <20> <> 6.10<EFBFBD> ^<5E> C<EFBFBD> <43> <EFBFBD> ̦U<CCA6> q<EFBFBD> <71> ¾<EFBFBD> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> | <20> ӷ<EFBFBD> | <20> γ ~ |
2026-05-19 17:04:26 +00:00
|------|------|------|
2026-05-20 07:01:08 +00:00
| `ZitadelEmail` <EFBFBD> ]<5D> J<EFBFBD> <4A> <EFBFBD> ^ | OIDC claim | <20> n<EFBFBD> J<EFBFBD> b<EFBFBD> <62> <EFBFBD> ѧO<D1A7> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> ~<7E> Ȧu<C8A6> <75> |
| `BusinessEmail` | <20> ~<7E> <> API <20> j<EFBFBD> w + OTP | <20> ~<7E> ȳq<C8B3> <71> <EFBFBD> B<EFBFBD> ~<7E> Ȧu<C8A6> <75> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> |
| `BusinessPhone` | <20> ~<7E> <> API <20> j<EFBFBD> w + OTP | SMS <20> q<EFBFBD> <71> <EFBFBD> BStep-up MFA <20> q<EFBFBD> D |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
**Verification Challenge<67> ]<5D> <> <EFBFBD> J Mongo<67> A<EFBFBD> Ȧs Redis<69> ATTL 5min<69> ^<5E> G**
2026-05-19 17:04:26 +00:00
```go
type VerificationChallenge struct {
TenantID string
UID string
Kind enum.VerifyKind // email | phone | step_up
2026-05-20 07:01:08 +00:00
Target string // email/phone <20> ت<EFBFBD> <D8AA> a<EFBFBD> Fstep_up <20> <> action
2026-05-19 17:04:26 +00:00
CodeHash string // bcrypt(otp)
2026-05-20 07:01:08 +00:00
AttemptCnt int // <20> <> <EFBFBD> Ѧ<EFBFBD> <D1A6> ơA<C6A1> W<EFBFBD> L MaxAttempts <20> <> <20> <>
2026-05-19 17:04:26 +00:00
ExpireAt int64 // epoch ms
CreateAt int64
}
```
2026-05-20 07:01:08 +00:00
### 5.5 OTP <20> 뻼<EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> G<EFBFBD> z<EFBFBD> L Notification Module<6C> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> ~<7E> <> / step-up OTP ** <EFBFBD> @<40> ߨ<EFBFBD> ** `notification.NotifierUseCase` <EFBFBD> A**<2A> <> **<2A> b member <20> Ҳժ<D2B2> <D5AA> <EFBFBD> <EFBFBD> <EFBFBD> provider SDK<44> CNotification module <20> Τ @<40> B<EFBFBD> z provider <20> <> <EFBFBD> <EFBFBD> <EFBFBD> B<EFBFBD> ҪO<D2AA> Bidempotency<63> B<EFBFBD> <42> <EFBFBD> ա Baudit<69> ]<5D> <> <20> <> 11<31> ^<5E> C
2026-05-19 17:04:26 +00:00
```go
2026-05-20 07:01:08 +00:00
// member.VerificationUseCase <20> <> <EFBFBD> I<EFBFBD> s
2026-05-19 17:04:26 +00:00
nu.Notifier.Send(ctx, & notification.SendRequest{
TenantID: tenantID,
UID: uid,
Channel: enum.ChannelEmail,
Kind: enum.NotifyVerifyEmail,
Target: targetEmail,
Locale: member.Language,
Data: map[string]any{"code": otp, "expires_in": 300},
2026-05-20 07:01:08 +00:00
IdempotencyKey: challengeID, // <20> P challenge <20> <> <EFBFBD> |<7C> <> <EFBFBD> o
DoNotPersistBody: true, // OTP <20> <> <EFBFBD> J notification.body
2026-05-19 17:04:26 +00:00
Severity: enum.SeverityInfo,
})
```
2026-05-20 07:01:08 +00:00
- **OTP <20> W<EFBFBD> <57> **<2A> G6 <20> <> <EFBFBD> ơBTTL 5min<69> Bbcrypt <20> x<EFBFBD> s<EFBFBD> ]<5D> <> <EFBFBD> s<EFBFBD> <73> <EFBFBD> X<EFBFBD> ^<5E> B<EFBFBD> <42> <EFBFBD> o<EFBFBD> N<EFBFBD> o 60s<30> B<EFBFBD> <42> <EFBFBD> @ challenge <20> <> <EFBFBD> <EFBFBD> 5 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
- **Rate Limit**<2A> G
- `verify:rate:{tenant}:{uid}:{kind}` SETNX TTL=60s<30> ]<5D> <> <EFBFBD> o<EFBFBD> O<EFBFBD> @<40> ^
- `verify:daily:{tenant}:{uid}:{kind}` INCR TTL=24h<34> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> W<EFBFBD> <57> <EFBFBD> A<EFBFBD> w<EFBFBD> ] 10 <20> <> <EFBFBD> ^
- **Audit**<2A> GStart / Confirm <20> i audit log<6F> ]Notification <20> ۤv<DBA4> ]<5D> |<7C> O<EFBFBD> e<EFBFBD> F<EFBFBD> <46> <EFBFBD> A<EFBFBD> A<EFBFBD> <41> <EFBFBD> ̤<EFBFBD> <CCA4> ɡ ^
- **Provider <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> T member <20> Ҳ<EFBFBD> **<2A> G<EFBFBD> <47> SendGrid <20> <> SES<45> BTwilio <20> <> SNS <20> u<EFBFBD> <75> `etc/gateway.yaml` <20> P library <20> <> <EFBFBD> @
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 5.6 Step-up MFA<46> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> G<EFBFBD> ҥΡ ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
**<2A> γ ~**<2A> G<EFBFBD> <47> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> ~<7E> Ⱦާ@<40> e<EFBFBD> <65> <EFBFBD> G<EFBFBD> <47> <EFBFBD> <EFBFBD> <EFBFBD> ҡA<D2A1> P ZITADEL <20> <> <EFBFBD> <EFBFBD> MFA ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> N**<2A> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### <20> <> <EFBFBD> <EFBFBD> <EFBFBD> I Action <20> M<EFBFBD> <4D> <EFBFBD> ]enum<75> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| Action | <20> ؼ<EFBFBD> API |
2026-05-19 17:04:26 +00:00
|--------|---------|
| `change_business_email` | `PATCH /members/me/business-email` |
| `change_business_phone` | `PATCH /members/me/business-phone` |
| `delete_member` | `DELETE /members/me` |
2026-05-20 07:01:08 +00:00
| `tenant_admin_force_status` | `PATCH /members/:uid/status` <EFBFBD> ]<5D> z<DEB2> <7A> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> L<EFBFBD> H<EFBFBD> ^|
2026-05-19 17:04:26 +00:00
| `revoke_all_sessions` | `POST /auth/revoke-all` |
| `disable_totp` | `DELETE /members/me/totp` |
2026-05-20 07:01:08 +00:00
> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> i<EFBFBD> <EFBFBD> tenant <20> z<EFBFBD> L<EFBFBD> ]<5D> w<EFBFBD> [<5B> զ W<D5A6> <57> <EFBFBD> F<EFBFBD> 쪩 platform-wide enum<75> A<EFBFBD> T<EFBFBD> <54> <EFBFBD> <EFBFBD> <EFBFBD> N<EFBFBD> r<EFBFBD> <72> <EFBFBD> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### Step-up <20> q<EFBFBD> D<EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> u<EFBFBD> <EFBFBD> <EFBFBD> ǡG**TOTP > SMS > Email**
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| <20> q<EFBFBD> D | <20> <> <EFBFBD> <EFBFBD> | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> u<EFBFBD> <75> |
2026-05-19 17:04:26 +00:00
|------|------|---------|
2026-05-20 07:01:08 +00:00
| **TOTP** <EFBFBD> ]Google Authenticator<6F> ^ | <20> ϥΪ̤w `enroll_totp` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> 5.8<EFBFBD> ^ | <20> <> <EFBFBD> ̿<EFBFBD> <CCBF> ~<7E> <> provider<65> B<EFBFBD> <42> <EFBFBD> |<7C> Q SIM swap<61> B<EFBFBD> L<EFBFBD> W<EFBFBD> e<EFBFBD> <65> <EFBFBD> <EFBFBD> <EFBFBD> B<EFBFBD> s<EFBFBD> <73> <EFBFBD> <EFBFBD> |
| **SMS** | `BusinessPhoneVerified = true` | <20> <> email <20> Y<EFBFBD> ɡ B<C9A1> <42> <EFBFBD> <EFBFBD> <EFBFBD> Q<EFBFBD> d<EFBFBD> I |
| **Email** | `BusinessEmailVerified = true` | <20> <> <EFBFBD> Ƴq<C6B3> D |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
Start <20> ɥ<EFBFBD> `StepUpUseCase` <20> ̨ϥΪ̪<CEAA> <CCAA> A<EFBFBD> D<EFBFBD> q<EFBFBD> D<EFBFBD> F<EFBFBD> Y<EFBFBD> ϥΪ̭n<CCAD> D<EFBFBD> <44> <EFBFBD> L<EFBFBD> q<EFBFBD> D<EFBFBD> ]<5D> p<EFBFBD> <70> <EFBFBD> Q<EFBFBD> <51> TOTP<54> ^<5E> i<EFBFBD> b request <20> a `prefer_channel` <20> мg<D0BC> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> ݸӳq<D3B3> D<EFBFBD> w<EFBFBD> <77> <EFBFBD> ҡC
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### <20> y<EFBFBD> {
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
1. Client <20> <> POST /auth/step-up/start { action, prefer_channel?: "totp" }
- <20> ѪR<D1AA> ϥΪ̤w<CCA4> i<EFBFBD> γ q<CEB3> D<EFBFBD> F<EFBFBD> D<EFBFBD> <44> <EFBFBD> u<EFBFBD> <75> <EFBFBD> q<EFBFBD> D
- <20> Y<EFBFBD> <59> totp<74> G<EFBFBD> <47> <EFBFBD> H OTP<54> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> ^ challenge_id<69> Fcode <20> ѨϥΪ̱q app <20> <>
- <20> Y<EFBFBD> <59> sms/email<69> G<EFBFBD> ͦ<EFBFBD> 6 <20> X OTP<54> Bbcrypt <20> x<EFBFBD> s<EFBFBD> B<EFBFBD> z<EFBFBD> L NotifierUseCase.Send <20> H<EFBFBD> X
<20> <> { challenge_id, channel: "totp"|"sms"|"email", expires_in: 300 }
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
2. Client <20> <> POST /auth/step-up/confirm { challenge_id, code, action }
- totp<74> Gmember.TOTPUseCase.VerifyCode(uid, code, window=<3D> <> 1)
- sms/email<69> Gbcrypt <20> <> <EFBFBD> <EFBFBD> challenge code<64> F<EFBFBD> <46> <EFBFBD> <EFBFBD> INCR AttemptCnt
- <20> <> <EFBFBD> \ <20> <> auth.StepUpTokenUseCase.Issue(tenant, uid, action) <20> <> <20> u<EFBFBD> <75> JWT
<20> <> { step_up_token, token_type: "step_up", expires_in: 300 }
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
3. Client <20> <> PATCH /members/me/business-email { ... }
2026-05-19 17:04:26 +00:00
Header: X-Step-Up-Token: < step_up_token >
2026-05-20 07:01:08 +00:00
- Logic <20> h<EFBFBD> G
a. Casbin enforce <20> q<EFBFBD> L<EFBFBD> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> <76> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
b. StepUpTokenUseCase.Verify(token, expectedAction="change_business_email", tenant, uid)
2026-05-20 07:01:08 +00:00
c. SETNX auth:stepup:used:{jti}=1<> A<EFBFBD> w<EFBFBD> ι L <20> <> <20> ڵ<EFBFBD>
d. <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ~<7E> <> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
#### <20> u<EFBFBD> <75> <EFBFBD> I
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- Logic <20> h<EFBFBD> u<EFBFBD> <75> <EFBFBD> G**Casbin allow <20> <> **<2A> A<EFBFBD> <41> step-up<75> F<EFBFBD> <46> <EFBFBD> h<EFBFBD> <68>
- Header <20> W<EFBFBD> ١ G`X-Step-Up-Token`
- <20> <> <EFBFBD> Ѧ^<5E> ǡG`403 step_up_required` + `{ required_action: "change_business_email", available_channels: ["totp","sms"] }` <EFBFBD> A<EFBFBD> e<EFBFBD> ݨ̦<EFBFBD> <EFBFBD> <EFBFBD> step-up <20> y<EFBFBD> {
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 5.7 <20> b<EFBFBD> <62> <EFBFBD> R<EFBFBD> <52> <EFBFBD> P<EFBFBD> ΦW<CEA6> ơ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
```
T0: DELETE /api/v1/members/me (Step-up: delete_member)
1. status = deleted, deleted_at = now
2026-05-20 07:01:08 +00:00
2. auth.RevokeAllForUser<65> ]INCR auth_gen + <20> <> jti pair <20> ¦W<C2A6> <57> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
3. ZITADEL Mgmt.DeactivateUser
2026-05-20 07:01:08 +00:00
4. <20> M member:profile / member:sub cache
2026-05-19 17:04:26 +00:00
5. audit log (actor, ip, ua, step_up_jti)
2026-05-20 07:01:08 +00:00
T+30 <20> <> : cron `member_anonymize_worker`
<20> ΦW<CEA6> <57> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> мg<D0BC> <67> hash <20> ΩT<CEA9> w placeholder<65> ^:
ZitadelEmail <20> <> "deleted:{uid}@anonymized.local"
DisplayName <20> <> "Deleted User"
Avatar <20> <> ""
Phone <20> <> ""
BusinessEmail <20> <> ""
BusinessPhone <20> <> ""
BusinessEmail/PhoneVerified <20> <> false
TOTPSecretCipher <20> <> ""
TOTPBackupCodesHash <20> <> nil
external_id, ldap_dn <20> <> ""
zitadel_sub <20> <> "deleted:{uid}" # <20> <> <EFBFBD> <EFBFBD> identities <20> ߤ@<40> <> <EFBFBD> <EFBFBD>
<20> O<EFBFBD> d<EFBFBD> <64> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> <EFBFBD> i<EFBFBD> <69> / <20> f<EFBFBD> p<EFBFBD> Ρ ^:
2026-05-19 17:04:26 +00:00
tenant_id, uid, status=deleted, deleted_at, anonymized_at, created_at
2026-05-20 07:01:08 +00:00
<20> g audit log: action=member.anonymized
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
- **<2A> <> <EFBFBD> i<EFBFBD> f**<2A> F30 <20> Ѥ<EFBFBD> <D1A4> i<EFBFBD> ѯ<EFBFBD> <D1AF> <EFBFBD> admin <20> ٭ <EFBFBD> <D9AD> ]`status=deleted <20> <> active`<60> A<EFBFBD> <41> <EFBFBD> _ cache<68> A<EFBFBD> <41> ZITADEL <20> b<EFBFBD> <62> <EFBFBD> ݥt<DDA5> <74> <EFBFBD> ҥΡ ^
- audit log <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ΦW<CEA6> Ƽ v<C6BC> T<EFBFBD> ]actor uid <20> <> <EFBFBD> O<EFBFBD> d<EFBFBD> A<EFBFBD> K<EFBFBD> <4B> <EFBFBD> l<EFBFBD> <6C> <EFBFBD> ^
- <20> ΦW<CEA6> ƫ<EFBFBD> SCIM `Users.{id}` <20> <> <EFBFBD> i<EFBFBD> d<EFBFBD> <64> <EFBFBD> ]<5D> ^<5E> <> `active=false` + <20> ΦW payload<61> ^<5E> A<EFBFBD> <41> <EFBFBD> ^ 404<30> A<EFBFBD> H<EFBFBD> <48> <EFBFBD> <EFBFBD> client <20> <> reconciliation
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 5.8 TOTP<54> ]Authenticator App<70> A<EFBFBD> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> G<EFBFBD> ҥΡ ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> ~<7E> ȯ<EFBFBD> TOTP<54> AGateway ** <EFBFBD> ۤv<EFBFBD> s secret**<2A> A<EFBFBD> P ZITADEL <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> TOTP ** <EFBFBD> W<EFBFBD> <EFBFBD> **<2A> ]<5D> <> <EFBFBD> ӿW<D3BF> ߸j<DFB8> w<EFBFBD> A<EFBFBD> ϥΪ̭<CEAA> <CCAD> <EFBFBD> setup <20> ݦU<DDA6> <55> <EFBFBD> @<40> <> QR<51> ^<5E> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> }<7D> HZITADEL TOTP <20> O<EFBFBD> n<EFBFBD> J<EFBFBD> Ρ Bsecret <20> b ZITADEL<45> FGateway step-up TOTP <20> Ω<EFBFBD> <CEA9> ~<7E> Ⱦާ@<40> Bsecret <20> b Gateway<61> A<EFBFBD> קK Gateway <20> <> ZITADEL <20> p<EFBFBD> <70> <EFBFBD> <EFBFBD> <EFBFBD> ƪ<EFBFBD> <C6AA> ̿<EFBFBD> <CCBF> P<EFBFBD> <50> <EFBFBD> X<EFBFBD> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### Member <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> ɥR <20> <> 5.4<EFBFBD> ^
2026-05-19 17:04:26 +00:00
```go
type Member struct {
2026-05-20 07:01:08 +00:00
// ... <20> J<EFBFBD> <4A> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
TOTPEnrolled bool
2026-05-20 07:01:08 +00:00
TOTPSecretCipher string // AES-GCM(secret, KEK)<29> AAES-256<35> FKEK <20> <> KMS / secret manager
2026-05-19 17:04:26 +00:00
TOTPEnrolledAt int64
2026-05-20 07:01:08 +00:00
TOTPBackupCodesHash []string // bcrypt(code)<29> A10 <20> դ@<40> <> <EFBFBD> ʳƴ<CAB3> <C6B4> X<EFBFBD> A<EFBFBD> ι L<CEB9> Y<EFBFBD> ٰ<EFBFBD>
2026-05-19 17:04:26 +00:00
}
```
2026-05-20 07:01:08 +00:00
> Secret <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ٥ [<5B> K<EFBFBD> x<EFBFBD> s<EFBFBD> A**<2A> T<EFBFBD> <54> **<2A> <> <EFBFBD> X<EFBFBD> γ <EFBFBD> <CEB3> <EFBFBD> base32<33> CKEK <20> <> KMS / Vault<6C> Frotation <20> ɳv<C9B3> <76> re-encrypt<70> ]<5D> I<EFBFBD> <49> worker<65> ^<5E> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### UseCase <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> ɥR <20> <> 5.2<EFBFBD> ^
2026-05-19 17:04:26 +00:00
```go
type TOTPUseCase interface {
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> <EFBFBD> secret + otpauth URL + 10 <20> <> backup codes<65> ]<5D> <> <EFBFBD> <EFBFBD> enroll<6C> F<EFBFBD> |<7C> <> <EFBFBD> ҥΡ ^
2026-05-19 17:04:26 +00:00
StartEnroll(ctx context.Context, tenantID, uid string) (*EnrollStartDTO, error)
2026-05-20 07:01:08 +00:00
// <20> ϥΪ̱<CEAA> QR<51> B<EFBFBD> <42> <EFBFBD> J<EFBFBD> Ĥ@<40> <> code <20> <> <20> T<EFBFBD> { <20> <> <20> <> TOTPEnrolled = true
ConfirmEnroll(ctx context.Context, tenantID, uid, code string) ([]string, error) // <20> ^ backup_codes<65> ]<5D> <> <EFBFBD> X<EFBFBD> A<EFBFBD> u<EFBFBD> ^<5E> @<40> <> <EFBFBD> ^
// step-up <20> Ρ G<CEA1> <47> <EFBFBD> @<40> <> code<64> ]<5D> t backup code<64> ^
2026-05-19 17:04:26 +00:00
VerifyCode(ctx context.Context, tenantID, uid, code string) error
2026-05-20 07:01:08 +00:00
// <20> Ѱ<EFBFBD> <D1B0> j<EFBFBD> w<EFBFBD> ]<5D> <> step-up = disable_totp<74> ^
2026-05-19 17:04:26 +00:00
Disable(ctx context.Context, tenantID, uid string) error
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> s<EFBFBD> <73> <EFBFBD> <EFBFBD> backup codes<65> ]<5D> <> step-up<75> ^
2026-05-19 17:04:26 +00:00
RegenerateBackupCodes(ctx context.Context, tenantID, uid string) ([]string, error)
}
```
2026-05-20 07:01:08 +00:00
#### <20> y<EFBFBD> {
2026-05-19 17:04:26 +00:00
```
A. Enroll
2026-05-20 07:01:08 +00:00
Client <20> <> POST /api/v1/members/me/totp/enroll-start
1. <20> Y<EFBFBD> w TOTPEnrolled = true <20> <> 409 already_enrolled
2. <20> ͦ<EFBFBD> 32-byte random secret <20> <> base32
2026-05-19 17:04:26 +00:00
3. otpauth_url = "otpauth://totp/{Issuer}:{tenant_slug}:{uid}?secret={base32}& issuer={Issuer}& algorithm=SHA1& digits=6& period=30"
2026-05-20 07:01:08 +00:00
4. <20> Ȧs<C8A6> <73> Redis<69> ]<5D> <> <EFBFBD> J Mongo<67> A<EFBFBD> קK<D7A7> b<EFBFBD> <62> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> secret <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ^:
2026-05-19 17:04:26 +00:00
totp:enroll:{tenant}:{uid} = {secret_cipher} TTL 10min
2026-05-20 07:01:08 +00:00
<20> <> { otpauth_url, qr_png_base64 }
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
Client <20> <> POST /api/v1/members/me/totp/enroll-confirm { code }
1. <20> q Redis <20> <> <EFBFBD> Ȧs secret
2. VerifyTOTP(secret, code, window=<3D> <> 1) <20> <> <20> <> <EFBFBD> ѫh 400 invalid_code
2026-05-19 17:04:26 +00:00
3. member.TOTPSecretCipher = secret_cipher
member.TOTPEnrolled = true
member.TOTPEnrolledAt = now
2026-05-20 07:01:08 +00:00
4. <20> ͦ<EFBFBD> 10 <20> <> backup code (random hex)<29> Bbcrypt <20> <> <EFBFBD> s TOTPBackupCodesHash
2026-05-19 17:04:26 +00:00
5. DEL totp:enroll:*
6. audit log
2026-05-20 07:01:08 +00:00
<20> <> { backup_codes: [...10 <20> թ<EFBFBD> <D5A9> X<EFBFBD> A<EFBFBD> Ȧ<EFBFBD> <C8A6> @<40> <> <EFBFBD> ^<5E> <> ] }
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
B. Verify<66> ]step-up <20> @<40> Ρ ^
StepUpUseCase.Confirm <20> <> <EFBFBD> G
VerifyTOTP(decryptedSecret, code, window=<3D> <> 1) OR matchBackupCode(code)
<20> Y<EFBFBD> <59> backup code <20> R<EFBFBD> <52> <20> <> <20> q TOTPBackupCodesHash <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ӵ<EFBFBD> <D3B5> ]<5D> 榸<EFBFBD> ʡ^
2026-05-19 17:04:26 +00:00
C. Disable
2026-05-20 07:01:08 +00:00
Client <20> <> DELETE /api/v1/members/me/totp
2026-05-19 17:04:26 +00:00
Header: X-Step-Up-Token: < action = disable_totp >
2026-05-20 07:01:08 +00:00
1. <20> M TOTPSecretCipher<65> BTOTPEnrolled=false<73> BTOTPBackupCodesHash=nil
2026-05-19 17:04:26 +00:00
2. audit log
```
2026-05-20 07:01:08 +00:00
#### TOTP <20> t<EFBFBD> <74> <EFBFBD> k<EFBFBD> P<EFBFBD> Ѽ<EFBFBD>
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- **RFC 6238**<2A> ]SHA1 / 30s period / 6 digits<74> ^<5E> A<EFBFBD> ۮe Google Authenticator<6F> BAuthy<68> B1Password<72> BMicrosoft Authenticator
- `window = <20> <> 1` <EFBFBD> G<EFBFBD> <EFBFBD> <EFBFBD> \<5C> e<EFBFBD> <65> <EFBFBD> @<40> <> 30s <20> ϶<EFBFBD> <CFB6> A<EFBFBD> e<EFBFBD> Ԯ<EFBFBD> <D4AE> <EFBFBD> <EFBFBD> }<7D> <>
- Replay <20> O<EFBFBD> @<40> G<EFBFBD> <47> <EFBFBD> \<5C> ϥΪ<CFA5> `(uid, code, timestep)` <20> g<EFBFBD> J `totp:used:{tenant}:{uid}:{timestep}` SETNX TTL=90s<30> F<EFBFBD> P<EFBFBD> @ code <20> G<EFBFBD> <47> <EFBFBD> X<EFBFBD> {<7B> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
- Backup code<64> G10 <20> ա B12 <20> r hex<65> ]48-bit entropy<70> ^<5E> Bbcrypt cost 10<31> B<EFBFBD> <42> <EFBFBD> X<EFBFBD> <58> enroll <20> ɦ^<5E> Ǥ@<40> <>
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### API<50> ]<5D> ɥR <20> <> 7.2<EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| Method | Path | <20> <> <EFBFBD> <EFBFBD> | Step-up |
2026-05-19 17:04:26 +00:00
|--------|------|------|---------|
2026-05-20 07:01:08 +00:00
| POST | `/api/v1/members/me/totp/enroll-start` | <20> <> otpauth URL + QR | <20> X |
| POST | `/api/v1/members/me/totp/enroll-confirm` | <20> <> <EFBFBD> Ĥ@<40> <> code<64> A<EFBFBD> ҥ<EFBFBD> + <20> ^ backup codes | <20> X |
| GET | `/api/v1/members/me/totp` | <20> <> TOTP <20> <> <EFBFBD> A<EFBFBD> ]enrolled? backup <20> Ѿl<D1BE> ơ^ | <20> X |
| POST | `/api/v1/members/me/totp/backup-codes` | <20> <> <EFBFBD> <EFBFBD> backup codes | ? `disable_totp` |
| DELETE | `/api/v1/members/me/totp` | <20> Ѱ<EFBFBD> <D1B0> j<EFBFBD> w | ? `disable_totp` |
2026-05-19 17:04:26 +00:00
2026-05-21 06:45:35 +00:00
### 5.9 UseCase <20> s<EFBFBD> ƥܨ<C6A5>
2026-05-19 17:04:26 +00:00
2026-05-21 06:45:35 +00:00
> <EFBFBD> i<EFBFBD> <EFBFBD> atomic primitives <20> b **logic <20> h** <20> <> <EFBFBD> զ X<D5A6> 覡<EFBFBD> CB2C <20> <> <EFBFBD> U / <20> n<EFBFBD> J **<2A> w<EFBFBD> <77> <EFBFBD> @** <20> <> `internal/logic/auth/`<60> F<EFBFBD> <46> [auth-unified-registration.md](./auth-unified-registration.md)<29> C
2026-05-19 17:04:26 +00:00
2026-05-21 06:45:35 +00:00
#### Case A<> G<EFBFBD> <47> <EFBFBD> x<EFBFBD> <78> <EFBFBD> ͵<EFBFBD> <CDB5> U + Email OTP <20> <> <EFBFBD> ҡ]**<2A> w<EFBFBD> <77> <EFBFBD> @**<2A> G`RegisterLogic` / `RegisterConfirmLogic`<60> ^
2026-05-19 17:04:26 +00:00
```go
2026-05-21 06:45:35 +00:00
// HTTP: POST /auth/register <20> <> Logic <20> s<EFBFBD> ơ]<5D> K<EFBFBD> n<EFBFBD> ^
// 1) invite consume<6D> ]<5D> Y RequireInviteCode<64> ^
// 2) zitadel.CreateHumanUser
2026-05-19 17:04:26 +00:00
m, _ := mLifecycle.CreateUnverified(ctx, & CreatePlatformMemberRequest{
2026-05-21 06:45:35 +00:00
TenantID: tenantID, Email: email, DisplayName: name, ZitadelUserID: zitadelSub,
2026-05-19 17:04:26 +00:00
})
2026-05-21 06:45:35 +00:00
// 3) registration metadata.Record<72> ]channel=email<69> ^
chal, plain, _ := mOTP.Generate(ctx, & GenerateOTPRequest{
TenantID: tenantID, UID: m.UID, Purpose: OTPPurposeRegistrationEmail, Target: email,
2026-05-19 17:04:26 +00:00
})
2026-05-21 06:45:35 +00:00
notifier.Send(ctx, & SendRequest{ Kind: NotifyVerifyRegistrationEmail, Data: map[string]any{"code": plain, ...} })
// HTTP: POST /auth/register/confirm
_ = mOTP.Verify(ctx, & VerifyOTPRequest{ ... Purpose: OTPPurposeRegistrationEmail })
2026-05-19 17:04:26 +00:00
_ = mLifecycle.Activate(ctx, tenantID, m.UID)
2026-05-21 06:45:35 +00:00
// auth.IssuePair <20> <> { access_token, refresh_token }
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
#### Case B<> GOIDC<44> ]Social / ZITADEL Hosted UI<55> ^<5E> n<EFBFBD> J <20> X <20> <> <EFBFBD> <EFBFBD> OTP
2026-05-19 17:04:26 +00:00
```go
m, _ := mProv.EnsureFromOIDC(ctx, & EnsureFromOIDCRequest{
TenantID: tenantID,
ZitadelSub: claims.Sub,
Email: claims.Email,
EmailVerified: claims.EmailVerified,
DisplayName: claims.Name,
})
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> <EFBFBD> active<76> F<EFBFBD> <46> <EFBFBD> <EFBFBD> auth.IssueTokenPair
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
#### Case C<> GLDAP IdP <20> <> <EFBFBD> <EFBFBD> <EFBFBD> n<EFBFBD> J JIT <20> X <20> <> <EFBFBD> <EFBFBD> OTP
2026-05-19 17:04:26 +00:00
```go
m, _ := mProv.EnsureFromLDAP(ctx, & EnsureFromLDAPRequest{
TenantID: tenantID, ExternalID: ldapUUID, LDAPDN: dn,
Username: username, Email: email, DisplayName: name,
Groups: groups, Source: RoleSourceLDAPJIT,
})
```
2026-05-20 07:01:08 +00:00
#### Case D<> GSCIM Create User <20> X <20> <> <EFBFBD> <EFBFBD> OTP
2026-05-19 17:04:26 +00:00
```go
m, _ := mProv.EnsureFromSCIM(ctx, & EnsureFromSCIMRequest{
TenantID: tenantID, ExternalID: scimExternalID,
UserName: username, Email: email, Active: true, RawPayload: rawJSON,
})
```
2026-05-20 07:01:08 +00:00
#### Case E<> G<EFBFBD> w<EFBFBD> n<EFBFBD> J user <20> <> <EFBFBD> j<EFBFBD> ~<7E> <> email<69> ]atomic <20> <> <EFBFBD> <EFBFBD> vs composite<74> ^
2026-05-19 17:04:26 +00:00
```go
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> | 1<> G<EFBFBD> <47> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> atomic<69> ]<5D> <> <EFBFBD> ӱ<EFBFBD> <D3B1> <EFBFBD> <EFBFBD> ɥΡ ^
2026-05-19 17:04:26 +00:00
chal, _ := mOTP.Generate(ctx, & GenerateOTPRequest{
TenantID: tenantID, Purpose: OTPPurposeBusinessEmail, Identifier: uid,
})
notifier.Send(ctx, & SendRequest{
Channel: ChannelEmail, Kind: NotifyVerifyBusinessEmail,
Target: newEmail, Data: map[string]any{"code": chal.Code},
IdempotencyKey: chal.ChallengeID, DoNotPersistBody: true,
})
_ = mOTP.Verify(ctx, & VerifyOTPRequest{
TenantID: tenantID, ChallengeID: chal.ChallengeID,
Code: userCode, Purpose: OTPPurposeBusinessEmail,
})
_ = mProfile.SetBusinessEmailVerified(ctx, tenantID, uid, newEmail)
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> | 2<> G<EFBFBD> <47> composite<74> ]²<> 污<EFBFBD> p<EFBFBD> <70> <EFBFBD> o<EFBFBD> ӴN<D3B4> n<EFBFBD> ^
2026-05-19 17:04:26 +00:00
chal, _ := mVerification.StartEmailVerify(ctx, tenantID, uid, newEmail)
// ...
_ = mVerification.ConfirmEmailVerify(ctx, tenantID, uid, chal.ChallengeID, userCode)
```
2026-05-20 07:01:08 +00:00
> <EFBFBD> C<EFBFBD> <EFBFBD> atomic <20> ʧ@<40> W<EFBFBD> ߥi<DFA5> I<EFBFBD> s<EFBFBD> B<EFBFBD> W<EFBFBD> <57> audit<69> B<EFBFBD> W<EFBFBD> ߥ<EFBFBD> <DFA5> ѭ<EFBFBD> <D1AD> ա CLogic <20> ۦ<EFBFBD> <DBA6> M<EFBFBD> w<EFBFBD> զ X<D5A6> P<EFBFBD> <50> <EFBFBD> ǡC
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 6. permission <20> Ҳա ]B2B <20> ۩w<DBA9> q<EFBFBD> A<EFBFBD> Ѧ<EFBFBD> permission-server<65> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> <EFBFBD> <EFBFBD> |<7C> G`internal/model/permission/`
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
> <EFBFBD> <EFBFBD> <EFBFBD> `<60> l<EFBFBD> <6C> [app-cloudep-permission-server](https://code.30cm.net/digimon/app-cloudep-permission-server) <20> w<EFBFBD> <77> <EFBFBD> Ҫ<EFBFBD> <D2AA> ]<5D> p<EFBFBD> G**Casbin + Redis RBAC**<2A> B**Permission Tree<65> ]<5D> <> <EFBFBD> l<EFBFBD> ~<7E> ӡ^**<2A> B**HTTP Path/Method <20> j<EFBFBD> w**<2A> C
> **<2A> P<EFBFBD> <50> permission-server <20> <> <EFBFBD> t<EFBFBD> <74> **<2A> GToken ñ<> o/<2F> <> <EFBFBD> <EFBFBD> /<2F> ¦W<C2A6> 沾<EFBFBD> <E6B2BE> Gateway `auth` <20> Ҳա F`ClientID` <20> אּ `tenant_id`<60> F<EFBFBD> 䴩<EFBFBD> h<EFBFBD> <68> <EFBFBD> <EFBFBD> B2B <20> U<EFBFBD> ۩w<DBA9> q Role<6C> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 6.1 <20> ]<5D> p<EFBFBD> ؼ<EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> O | <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
|------|------|
2026-05-20 07:01:08 +00:00
| **Permission Tree** | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> <76> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> <EFBFBD> x seed<65> ^<5E> A<EFBFBD> <41> <EFBFBD> l<EFBFBD> `<60> I<EFBFBD> ~<7E> ӡF<D3A1> <46> <EFBFBD> `<60> I<EFBFBD> <49> <EFBFBD> <EFBFBD> <EFBFBD> h<EFBFBD> l<EFBFBD> `<60> I<EFBFBD> <49> <EFBFBD> i<EFBFBD> <69> |
| **Casbin RBAC** | <20> H `(tenant_id, role_key, http_path, http_method)` <20> <> API <20> <> <EFBFBD> v<EFBFBD> Fpath <20> 䴩 `keyMatch2` <20> U<EFBFBD> Φr<CEA6> <72> |
| **B2B <20> ۩w<DBA9> q Role** | <20> C<EFBFBD> ӯ<EFBFBD> <D3AF> <EFBFBD> <EFBFBD> إߦۭq Role<6C> A<EFBFBD> q<EFBFBD> <71> <EFBFBD> <EFBFBD> Catalog ** <EFBFBD> Ŀ<EFBFBD> ** Permission<6F> ]<5D> <> <EFBFBD> i<EFBFBD> ۳<EFBFBD> Permission <20> r<EFBFBD> <72> <EFBFBD> ^ |
| **UserRole** | <20> <> <EFBFBD> <EFBFBD> + uid + role<6C> F<EFBFBD> 䴩<EFBFBD> h<EFBFBD> <68> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> H immutable role key <20> <> Casbin subject<63> ^ |
| **RolePermission** | <20> Ŀ<EFBFBD> <C4BF> l<EFBFBD> v<EFBFBD> <76> <EFBFBD> ɦ۰ ʸɻ<CAB8> <C9BB> <EFBFBD> <EFBFBD> v<EFBFBD> <76> ID<49> ]<5D> u<EFBFBD> <75> permission-server <20> <> `getFullParentPermissionIDs` <EFBFBD> ^ |
| **Policy <20> P<EFBFBD> B** | MongoDB <20> <> Casbin Policy <20> <> Redis<69> F<EFBFBD> w<EFBFBD> <77> `LoadPolicy` + <20> ܧ<EFBFBD> <DCA7> <EFBFBD> IJ<EFBFBD> o reload |
| ** <EFBFBD> ~<7E> <> <EFBFBD> M<EFBFBD> g** | ZITADEL Role / LDAP Group / SCIM Group <20> <> <20> <> <EFBFBD> ᤺<EFBFBD> <E1A4BA> Role.Key |
| ** <EFBFBD> Ӳɫ<EFBFBD> <EFBFBD> X<EFBFBD> i** | <20> P<EFBFBD> @ API <20> i<EFBFBD> <69> `.plain_code` <20> l<EFBFBD> v<EFBFBD> <76> <EFBFBD> ]<5D> p<EFBFBD> <70> <EFBFBD> X<EFBFBD> d<EFBFBD> ߡ^<5E> A<EFBFBD> u<EFBFBD> <75> <EFBFBD> ³]<5D> p |
### 6.2 <20> P app-cloudep-permission-server <20> <> <EFBFBD> <EFBFBD>
| permission-server | Gateway permission <20> Ҳ<EFBFBD> | <20> Ƶ<EFBFBD> |
2026-05-19 13:56:59 +00:00
|-------------------|---------------------------|------|
2026-05-20 07:01:08 +00:00
| `TokenService` | ** `auth` <20> Ҳ<EFBFBD> ** | JWT <20> <> <EFBFBD> A<EFBFBD> <41> permission-server |
| `PermissionService` <EFBFBD> ]<5D> š^ | <20> <> <EFBFBD> <EFBFBD> HTTP API | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> b Gateway <20> <> <EFBFBD> S |
| `entity.Permission` | <20> u<EFBFBD> <75> + `tenant_id` <20> <> <EFBFBD> A<EFBFBD> Ρ ]<5D> <> <EFBFBD> <EFBFBD> Catalog<6F> ^ | Permission <20> <> <EFBFBD> <EFBFBD> <EFBFBD> x<EFBFBD> <78> |
| `entity.Role.ClientID` | `Role.TenantID` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> j<EFBFBD> <6A> |
| `entity.Role.UID` | `Role.CreatorUID` | <20> إߪ̡A<CCA1> i<EFBFBD> <69> |
| `entity.Role.Name` | `Role.DisplayName` | <20> <> <EFBFBD> ܦW<DCA6> ١ A<D9A1> i<EFBFBD> <69> <EFBFBD> W |
| <20> X | `Role.Key` | **Casbin policy <20> <> role <20> <> <EFBFBD> <EFBFBD> ** <EFBFBD> A<EFBFBD> <EFBFBD> <EFBFBD> ᤺<EFBFBD> ߤ@<40> B<EFBFBD> <42> <EFBFBD> i<EFBFBD> <69> |
| `Casbin Enforcer` | `RBACUseCase` + Redis Adapter | <20> u<EFBFBD> <75> |
| `PermissionTree` | `usecase/permission_tree.go` | <20> u<EFBFBD> <75> |
| `AdminRoleUID` / `GodDog` | `PlatformAdminRoleKey` + allowlist | <20> <> <EFBFBD> x<EFBFBD> W<EFBFBD> źz<DEB2> <7A> bypass<73> A<EFBFBD> <41> audit |
2026-05-19 13:56:59 +00:00
| `permission.Type` | `enum.PermissionType` | `BackendUser` / `FrontendUser` |
2026-05-20 07:01:08 +00:00
### 6.3 <20> ֤߷<D6A4> <DFB7> <EFBFBD>
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Permission<EFBFBD> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ^ <20> <> <EFBFBD> x<EFBFBD> w<EFBFBD> q<EFBFBD> A<EFBFBD> t name / http_path / http_method / parent / status / type
Role<EFBFBD> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> ۩w<DBA9> q<EFBFBD> ^ <20> <> <EFBFBD> <EFBFBD> <EFBFBD> إߪ<D8A5> <DFAA> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Fdisplay_name <20> i<EFBFBD> <69> <EFBFBD> Akey <20> <> <EFBFBD> i<EFBFBD> <69> <EFBFBD> A<EFBFBD> p sales_supervisor<6F> Btenant_admin
RolePermission Role ? Permission ID <20> h<EFBFBD> <68> <EFBFBD> h<EFBFBD> F<EFBFBD> Ŀ<EFBFBD> <C4BF> ɦ۰ ʸɤ<CAB8> <C9A4> `<60> I
UserRole uid ? Role<6C> F<EFBFBD> @ user <20> i<EFBFBD> h role
RoleMapping <20> ~<7E> <> Group/Role <20> <> <20> <> <EFBFBD> <EFBFBD> RoleID / Role.Key
2026-05-19 17:04:26 +00:00
Casbin Policy p, {tenant_id}, {role_key}, {http_path}, {http_method}, {permission.Name}
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### 6.4 Permission Entity<74> ]<5D> <> <EFBFBD> <EFBFBD> Catalog<6F> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> u<EFBFBD> <EFBFBD> permission-server <20> <> `entity.Permission` <20> <> <EFBFBD> c<EFBFBD> AMongoDB collection<6F> G`permission`<60> C
2026-05-19 13:56:59 +00:00
```go
type Permission struct {
2026-05-19 17:04:26 +00:00
ID primitive.ObjectID
2026-05-20 07:01:08 +00:00
Parent string // <20> <> <EFBFBD> v<EFBFBD> <76> ID<49> ]ObjectID hex<65> ^<5E> F<EFBFBD> <46> = <20> <> root
Name string // <20> ߤ@<40> y<EFBFBD> N<EFBFBD> W<EFBFBD> Adot notation<6F> A<EFBFBD> p member.info.select
HTTPMethods string // <20> <> <EFBFBD> Ȧp "GET"<22> A<EFBFBD> <41> regex <20> p "GET|POST|PATCH"<22> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> `<60> I<EFBFBD> <49> <EFBFBD> <EFBFBD>
HTTPPath string // <20> p /api/v1/members/*<2A> ]keyMatch2 pattern<72> ^<5E> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> `<60> I<EFBFBD> <49> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
Status enum.Status // open | close
2026-05-20 07:01:08 +00:00
Type enum.PermissionType // backend_user | frontend_user<65> ]<5D> <> <EFBFBD> x / <20> e<EFBFBD> x<EFBFBD> <78> <EFBFBD> <EFBFBD> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
CreateAt int64
UpdateAt int64
2026-05-19 13:56:59 +00:00
}
```
2026-05-20 07:01:08 +00:00
> **`Permission.Name` <20> @<40> <> <EFBFBD> إߤ<D8A5> <DFA4> i<EFBFBD> <69> <EFBFBD> W**<2A> ]<5D> Q RolePermission<6F> BUI i18n <20> <> <EFBFBD> BCasbin policy.name <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ޥΡ ^<5E> C
> <EFBFBD> o<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> `status=close`<60> F<EFBFBD> s<EFBFBD> W<EFBFBD> ٥ t<D9A5> طs leaf<61> C<EFBFBD> <43> <EFBFBD> R<EFBFBD> W<EFBFBD> n<EFBFBD> <6E> <EFBFBD> <EFBFBD> <EFBFBD> ƾE<C6BE> <45> <EFBFBD> }<7D> <> <EFBFBD> C
> **`HTTPPath` <20> <> <EFBFBD> <EFBFBD> **<2A> G<EFBFBD> קK<D7A7> r `*`<60> F<EFBFBD> U<EFBFBD> θ<EFBFBD> <CEB8> |<7C> n<EFBFBD> <6E> <EFBFBD> T<EFBFBD> Х X<D0A5> 귽<EFBFBD> ڡA<DAA1> Ҧp `/api/v1/members/*`<60> A<EFBFBD> T<EFBFBD> <54> `/api/v1/*` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> s<EFBFBD> <73> pattern<72> ]<5D> <> keyMatch2 <20> g<EFBFBD> <67> <EFBFBD> R<EFBFBD> <52> <EFBFBD> ^<5E> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### <20> R<EFBFBD> W<EFBFBD> W<EFBFBD> h<EFBFBD> ]dot notation<6F> A<EFBFBD> P permission-server <20> @<40> P<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```
{domain}.{module}.{action}
2026-05-20 07:01:08 +00:00
{domain}.{module}.{action}.{variant} # <20> p .plain_code
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
#### Permission Tree <20> d<EFBFBD> ҡ]seed <20> <> <EFBFBD> ס ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
member.info.management # <20> @<40> šG<C5A1> |<7C> <> <EFBFBD> <EFBFBD> <EFBFBD> T<EFBFBD> z<DEB2> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> L HTTP<54> ^
<EFBFBD> u<EFBFBD> w<EFBFBD> w member.basic.info # <20> G<EFBFBD> šG<C5A1> <47> ¦<EFBFBD> <C2A6> <EFBFBD> T
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w member.info.select # GET /api/v1/members/me
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w member.info.update # PATCH /api/v1/members/me
<EFBFBD> x <20> |<7C> w<EFBFBD> w member.info.select.plain_code # GET /api/v1/members<72> ]<5D> <> <EFBFBD> X<EFBFBD> <58> <EFBFBD> <EFBFBD> <EFBFBD> ^
<EFBFBD> u<EFBFBD> w<EFBFBD> w member.admin.list # GET /api/v1/members
<EFBFBD> u<EFBFBD> w<EFBFBD> w member.admin.read # GET /api/v1/members/:uid
<EFBFBD> u<EFBFBD> w<EFBFBD> w member.admin.update # PATCH /api/v1/members/:uid
<EFBFBD> |<7C> w<EFBFBD> w member.admin.status # PATCH /api/v1/members/:uid/status
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
permission.role.management # <20> @<40> šG<C5A1> <47> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> <76> <EFBFBD> z
<EFBFBD> u<EFBFBD> w<EFBFBD> w permission.role.read # GET /api/v1/permissions/roles
<EFBFBD> u<EFBFBD> w<EFBFBD> w permission.role.write # POST/PUT/DELETE roles
<EFBFBD> u<EFBFBD> w<EFBFBD> w permission.assign.write # POST/DELETE user roles
<EFBFBD> |<7C> w<EFBFBD> w permission.catalog.read # GET /api/v1/permissions/catalog
2026-05-19 13:56:59 +00:00
tenant.management
2026-05-20 07:01:08 +00:00
<EFBFBD> u<EFBFBD> w<EFBFBD> w tenant.read
<EFBFBD> u<EFBFBD> w<EFBFBD> w tenant.ldap.write
<EFBFBD> |<7C> w<EFBFBD> w tenant.sync.trigger
2026-05-19 13:56:59 +00:00
scim.management
2026-05-20 07:01:08 +00:00
<EFBFBD> u<EFBFBD> w<EFBFBD> w scim.users.write
<EFBFBD> |<7C> w<EFBFBD> w scim.groups.write
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
system.management # <20> <> <EFBFBD> x<EFBFBD> <78>
<EFBFBD> |<7C> w<EFBFBD> w system.tenant.create
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
> **<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> `<60> I**<2A> ]<5D> L `http_path`<60> ^<5E> <> UI <20> 𪬤Ŀ<F0AAACA4> <C4BF> F**<2A> <> <EFBFBD> `<60> I**<2A> ~<7E> g<EFBFBD> J Casbin Policy<63> C
> <EFBFBD> s<EFBFBD> W Permission <20> <> <EFBFBD> <EFBFBD> <EFBFBD> x seed migration<6F> F<EFBFBD> <46> <EFBFBD> <EFBFBD> **<2A> <> <EFBFBD> i**<2A> ۦ<EFBFBD> <DBA6> s<EFBFBD> W Permission <20> W<EFBFBD> ١ C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
#### Permission Tree <20> 欰<EFBFBD> ]<5D> u<EFBFBD> <75> permission-server<65> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
1. ** `filterOpenNodes` **<2A> G<EFBFBD> <47> <EFBFBD> `<60> I `status=close` <20> <> <20> <> <EFBFBD> ʤl<CAA4> 𤣥i<F0A4A3A5> <69>
2. ** `getFullParentPermissionIDs` **<2A> G<EFBFBD> Ŀ<EFBFBD> <C4BF> l<EFBFBD> v<EFBFBD> <76> <20> <> <20> ۰ ʥ[<5B> J<EFBFBD> Ҧ<EFBFBD> <D2A6> <EFBFBD> <EFBFBD> `<60> I ID
3. ** `getFullParentPermission` **<2A> G<EFBFBD> d Role <20> v<EFBFBD> <76> <20> <> <20> ^<5E> ǧt<C7A7> <74> <EFBFBD> `<60> I<EFBFBD> <49> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> permission name <20> <> status map<61> ]<5D> ѫe<D1AB> <65> UI<55> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 6.5 Role Entity<74> ]B2B <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ۩w<DBA9> q<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```go
type Role struct {
2026-05-19 17:04:26 +00:00
ID primitive.ObjectID
2026-05-20 07:01:08 +00:00
TenantID string // <20> <> <EFBFBD> <EFBFBD> ID<49> ]= <20> <> ClientID<49> ^
Key string // immutable role key<65> A<EFBFBD> <41> <EFBFBD> ᤺<EFBFBD> ߤ@<40> FCasbin enforce <20> Φ<EFBFBD> <CEA6> <EFBFBD>
DisplayName string // <20> <> <EFBFBD> ܦW<DCA6> ١ A<D9A1> i<EFBFBD> <69> <EFBFBD> W
CreatorUID string // <20> إߪ<D8A5> uid<69> ]= <20> <> Role.UID<49> A<EFBFBD> i<EFBFBD> <69> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
Status enum.Status // open | close
2026-05-20 07:01:08 +00:00
IsSystem bool // <20> t<EFBFBD> <74> seed <20> <> <EFBFBD> w<EFBFBD> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> AB2B <20> i<EFBFBD> <69> Permission <20> <> <EFBFBD> <EFBFBD> <EFBFBD> i<EFBFBD> R<EFBFBD> <52> Owner
2026-05-19 17:04:26 +00:00
CreateAt int64
UpdateAt int64
2026-05-19 13:56:59 +00:00
}
2026-05-19 17:04:26 +00:00
// Index: { tenant_id, key } unique
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
> **`Role.Key` <20> W<EFBFBD> d**<2A> G
> - <20> 榡<EFBFBD> G`^[a-z][a-z0-9_]{1,63}$`
> - <20> <> <EFBFBD> ᤺<EFBFBD> ߤ@<40> F<EFBFBD> إ߫<D8A5> **<2A> <> <EFBFBD> i<EFBFBD> ק<EFBFBD> **
> - <20> T<EFBFBD> <54> `system.` / `platform_` <20> r<EFBFBD> <72> <EFBFBD> ]<5D> O<EFBFBD> d<EFBFBD> <64> <EFBFBD> <EFBFBD> <EFBFBD> x<EFBFBD> <78> role<6C> ^
> - rename <20> <> `DisplayName`<60> A<EFBFBD> <41> <EFBFBD> v<EFBFBD> T UserRole<6C> BRoleMapping<6E> BCasbin policy <20> P<EFBFBD> J<EFBFBD> <4A> token
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### B2B <20> ۩w<DBA9> q<EFBFBD> W<EFBFBD> h
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
1. <20> <> <EFBFBD> <EFBFBD> <EFBFBD> i **CRUD** <20> ۭq Role<6C> ]`is_system=false`<60> ^
2. <20> t<EFBFBD> <74> seed <20> <> <EFBFBD> w<EFBFBD> ] Role<6C> ]`is_system=true`<60> ^<5E> i<EFBFBD> ק<EFBFBD> Permission <20> <> <EFBFBD> X<EFBFBD> A**tenant_owner <20> <> <EFBFBD> i<EFBFBD> R**
3. Role <20> j<EFBFBD> w<EFBFBD> <77> Permission <20> <> <EFBFBD> <EFBFBD> <EFBFBD> O<EFBFBD> <4F> <EFBFBD> <EFBFBD> Catalog <20> <> `status=open` <20> <> <EFBFBD> `<60> I
4. <20> <> <EFBFBD> <EFBFBD> **<2A> <> <EFBFBD> i**<2A> Ŀ<EFBFBD> `system.*` <20> v<EFBFBD> <76> <EFBFBD> ]<5D> <> <EFBFBD> D<EFBFBD> <44> <EFBFBD> x<EFBFBD> t<EFBFBD> <74> <EFBFBD> }<7D> ҡ^
5. <20> ܤ֫O<D6AB> d<EFBFBD> @<40> <> Role <20> t `permission.role.write` <EFBFBD> A<EFBFBD> קK<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
#### <20> w<EFBFBD> ] Role <20> ҪO<D2AA> ]<5D> إ<EFBFBD> B2B tenant <20> <> seed<65> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| Key | DisplayName | <20> w<EFBFBD> ]<5D> Ŀ<EFBFBD> <C4BF> ]Permission Name<6D> ^ |
2026-05-19 13:56:59 +00:00
|------|------|----------------------------|
2026-05-20 07:01:08 +00:00
| `tenant_owner` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ֦<EFBFBD> <D6A6> <EFBFBD> | <20> <> `system.*` <20> ~<7E> <> <EFBFBD> <EFBFBD> open <20> `<60> I |
| `tenant_admin` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> z<DEB2> <7A> | member.*, permission.*, tenant.*, scim.* |
| `member_manager` | <20> |<7C> <> <EFBFBD> z | member.admin.list, member.admin.read, member.admin.status |
| `member` | <20> @<40> <> <EFBFBD> |<7C> <> | member.info.select, member.info.update |
| `viewer` | <20> <> Ū | member.info.select |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
B2B <20> z<DEB2> <7A> <EFBFBD> d<EFBFBD> ҡG
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
<EFBFBD> إ<EFBFBD> Role<6C> Gsales_supervisor
<EFBFBD> Ŀ<EFBFBD> <EFBFBD> Gmember.admin.list, member.admin.read
<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> GPOST /permissions/users/{uid}/roles { "role_id": "..." }
<EFBFBD> <EFBFBD> RolePermission.Create <20> <> getFullParentPermissionIDs <20> ۰ ʸ<DBB0> parent
<EFBFBD> <EFBFBD> LoadPolicy <20> <> <EFBFBD> s Casbin
2026-05-19 13:56:59 +00:00
```
### 6.6 UserRole / RolePermission
```go
type UserRole struct {
TenantID string
UID string
RoleID string // Role._id hex
Source enum.RoleSource // manual | zitadel | ldap | scim
CreateAt int64
UpdateAt int64
}
// Index: { tenant_id, uid, role_id } unique
// Index: { tenant_id, uid }
type RolePermission struct {
2026-05-19 17:04:26 +00:00
TenantID string
2026-05-19 13:56:59 +00:00
RoleID string
PermissionID string
CreateAt int64
UpdateAt int64
}
2026-05-19 17:04:26 +00:00
// Index: { tenant_id, role_id, permission_id } unique
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
> <EFBFBD> <EFBFBD> permission-server <20> <> UserRole <20> <> <EFBFBD> @ user <20> @ role<6C> ]Update <20> л\<5C> ^<5E> F<EFBFBD> s<EFBFBD> ]<5D> p**<2A> 䴩<EFBFBD> h<EFBFBD> <68> <EFBFBD> <EFBFBD> **<2A> AMiddleware <20> <> <EFBFBD> C<EFBFBD> <43> immutable role key <20> <> Casbin enforce<63> A<EFBFBD> <41> <EFBFBD> @ allow <20> Y<EFBFBD> q<EFBFBD> L<EFBFBD> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 6.7 Casbin RBAC<41> ]<5D> ֤߱<D6A4> <DFB1> v<EFBFBD> <76> <EFBFBD> <EFBFBD> <EFBFBD> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
#### <20> ҫ<EFBFBD> <D2AB> <EFBFBD> `etc/rbac.conf`<60> ]Gateway <20> h<EFBFBD> <68> <EFBFBD> ᪩<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```ini
[request_definition]
2026-05-19 17:04:26 +00:00
r = tenant, role, path, method
2026-05-19 13:56:59 +00:00
[policy_definition]
2026-05-19 17:04:26 +00:00
p = tenant, role, path, methods, name
2026-05-19 13:56:59 +00:00
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
2026-05-19 17:04:26 +00:00
m = r.tenant == p.tenant & & r.role == p.role & & keyMatch2(r.path, p.path) & & regexMatch(r.method, p.methods)
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
- **`keyMatch2`**<2A> G<EFBFBD> 䴩 `/api/v1/members/*` <20> U<EFBFBD> <55> path
- **`regexMatch`**<2A> G<EFBFBD> 䴩 `GET|POST` <20> h method <20> g<EFBFBD> b<EFBFBD> P<EFBFBD> @ policy
- **SuperAdmin bypass**<2A> G<EFBFBD> <47> <EFBFBD> <EFBFBD> <EFBFBD> b Casbin matcher<65> F<EFBFBD> <46> Middleware <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> platform role / allowlist <20> <> <EFBFBD> u<EFBFBD> <75> <EFBFBD> A<EFBFBD> üg<C3BC> J audit log
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
#### Policy <20> <> <EFBFBD> J<EFBFBD> ]`RBACUseCase.LoadPolicy`<60> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
1. permissionRepo.GetAll <20> <> GeneratePermissionTree <20> <> filterOpenNodes
2. roleRepo.All(tenant_id) <20> <> <20> C<EFBFBD> <43> role <20> <> rolePermissionRepo.Get
3. <20> <> <EFBFBD> C<EFBFBD> <43> (role, permission) <20> Y http_path + http_method <20> D<EFBFBD> šG
2026-05-19 17:04:26 +00:00
enforcer.AddPolicy(tenantID, role.Key, permission.HTTPPath, permission.HTTPMethods, permission.Name)
2026-05-20 07:01:08 +00:00
4. adapter.SavePolicy(tenant_id) <20> <> Redis List<73> ]tenant-scoped casbin rules<65> ^
2026-05-19 13:56:59 +00:00
5. enforcer.LoadPolicy()
```
2026-05-20 07:01:08 +00:00
#### <20> <> <EFBFBD> v<EFBFBD> ˬd<CBAC> ]`RBACUseCase.Check`<60> ^
2026-05-19 13:56:59 +00:00
```go
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> J<EFBFBD> GtenantID, roleKey, requestPath, requestMethod
2026-05-19 17:04:26 +00:00
ok, policy, err := enforcer.EnforceEx(tenantID, roleKey, path, method)
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
// <20> ^<5E> <> CheckRolePermissionStatus<75> G
2026-05-19 13:56:59 +00:00
// Allow: bool
2026-05-20 07:01:08 +00:00
// PermissionName: string // <20> R<EFBFBD> <52> <EFBFBD> <EFBFBD> permission.Name
// PlainCode: bool // <20> O<EFBFBD> _<EFBFBD> <5F> .plain_code <20> l<EFBFBD> v<EFBFBD> <76> <EFBFBD> ]GET <20> <> <EFBFBD> B<EFBFBD> ~<7E> d<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
#### Policy <20> P<EFBFBD> B<EFBFBD> <42> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| IJ<> o | <20> ʧ@ |
2026-05-19 13:56:59 +00:00
|------|------|
2026-05-20 07:01:08 +00:00
| RolePermission <20> ܧ<EFBFBD> | <20> <> tenant `LoadPolicy` + <20> v<EFBFBD> <76> <EFBFBD> ֨<EFBFBD> <D6A8> <EFBFBD> <EFBFBD> <EFBFBD> |
| Permission status <20> ܧ<EFBFBD> <DCA7> ]<5D> <> <EFBFBD> x<EFBFBD> ^ | <20> <> <EFBFBD> <EFBFBD> `LoadAllPolicies` + <20> v<EFBFBD> <76> <EFBFBD> ֨<EFBFBD> <D6A8> <EFBFBD> <EFBFBD> <EFBFBD> |
| <20> w<EFBFBD> <77> cron<6F> ]<5D> p 5min<69> ^ | `SyncPolicy` <20> ©<EFBFBD> |
| Gateway <20> Ұ<EFBFBD> | <20> <> <EFBFBD> l `LoadPolicy` |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
Redis <20> x<EFBFBD> s Casbin rules<65> G`permission:casbin:rules:{tenant_id}`<60> ]List of JSON `rbac.Rule` <EFBFBD> ^<5E> C<EFBFBD> <43> <EFBFBD> q<EFBFBD> <71> <EFBFBD> J<EFBFBD> ɥi<C9A5> <69> <EFBFBD> y tenant-scoped keys<79> A<EFBFBD> Υ <EFBFBD> repository <20> <> MongoDB role/permission <20> <> <EFBFBD> ءC
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 6.8 UseCase <20> <> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
```go
2026-05-20 07:01:08 +00:00
// --- Casbin <20> <> <EFBFBD> v<EFBFBD> ]<5D> ֤ߡ^---
2026-05-19 13:56:59 +00:00
type RBACUseCase interface {
Check(ctx context.Context, req *CheckRequest) (*CheckResult, error)
LoadPolicy(ctx context.Context, tenantID string) error
LoadAllPolicies(ctx context.Context) error
SyncPolicy(ctx context.Context, interval time.Duration)
}
type CheckRequest struct {
TenantID string
UID string
2026-05-19 17:04:26 +00:00
RoleKey string // immutable Role.Key
2026-05-20 07:01:08 +00:00
Path string // <20> <> <EFBFBD> ڽШD path
Method string // <20> <> <EFBFBD> <EFBFBD> HTTP method
2026-05-19 13:56:59 +00:00
}
type CheckResult struct {
Allow bool
PermissionName string
PlainCode bool
MatchedRole string
}
2026-05-20 07:01:08 +00:00
// --- Permission Catalog<6F> ]<5D> <> <EFBFBD> x<EFBFBD> š^---
2026-05-19 13:56:59 +00:00
type PermissionUseCase interface {
All(ctx context.Context, status *enum.Status) ([]PermissionDTO, error)
2026-05-20 07:01:08 +00:00
FilterAll(ctx context.Context) ([]PermissionDTO, error) // <20> 𪬹L<F0AAACB9> o open <20> `<60> I
Insert(ctx context.Context, req *CreatePermissionRequest) error // <20> <> <EFBFBD> x Admin
2026-05-19 13:56:59 +00:00
Update(ctx context.Context, id string, req *UpdatePermissionRequest) error
}
2026-05-20 07:01:08 +00:00
// --- Role<6C> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> šAB2B <20> ۩w<DBA9> q<EFBFBD> ^---
2026-05-19 13:56:59 +00:00
type RoleUseCase interface {
List(ctx context.Context, req *ListRolesRequest) ([]RoleDTO, int64, error)
All(ctx context.Context, tenantID string) ([]RoleDTO, error)
GetByID(ctx context.Context, tenantID, id string) (*RoleDTO, error)
Create(ctx context.Context, req *CreateRoleRequest) error
Update(ctx context.Context, id string, req *UpdateRoleRequest) error
Delete(ctx context.Context, tenantID, id string) error
}
// --- RolePermission ---
type RolePermissionUseCase interface {
2026-05-20 07:01:08 +00:00
Get(ctx context.Context, tenantID, roleID string) (enum.Permissions, error) // name <20> <> open/close
Replace(ctx context.Context, tenantID, roleID string, permNames []string) error // <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> N
2026-05-19 13:56:59 +00:00
}
// --- UserRole ---
type UserRoleUseCase interface {
GetByUID(ctx context.Context, tenantID, uid string) ([]UserRoleDTO, error)
2026-05-20 07:01:08 +00:00
GetRoleKeys(ctx context.Context, tenantID, uid string) ([]string, error) // Middleware <20> Ρ A<CEA1> <41> cache
2026-05-19 17:04:26 +00:00
Assign(ctx context.Context, tenantID, uid, roleID string, source enum.RoleSource) error
2026-05-19 13:56:59 +00:00
Revoke(ctx context.Context, tenantID, uid, roleID string) error
2026-05-20 07:01:08 +00:00
Replace(ctx context.Context, tenantID, uid string, roleIDs []string, source enum.RoleSource) error // <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> N<EFBFBD> u<EFBFBD> <75> source<63> v<EFBFBD> <76> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
}
2026-05-20 07:01:08 +00:00
// --- <20> ~<7E> <> <EFBFBD> M<EFBFBD> g ---
2026-05-19 13:56:59 +00:00
type RoleMappingUseCase interface {
List(ctx context.Context, tenantID string) ([]RoleMappingDTO, error)
Upsert(ctx context.Context, req *UpsertRoleMappingRequest) error
Delete(ctx context.Context, tenantID, id string) error
SyncFromZitadelClaims(ctx context.Context, req *SyncFromZitadelRequest) error
SyncFromScimGroup(ctx context.Context, req *SyncFromScimGroupRequest) error
SyncFromLDAPGroups(ctx context.Context, req *SyncFromLDAPGroupsRequest) error
}
2026-05-20 07:01:08 +00:00
// --- <20> E<EFBFBD> X<EFBFBD> d<EFBFBD> ߡ]<5D> e<EFBFBD> ݵ<EFBFBD> ?<3F> ^---
2026-05-19 13:56:59 +00:00
type AuthorizationQueryUseCase interface {
GetMyPermissions(ctx context.Context, tenantID, uid string) (enum.Permissions, error)
GetMyRoles(ctx context.Context, tenantID, uid string) ([]string, error)
}
```
2026-05-20 07:01:08 +00:00
> **<2A> ᨾ<F3AFB2A4> b**<2A> G<EFBFBD> Ҧ<EFBFBD> mutation usecase<73> ]Role*, RolePermission*, UserRole*, RoleMapping*<2A> ^<5E> i<EFBFBD> J<EFBFBD> ɥ<EFBFBD> <C9A5> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> target ID <20> ݩ<EFBFBD> `tenantID`<60> Frepository <20> d<EFBFBD> ߤ@<40> ߱a `{tenant_id, _id}`<60> A<EFBFBD> 䤣<EFBFBD> <E4A4A3> <EFBFBD> ^ `ErrRoleNotInTenant` / `ErrUserRoleNotInTenant`<60> C
> Logic <20> h**<2A> T<EFBFBD> <54> **<2A> <> path <20> <> `:id` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> usecase <20> Ӥ<EFBFBD> <D3A4> a `tenant_id`<60> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 6.9 Middleware <20> <> <EFBFBD> v<EFBFBD> y<EFBFBD> {
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Request<EFBFBD> ]JwtRevokeMiddleware <20> w<EFBFBD> <77> <EFBFBD> L JWT + auth_gen<65> ^
1. <20> <> ctx.tenant_id, ctx.uid
2. userRoleUC.GetRoleKeys <20> <> []Role.Key<65> ]<5D> <> perm:user_roles cache<68> ^
3. <20> <> <EFBFBD> C<EFBFBD> <43> roleKey enforce(tenantID, roleKey, path, method)<29> F
<20> E<EFBFBD> X<EFBFBD> Ҧ<EFBFBD> allow <20> <> <EFBFBD> G<EFBFBD> <47> []CheckResult
4. <20> Y<EFBFBD> L<EFBFBD> <4C> <EFBFBD> <EFBFBD> allow <20> <> 403 Forbidden
5. <20> E<EFBFBD> X<EFBFBD> W<EFBFBD> h<EFBFBD> G
- PermissionNames = <20> Ҧ<EFBFBD> allow <20> R<EFBFBD> <52> <EFBFBD> <EFBFBD> permission.Name<6D> ]<5D> h<EFBFBD> <68> <EFBFBD> ^
- PlainCode = <20> <> <EFBFBD> C<EFBFBD> өR<D3A9> <52> permission<6F> A<EFBFBD> B<EFBFBD> ~ enforce
(permission.Name + ".plain_code") <20> <> <EFBFBD> <EFBFBD> <EFBFBD> F<EFBFBD> <46> <EFBFBD> @<40> q<EFBFBD> L <20> <> true
6. <20> `<60> J ctx.permission_names, ctx.plain_code
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
> **PlainCode <20> <> <EFBFBD> @**<2A> G`*.plain_code` <20> P<EFBFBD> @<40> <> leaf <20> @<40> ˼g<CBBC> J Casbin policy<63> FCheck <20> ɥD permission <20> R<EFBFBD> <52> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> ΦP<CEA6> @ `(tenantID, roleKey, path, method)` <20> A<EFBFBD> <41> <EFBFBD> @<40> <> <EFBFBD> a `.plain_code` <20> <> EnforceEx<45> C<EFBFBD> S<EFBFBD> <53> plain_code <20> <> <EFBFBD> <EFBFBD> <20> <> false<73> C
> Logic <20> hŪ `ctx.plain_code` <20> M<EFBFBD> w<EFBFBD> O<EFBFBD> _<EFBFBD> ^<5E> ǩ<EFBFBD> <C7A9> X<EFBFBD> <58> <EFBFBD> <EFBFBD> <EFBFBD> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> **Platform Admin bypass** <20> <> `JwtRevokeMiddleware` <20> <> 0 <20> B<EFBFBD> B<EFBFBD> z<EFBFBD> ]<5D> <> <20> <> 4.6<EFBFBD> ^<5E> A<EFBFBD> <41> <EFBFBD> i<EFBFBD> o<EFBFBD> Ӭy<D3AC> {<7B> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 6.10 <20> ~<7E> <> Group / Role <20> M<EFBFBD> g
2026-05-19 13:56:59 +00:00
```go
type RoleMapping struct {
TenantID string
ExternalSource enum.RoleSource // zitadel | ldap | scim
ExternalKey string // ZITADEL role / LDAP group DN / SCIM group id
2026-05-20 07:01:08 +00:00
InternalRoleID string // <20> <> <EFBFBD> <EFBFBD> Role._id hex
InternalRoleKey string // denormalized Role.Key<65> A<EFBFBD> <41> <EFBFBD> K<EFBFBD> d<EFBFBD> P<DFBB> f<EFBFBD> p
2026-05-19 13:56:59 +00:00
}
// Index: { tenant_id, external_source, external_key } unique
```
2026-05-20 07:01:08 +00:00
| <20> ӷ<EFBFBD> | ExternalKey <20> d<EFBFBD> <64> | <20> M<EFBFBD> g<EFBFBD> <67> |
2026-05-19 13:56:59 +00:00
|------|------------------|--------|
| ZITADEL | `org_admin` | `tenant_admin` |
2026-05-20 07:01:08 +00:00
| LDAP (AD) | `CN=CloudEP-Admins,OU=Groups,DC=acme,DC=com` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ۭq Role.Key |
| LDAP (OpenLDAP) | `cn=admins,ou=groups,dc=acme,dc=com` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ۭq Role.Key |
| SCIM Group | `group-uuid-xxx` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ۭq Role.Key |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> <EFBFBD> B2B <20> <> <EFBFBD> <EFBFBD> <EFBFBD> z<DEB2> <7A> <EFBFBD> b<EFBFBD> <62> <EFBFBD> x<EFBFBD> ]<5D> w<EFBFBD> ]<5D> ݩR<DDA9> <52> `permission.role.write` <20> <> <EFBFBD> <EFBFBD> API<50> ^<5E> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
#### <20> ~<7E> <> <EFBFBD> ӷ<EFBFBD> <D3B7> P<EFBFBD> B<EFBFBD> W<EFBFBD> h<EFBFBD> ]<5D> קK<D7A7> ~<7E> <> manual <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
`SyncFromZitadelClaims` / `SyncFromScimGroup` / `SyncFromLDAPGroups` <20> @<40> ߥH ** `source` <20> <> <EFBFBD> <EFBFBD> **<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> q<EFBFBD> <71> <EFBFBD> N<EFBFBD> G
2026-05-19 17:04:26 +00:00
```
UserRoleUC.Replace(tenantID, uid, roleIDs, source = zitadel)
2026-05-20 07:01:08 +00:00
<20> <> DELETE user_roles WHERE tenant_id=? AND uid=? AND source='zitadel'
<20> <> INSERT <20> s<EFBFBD> <73> roleIDs<44> ]source='zitadel'<27> ^
<20> <> source='manual' / 'scim' / 'ldap' <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> T
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
> <EFBFBD> <EFBFBD> <EFBFBD> ӷ<EFBFBD> <EFBFBD> Ĭ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> h<EFBFBD> GUserRole <20> <> <EFBFBD> u<EFBFBD> ö<EFBFBD> <C3B6> v<EFBFBD> A<EFBFBD> <41> <EFBFBD> @ source <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> role <20> Y<EFBFBD> ͮġFrevoke <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> w source<63> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 6.11 <20> v<EFBFBD> <76> <EFBFBD> ܧ<EFBFBD> <DCA7> ͮ<EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> ƥ<EFBFBD> | <20> ʧ@ |
2026-05-19 13:56:59 +00:00
|------|------|
2026-05-20 07:01:08 +00:00
| RolePermission Create/Delete | `LoadPolicy(tenant_id)` + `perm:role_perms:*` <20> ֨<EFBFBD> <D6A8> <EFBFBD> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
| Role Create/Update/Delete | `LoadPolicy(tenant_id)` |
2026-05-19 17:04:26 +00:00
| UserRole Assign/Revoke | ** `INCR auth:gen` ** + `LoadPolicy(tenant_id)` |
2026-05-20 07:01:08 +00:00
| SCIM / LDAP Group <20> ܧ<EFBFBD> | <20> <> <EFBFBD> s user_roles <20> <> `LoadPolicy` + ** `INCR auth_gen` ** |
| Permission status <20> ܧ<EFBFBD> <DCA7> ]<5D> <> <EFBFBD> x<EFBFBD> ^ | `LoadAllPolicies()` + <20> v<EFBFBD> <76> <EFBFBD> ֨<EFBFBD> <D6A8> <EFBFBD> <EFBFBD> ġF<C4A1> Y<EFBFBD> ܧ<EFBFBD> <DCA7> v<EFBFBD> T<EFBFBD> n<EFBFBD> J<EFBFBD> <4A> <EFBFBD> A<EFBFBD> A batch `INCR auth_gen` |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
#### <20> h Pod <20> P<EFBFBD> B<EFBFBD> <42> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
```
Channel: casbin:reload
2026-05-20 07:01:08 +00:00
Payload: { "tenant_id": "xxx", "ts": 1716120000000 } # tenant_id == "*" <20> N<EFBFBD> <4E> <EFBFBD> <EFBFBD> <EFBFBD> q
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
- **<2A> Y<EFBFBD> ɳq<C9B3> D**<2A> GPub/Sub
- Writer<65> G<EFBFBD> C<EFBFBD> <43> `LoadPolicy(tenant_id)` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> `PUBLISH casbin:reload {tenant_id}`
- Subscriber<65> G<EFBFBD> C<EFBFBD> <43> pod <20> Ұʮ<D2B0> `SUBSCRIBE` <EFBFBD> F<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> b<EFBFBD> O<EFBFBD> <EFBFBD> <EFBFBD> 餤 reload <20> <> <EFBFBD> <EFBFBD> tenant <20> <> policy
- **<2A> ©<EFBFBD> **<2A> G<EFBFBD> C pod <20> ҰʱƵ{ `5min` <20> <> <EFBFBD> q `LoadAllPolicies()` <EFBFBD> F<EFBFBD> <EFBFBD> pub message <20> |<7C> <> <EFBFBD> ]pod <20> Ұ<EFBFBD> <D2B0> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> BRedis <20> s<EFBFBD> u<EFBFBD> ݰʡ^
- **<2A> <> <EFBFBD> <EFBFBD> **<2A> Greload <20> γ <EFBFBD> <CEB3> @ mutex per tenant<6E> F<EFBFBD> P<EFBFBD> ɬq<C9AC> h<EFBFBD> <68> message <20> uIJ<75> o<EFBFBD> @<40> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> IO
- **<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> Ұ<EFBFBD> **<2A> Gpod <20> Ұʥ<D2B0> <CAA5> <EFBFBD> <EFBFBD> @<40> <> `LoadAllPolicies()` <EFBFBD> A<EFBFBD> A<EFBFBD> }<7D> l SUBSCRIBE
- <20> ]<5D> w<EFBFBD> G`Permission.PolicySyncInterval: 5m`<60> B`Permission.PolicyReloadChannel: casbin:reload`
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 6.12 B2C vs B2B <20> v<EFBFBD> <76> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> | Role <20> ۩w<DBA9> q | Permission <20> Ŀ<EFBFBD> | API <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 17:04:26 +00:00
|----------|-------------|-----------------|----------|
2026-05-20 07:01:08 +00:00
| **B2C** | ** <EFBFBD> <EFBFBD> <EFBFBD> i**<2A> ]<5D> <> Ū seed <20> ҪO<D2AA> ^ | <20> T<EFBFBD> w<EFBFBD> A<EFBFBD> <41> <EFBFBD> i<EFBFBD> <69> | <20> T<EFBFBD> <54> `POST/PUT/DELETE /permissions/roles*` |
| **B2B** | ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ۩w<EFBFBD> q** | <20> q<EFBFBD> <71> <EFBFBD> <EFBFBD> Catalog <20> ۥѤĿ<D1A4> | <20> <> <EFBFBD> <EFBFBD> permission API |
| **Hybrid** | <20> <> tenant.type <20> <> <EFBFBD> <EFBFBD> <EFBFBD> P<EFBFBD> _ | B2B <20> q<EFBFBD> i<EFBFBD> ۩w<DBA9> q | middleware <20> ˬd tenant <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
B2C <20> <> <EFBFBD> <EFBFBD> <EFBFBD> إ߮ɥu seed <20> T<EFBFBD> w Role<6C> ]<5D> p `member` <EFBFBD> B`viewer`<60> ^<5E> A**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ** Role CRUD <20> P Permission <20> Ŀ<EFBFBD> API<50> ]Casbin <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> J seed <20> <> <EFBFBD> G<EFBFBD> ^<5E> C
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 7. API <20> W<EFBFBD> <57>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> ɮס G`generate/api/`
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 7.1 auth.api<70> ]<5D> <> <EFBFBD> } / <20> <> JWT <20> <> API <20> өw<D3A9> ^
2026-05-19 13:56:59 +00:00
2026-05-21 06:45:35 +00:00
> **<2A> w<EFBFBD> <77> <EFBFBD> @**<2A> ]2026-05-21<32> ^<5E> G<EFBFBD> U<EFBFBD> <55> <EFBFBD> u<EFBFBD> <75> <EFBFBD> A<EFBFBD> v<EFBFBD> <76> ?<3F> `<60> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> ШD/<2F> ^<5E> <> <EFBFBD> <EFBFBD> [auth-unified-registration.md <20> <> 4](./auth-unified-registration.md#4-api-<2D> W<EFBFBD> <57> generateapiauthapi) <20> P `generate/api/auth.api`<60> C
| Method | Path | <20> <> <EFBFBD> <EFBFBD> | Ų<> v | <20> <> <EFBFBD> A |
|--------|------|------|------|------|
| POST | `/api/v1/auth/register` | Email + <20> K<EFBFBD> X<EFBFBD> <58> <EFBFBD> U<EFBFBD> ]ZITADEL + member + registration OTP<54> ^ | <20> <> <EFBFBD> } | ? |
| POST | `/api/v1/auth/register/confirm` | <20> T<EFBFBD> { registration OTP <20> <> CloudEP JWT | <20> <> <EFBFBD> } | ? |
| POST | `/api/v1/auth/register/resend` | <20> <> <EFBFBD> H registration OTP | <20> <> <EFBFBD> } | ? |
| POST | `/api/v1/auth/register/social/start` | Social ** <EFBFBD> <EFBFBD> <EFBFBD> U** start<72> ]<5D> t invite session<6F> ^ | <20> <> <EFBFBD> } | ? |
| GET | `/api/v1/auth/register/social/callback` | Social ** <EFBFBD> <EFBFBD> <EFBFBD> U** OAuth callback <20> <> JWT | <20> <> <EFBFBD> } | ? |
| POST | `/api/v1/auth/login` | Email + <20> K<EFBFBD> X<EFBFBD> n<EFBFBD> J<EFBFBD> ]ZITADEL ROPG <20> <> JWT<57> ^ | <20> <> <EFBFBD> } | ? |
| POST | `/api/v1/auth/login/social/start` | Social ** <EFBFBD> n<EFBFBD> J** start<72> ]<5D> L invite<74> ^ | <20> <> <EFBFBD> } | ? |
| GET | `/api/v1/auth/login/social/callback` | Social ** <EFBFBD> n<EFBFBD> J** OAuth callback <20> <> JWT | <20> <> <EFBFBD> } | ? |
| POST | `/api/v1/auth/token/refresh` | <20> <> <EFBFBD> s JWT | <20> <> <EFBFBD> }<7D> ]<5D> a refresh<73> ^ | ? |
| POST | `/api/v1/auth/token/exchange` | ZITADEL `id_token` <20> <> CloudEP JWT<57> ]<5D> <> <EFBFBD> ~ SSO<53> ^ | <20> <> <EFBFBD> } | ? |
| POST | `/api/v1/auth/logout` | <20> n<EFBFBD> X<EFBFBD> ]jti <20> ¦W<C2A6> <57> <EFBFBD> ^ | JWT | ? |
| POST | `/api/v1/auth/revoke-all` | <20> M<EFBFBD> P<EFBFBD> ۤv<DBA4> Ҧ<EFBFBD> session<6F> ]INCR auth_gen<65> ^ | JWT + Step-up `revoke_all_sessions` | <20> W<EFBFBD> <57> <EFBFBD> <EFBFBD> |
| POST | `/api/v1/auth/step-up/start` | <20> Ұ<EFBFBD> step-up MFA<46> A<EFBFBD> H OTP | JWT | <20> W<EFBFBD> <57> <EFBFBD> <EFBFBD> |
| POST | `/api/v1/auth/step-up/confirm` | <20> T<EFBFBD> { OTP <20> <> ñ<> o<EFBFBD> u<EFBFBD> <75> `step_up_token` | JWT | <20> W<EFBFBD> <57> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 7.2 member.api<70> ]<5D> <> JWT + Casbin<69> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| Method | Path | Casbin <20> R<EFBFBD> <52> Permission<6F> ]<5D> ܨҡ^ | Step-up |
2026-05-19 17:04:26 +00:00
|--------|------|-------------------------------|---------|
2026-05-20 07:01:08 +00:00
| GET | `/api/v1/members/me` | `member.info.select` | <20> X |
| PATCH | `/api/v1/members/me` | `member.info.update` | <20> X |
| PATCH | `/api/v1/members/me/business-email` | `member.info.update` | ? `change_business_email` |
| PATCH | `/api/v1/members/me/business-phone` | `member.info.update` | ? `change_business_phone` |
| DELETE | `/api/v1/members/me` | `member.info.delete` | ? `delete_member` |
| POST | `/api/v1/members/me/verifications/email/start` | `member.info.update` | <20> X |
| POST | `/api/v1/members/me/verifications/email/confirm` | `member.info.update` | <20> X |
| POST | `/api/v1/members/me/verifications/phone/start` | `member.info.update` | <20> X |
| POST | `/api/v1/members/me/verifications/phone/confirm` | `member.info.update` | <20> X |
| GET | `/api/v1/members/me/totp` | `member.info.select` | <20> X |
| POST | `/api/v1/members/me/totp/enroll-start` | `member.info.update` | <20> X |
| POST | `/api/v1/members/me/totp/enroll-confirm` | `member.info.update` | <20> X |
| POST | `/api/v1/members/me/totp/backup-codes` | `member.info.update` | ? `disable_totp` |
| DELETE | `/api/v1/members/me/totp` | `member.info.update` | ? `disable_totp` |
| GET | `/api/v1/members` | `member.admin.list` | <20> X |
| GET | `/api/v1/members/:uid` | `member.admin.read` | <20> X |
| PATCH | `/api/v1/members/:uid` | `member.admin.update` | <20> X |
| PATCH | `/api/v1/members/:uid/status` | `member.admin.status` | ? `tenant_admin_force_status` |
> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> <EFBFBD> **Casbin <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> path + method** <20> M<EFBFBD> w<EFBFBD> A<EFBFBD> D<EFBFBD> w<EFBFBD> s<EFBFBD> X permission <20> r<EFBFBD> <72> <EFBFBD> C
> Step-up <20> 欰?<3F> ̻ݦb Header <20> a `X-Step-Up-Token`<60> A<EFBFBD> B token claim <20> <> `action` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> P<EFBFBD> <50> <EFBFBD> C action <20> @<40> P<EFBFBD> ]<5D> <> <20> <> 5.6<EFBFBD> ^<5E> C
### 7.3 permission.api<70> ]<5D> <> JWT + Casbin<69> ^
| Method | Path | <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
|--------|------|------|
2026-05-20 07:01:08 +00:00
| GET | `/api/v1/permissions/catalog` | <20> <> <EFBFBD> <EFBFBD> Permission Tree<65> ]open <20> `<60> I<EFBFBD> ^ |
| GET | `/api/v1/permissions/me` | <20> <> <EFBFBD> e<EFBFBD> ϥΪ̪<CEAA> permission name <20> <> status map |
| GET | `/api/v1/permissions/roles` | <20> C<EFBFBD> X<EFBFBD> <58> <EFBFBD> <EFBFBD> Role |
| POST | `/api/v1/permissions/roles` | <20> إ<EFBFBD> Role<6C> ]B2B<32> ^ |
| PUT | `/api/v1/permissions/roles/:id` | <20> <> <EFBFBD> s Role |
| DELETE | `/api/v1/permissions/roles/:id` | <20> R<EFBFBD> <52> Role |
| GET | `/api/v1/permissions/roles/:id/permissions` | <20> <> <EFBFBD> o Role <20> Ŀ諸 Permission |
| PUT | `/api/v1/permissions/roles/:id/permissions` | <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> N Role <20> Ŀ<EFBFBD> `{ "permission_names": [...] }` <EFBFBD> ]PermissionTree <20> <> <EFBFBD> <EFBFBD> + <20> <> parent<6E> ^ |
| GET | `/api/v1/permissions/users/:uid/roles` | <20> d<EFBFBD> ϥΪ̨<CEAA> <CCA8> <EFBFBD> |
| POST | `/api/v1/permissions/users/:uid/roles` | <20> <> <EFBFBD> <EFBFBD> Role `{ "role_id": "..." }` |
| DELETE | `/api/v1/permissions/users/:uid/roles/:role_id` | <20> M<EFBFBD> P Role |
| GET | `/api/v1/permissions/role-mappings` | <20> ~<7E> <> Group <20> M<EFBFBD> g<EFBFBD> C<EFBFBD> <43> |
| PUT | `/api/v1/permissions/role-mappings` | <20> s<EFBFBD> W/<2F> <> <EFBFBD> s<EFBFBD> M<EFBFBD> g |
| POST | `/api/v1/permissions/policy/reload` | <20> <> <EFBFBD> <EFBFBD> IJ<EFBFBD> o LoadPolicy<63> ]<5D> <> <EFBFBD> x Admin<69> ^ |
### 7.4 tenant.api<70> ]<5D> <> <EFBFBD> x / <20> <> <EFBFBD> <EFBFBD> Admin<69> ^
| Method | Path | Casbin <20> R<EFBFBD> <52> Permission<6F> ]<5D> ܨҡ^ |
2026-05-19 13:56:59 +00:00
|--------|------|-------------------------------|
| POST | `/api/v1/admin/tenants` | `system.tenant.create` |
| GET | `/api/v1/admin/tenants/:tenant_id` | `tenant.read` |
| PUT | `/api/v1/admin/tenants/:tenant_id/ldap` | `tenant.ldap.write` |
| POST | `/api/v1/admin/tenants/:tenant_id/directory-sync` | `tenant.sync.trigger` |
2026-05-20 07:01:08 +00:00
### 7.5 scim.api<70> ]SCIM Bearer Token<65> A<EFBFBD> D JWT<57> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
**<2A> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> G<EFBFBD> H ** `tenant_id` ** <20> <> path <20> Ѽơ]<5D> <> <EFBFBD> Τ l<CEA4> <6C> <EFBFBD> W<EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-19 13:56:59 +00:00
```
/scim/v2/tenants/{tenant_id}/Users
/scim/v2/tenants/{tenant_id}/Groups
2026-05-19 17:04:26 +00:00
/scim/v2/tenants/{tenant_id}/ServiceProviderConfig
/scim/v2/tenants/{tenant_id}/Schemas
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
<EFBFBD> {<7B> ҡG`Authorization: Bearer {tenant_scim_token}`<60> ]hash <20> s<EFBFBD> <73> tenant <20> ]<5D> w<EFBFBD> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
- `{tenant_id}` = ZITADEL `org_id` <EFBFBD> A<EFBFBD> P JWT `tenant_id` <20> @<40> P
- SCIM <20> ШD<D0A8> <44> <EFBFBD> <EFBFBD> CloudEP JWT<57> F<EFBFBD> <46> <EFBFBD> v<EFBFBD> <76> tenant <20> <> SCIM token + <20> i<EFBFBD> <69> Casbin <20> Ӥ<EFBFBD>
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 8. Middleware <20> <>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 8.1 <20> @<40> <> <EFBFBD> <EFBFBD> <EFBFBD> O<EFBFBD> @ API
2026-05-19 13:56:59 +00:00
2026-05-21 06:45:35 +00:00
**<2A> ثe<D8AB> w<EFBFBD> <77> <EFBFBD> @<40> ]member <20> Ҳա ^<5E> G**
```
Request
<20> <> CloudEPJWT middleware<72> ]<5D> i<EFBFBD> <69> Bearer access JWT <20> <> <20> `<60> J tenant_id + uid <20> <> context<78> ^
<20> <> member handler<65> G<EFBFBD> Y context <20> L actor<6F> Afallback dev headers X-Tenant-ID + X-UID<49> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> }<7D> o<EFBFBD> ^
<20> <> handler <20> <> logic <20> <> usecase
```
**<2A> ؼЧ<D8BC> <D0A7> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]Casbin / permission <20> ҲմN<D5B4> <4E> <EFBFBD> <EFBFBD> <EFBFBD> ^<5E> G**
2026-05-19 13:56:59 +00:00
```
Request
2026-05-20 07:01:08 +00:00
<20> <> go-zero JWT <20> <> ñ
<20> <> JwtRevokeMiddleware<72> ]jti <20> ¦W<C2A6> <57> + auth_gen<65> ^
<20> <> TenantContextMiddleware<72> ]<5D> <> <EFBFBD> <EFBFBD> tenant_id <20> @<40> P<EFBFBD> ^
<20> <> CasbinRBACMiddleware<72> ]tenant_id <20> <> role_key <20> <> path <20> <> method <20> <> Allow<6F> ^
<20> <> handler <20> <> logic <20> <> usecase
2026-05-19 13:56:59 +00:00
```
### 8.2 CasbinRBACMiddleware
2026-05-20 07:01:08 +00:00
> Platform Admin bypass <20> b<EFBFBD> e<EFBFBD> @<40> h `JwtRevokeMiddleware` <20> <> 0 <20> B<EFBFBD> B<EFBFBD> z<EFBFBD> ]<5D> <> 4.6<EFBFBD> ^<5E> A<EFBFBD> <41> <EFBFBD> B<EFBFBD> <42> <EFBFBD> <EFBFBD> <EFBFBD> ơC
2026-05-19 17:04:26 +00:00
2026-05-19 13:56:59 +00:00
```go
2026-05-20 07:01:08 +00:00
// ?<3F> N?
2026-05-19 17:04:26 +00:00
roleKeys, _ := userRoleUC.GetRoleKeys(ctx, tenantID, uid)
var hits []rbac.CheckResult
for _, roleKey := range roleKeys {
res, _ := rbacUC.Check(ctx, & rbac.CheckRequest{
2026-05-19 13:56:59 +00:00
TenantID: tenantID, UID: uid,
2026-05-19 17:04:26 +00:00
RoleKey: roleKey, Path: r.URL.Path, Method: r.Method,
2026-05-19 13:56:59 +00:00
})
2026-05-19 17:04:26 +00:00
if res.Allow {
hits = append(hits, res)
2026-05-19 13:56:59 +00:00
}
}
2026-05-19 17:04:26 +00:00
if len(hits) == 0 {
httpx.Error(w, forbidden)
return
}
2026-05-20 07:01:08 +00:00
names, plain := aggregate(hits) // <20> h<EFBFBD> <68> + PlainCode OR
2026-05-19 17:04:26 +00:00
ctx = withPermissionNames(ctx, names)
ctx = withPlainCode(ctx, plain)
next(w, r)
2026-05-19 13:56:59 +00:00
```
### 8.3 SCIM API
```
Request
2026-05-20 07:01:08 +00:00
<20> <> ScimAuthMiddleware<72> ]tenant_scim_token<65> ^
<20> <> TenantContextMiddleware
<20> <> handler
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### 8.4 Logic <20> h<EFBFBD> ɥR<C9A5> <52> <EFBFBD> v
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
Casbin <20> B<EFBFBD> z **API <20> <> ** <20> <> <EFBFBD> v<EFBFBD> CLogic <20> <> <EFBFBD> i<EFBFBD> l<EFBFBD> [ ** <EFBFBD> 귽<EFBFBD> <EFBFBD> ** <20> P<EFBFBD> _<EFBFBD> G
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
- `member.info.select` vs <20> d<EFBFBD> L<EFBFBD> H<EFBFBD> G<EFBFBD> Y path <20> t `:uid` <20> B uid <20> <> caller<65> A<EFBFBD> ݩR<DDA9> <52> `member.admin.read`
- `PlainCode` <EFBFBD> GLogic Ū `ctx.plain_code` <EFBFBD> A<EFBFBD> M<EFBFBD> w<EFBFBD> O<EFBFBD> _<EFBFBD> ^<5E> ǩ<EFBFBD> <C7A9> X<EFBFBD> <58> <EFBFBD> <EFBFBD>
- **Step-up <20> u<EFBFBD> <75> **<2A> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> I action<6F> ^<5E> G
1. <20> q Header `X-Step-Up-Token` <20> <> token
2026-05-19 17:04:26 +00:00
2. `auth.StepUpTokenUseCase.Verify(token, expectedAction, tenantID, uid)`
2026-05-20 07:01:08 +00:00
- <20> <> `typ == "step_up"` <EFBFBD> B`action == expectedAction`<60> B`tenant_id` / `uid` <20> P ctx <20> @<40> P<EFBFBD> B<EFBFBD> <42> <EFBFBD> L<EFBFBD> <4C>
3. `SETNX auth:stepup:used:{jti}=1` <EFBFBD> A<EFBFBD> w<EFBFBD> s<EFBFBD> b <20> <> `403 step_up_replay`
4. <20> <> <EFBFBD> <EFBFBD> <EFBFBD> q<EFBFBD> L <20> <> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ~<7E> Ⱦާ@
5. <20> <> <EFBFBD> <EFBFBD> <20> <> `403 step_up_required` + `{ required_action: "<action>" }`
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 9. <20> ֤߬y<DFAC> {
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 9.1 <20> n<EFBFBD> J / <20> <> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-21 06:45:35 +00:00
#### 9.1.1 Email + <20> K<EFBFBD> X<EFBFBD> n<EFBFBD> J<EFBFBD> ]<5D> w<EFBFBD> <77> <EFBFBD> @<40> ^
```
Client <20> <> POST /api/v1/auth/login { tenant_slug, email, password }
1. tenant.ResolveBySlug
2. zitadel.VerifyPassword<72> ]ROPG<50> ^
3. <20> ѪR id_token / userinfo <20> <> zitadel sub
4. member.GetByZitadelUserID <20> <> <20> <> <EFBFBD> <EFBFBD> member_status == active
5. auth.IssuePair
Client <20> <> { access_token, refresh_token, uid }
```
#### 9.1.2 ZITADEL id_token <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]SSO / <20> <> client<6E> A<EFBFBD> w<EFBFBD> <77> <EFBFBD> @<40> ^
```
Client <20> <> POST /api/v1/auth/token/exchange { tenant_slug, id_token }
1. zitadel.VerifyIDToken<65> ]JWKS <20> <> ñ + iss/aud/exp<78> ^
2. tenant.ResolveBySlug
3. member.GetByZitadelUserID <20> <> <20> <> <EFBFBD> <EFBFBD> active
4. auth.IssuePair
Client <20> <> { access_token, refresh_token, uid }
```
#### 9.1.3 OIDC <20> n<EFBFBD> J + JIT<49> ]B2B / <20> <> B2C Hosted UI <20> <> <EFBFBD> |<7C> A<EFBFBD> <41> <EFBFBD> 䴩<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Client <20> <> ZITADEL OIDC Login<69> ]<5D> t LDAP IdP<64> ^
Client <20> <> POST /auth/token/exchange { tenant_slug, id_token }
2026-05-19 13:56:59 +00:00
1. zitadel.VerifyIDToken
2026-05-20 07:01:08 +00:00
2. tenant.ResolveBySlug <20> <> <20> <> <EFBFBD> <EFBFBD> org_id
2026-05-21 06:45:35 +00:00
3. member.EnsureFromOIDC <20> <> uid<69> ]<5D> p AMEX-10000000<30> ^ // <20> Y member <20> <> <EFBFBD> s<EFBFBD> b<EFBFBD> h JIT
4. permission.SyncFromZitadelClaims <20> <> user_roles // <20> W<EFBFBD> <57> <EFBFBD> <EFBFBD>
5. auth.IssueTokenPair
2026-05-20 07:01:08 +00:00
Client <20> <> { access_token, refresh_token, uid }
2026-05-19 13:56:59 +00:00
```
2026-05-21 06:45:35 +00:00
> **<2A> `<60> N**<2A> GB2C <20> s<EFBFBD> <73> <EFBFBD> U<EFBFBD> <55> <EFBFBD> <EFBFBD> <20> <> 3.4 Gateway `/auth/register*`<60> F`/auth/token/exchange` <20> O<EFBFBD> d<EFBFBD> <64> **<2A> w<EFBFBD> s<EFBFBD> b member** <20> <> SSO <20> n<EFBFBD> J<EFBFBD> P<EFBFBD> <50> <EFBFBD> ~ IdP<64> C
2026-05-20 07:01:08 +00:00
### 9.2 <20> <> <EFBFBD> O<EFBFBD> @ API
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Client <20> <> GET /api/v1/members/me (Bearer access_jwt)
1. JWT + <20> ¦W<C2A6> <57> + auth_gen
2. CasbinRBACMiddleware <20> <> Check(role, "/api/v1/members/me", "GET")
2026-05-19 13:56:59 +00:00
3. member.GetByUID
```
2026-05-20 07:01:08 +00:00
### 9.3 B2B <20> ۩w<DBA9> q Role + <20> Ŀ<EFBFBD> Permission
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Tenant Admin <20> <> PUT /api/v1/permissions/roles/{id}/permissions
2026-05-19 17:04:26 +00:00
{ "permission_names": ["member.admin.list", "member.admin.read"] }
2026-05-20 07:01:08 +00:00
<20> <> RolePermissionUC.Replace<63> ]<5D> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> N<EFBFBD> ^
<20> <> PermissionTree.getFullParentPermissionIDs<44> ]<5D> ۰ ʸ<DBB0> parent<6E> ^
<20> <> RBACUC.LoadPolicy(tenant_id) + <20> s<EFBFBD> <73> reload<61> ]<5D> <> <20> <> 6.11<EFBFBD> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
Tenant Admin <20> <> POST /api/v1/permissions/users/{uid}/roles
2026-05-19 13:56:59 +00:00
{ "role_id": "..." }
2026-05-20 07:01:08 +00:00
<20> <> UserRoleUC.Assign(tenantID, uid, roleID, source=manual)
<20> <> INCR auth_gen + DEL perm:user_roles cache
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### 9.4 <20> <> <EFBFBD> v
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Admin <20> <> PATCH /api/v1/members/:uid/status { status: "suspended" }
2026-05-19 17:04:26 +00:00
Header: X-Step-Up-Token: < step_up_token , action = tenant_admin_force_status >
2026-05-20 07:01:08 +00:00
1. Casbin enforce <20> R<EFBFBD> <52> member.admin.status
2. Logic <20> <> step_up_token + action <20> @<40> P
2026-05-19 17:04:26 +00:00
3. member.UpdateStatus
2026-05-20 07:01:08 +00:00
4. auth.RevokeAllForUser<65> ]INCR auth:gen:{tenant_id}:{uid}<7D> ^
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
### 9.5 <20> ~<7E> <> Email <20> <> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
Client <20> <> POST /api/v1/members/me/verifications/email/start { target: "biz@foo.com" }
1. (<28> i<EFBFBD> <69> ) <20> ˬd target <20> <> <EFBFBD> Q<EFBFBD> P<EFBFBD> <50> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> L member <20> ϥ<EFBFBD>
2. <20> ˬd verify:rate:{tenant}:{uid}:email <20> <> <EFBFBD> s<EFBFBD> b<EFBFBD> ]60s <20> N<EFBFBD> o<EFBFBD> ^
3. <20> ͦ<EFBFBD> 6 <20> X OTP <20> <> bcrypt <20> s verify:otp:{tenant}:{uid}:email:{challenge_id} TTL 5min
2026-05-19 17:04:26 +00:00
4. NotificationClient.Email.Send(target, template=VerifyEmail, data={code})
5. SETEX verify:rate:{tenant}:{uid}:email 60
6. audit log
2026-05-20 07:01:08 +00:00
Client <20> <> { challenge_id, expires_in: 300 }
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
Client <20> <> POST /api/v1/members/me/verifications/email/confirm { challenge_id, code }
1. Ū challenge<67> F<EFBFBD> L<EFBFBD> <4C> <EFBFBD> Υ <EFBFBD> <CEA5> <EFBFBD> 5 <20> <> <20> <> <20> ڵ<EFBFBD>
2. bcrypt compare<72> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <20> <> INCR AttemptCnt <20> <> <20> ڵ<EFBFBD>
3. <20> <> <EFBFBD> \ <20> <> member.BusinessEmail = target, BusinessEmailVerified = true, BusinessEmailVerifiedAt = now
2026-05-19 17:04:26 +00:00
4. DEL challenge
5. audit log
2026-05-20 07:01:08 +00:00
Client <20> <> { verified: true }
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
> phone <20> y<EFBFBD> {<7B> P<EFBFBD> W<EFBFBD> AOTP <20> q<EFBFBD> D<EFBFBD> <44> SMS Provider<65> Ftemplate <20> <> `VerifyPhone`<60> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 9.6 Step-up MFA + <20> <> <EFBFBD> ~<7E> <> Email
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
Client <20> <> POST /api/v1/auth/step-up/start { action: "change_business_email" }
1. <20> q ctx.uid Ū member<65> F<EFBFBD> n<EFBFBD> D BusinessEmailVerified || BusinessPhoneVerified
2. <20> <> <EFBFBD> q<EFBFBD> D<EFBFBD> G<EFBFBD> u<EFBFBD> <75> phone<6E> ]<5D> p<EFBFBD> w verified<65> ^<5E> _<EFBFBD> h email
3. <20> ͦ<EFBFBD> OTP <20> <> <20> H<EFBFBD> X<EFBFBD> ]<5D> B<EFBFBD> J<EFBFBD> P <20> <> 9.5<EFBFBD> ^
Client <20> <> { challenge_id, channel, expires_in: 300 }
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
Client <20> <> POST /api/v1/auth/step-up/confirm { challenge_id, code, action: "change_business_email" }
1. bcrypt <20> <> <EFBFBD> <EFBFBD> <EFBFBD> F<EFBFBD> <46> challenge.kind == step_up && target == action
2. auth.StepUpTokenUseCase.Issue(tenant, uid, action) <20> <> JWT (typ=step_up, action, TTL 5min)
Client <20> <> { step_up_token, token_type: "step_up", expires_in: 300 }
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
Client <20> <> PATCH /api/v1/members/me/business-email { new_email: "new@foo.com" }
2026-05-19 17:04:26 +00:00
Header: X-Step-Up-Token: < step_up_token >
2026-05-20 07:01:08 +00:00
1. Middleware <20> q<EFBFBD> L<EFBFBD> ]<5D> @<40> <> JWT + Casbin<69> ^
2. Logic step-up <20> u<EFBFBD> <75> <EFBFBD> ]<5D> <> <20> <> 8.4<EFBFBD> ^
3. <20> <> <EFBFBD> ] BusinessEmailVerified = false<73> ABusinessEmail = new_email
4. <20> <> <EFBFBD> <EFBFBD> IJ<EFBFBD> o <20> <> 9.5 <20> <> new_email <20> <> <EFBFBD> s<EFBFBD> o OTP<54> ]<5D> Ϊ<EFBFBD> <CEAA> <EFBFBD> <EFBFBD> ^ challenge_id <20> <> <EFBFBD> e<EFBFBD> ݡ^
5. audit log<6F> ]<5D> t<EFBFBD> <74> / <20> s email<69> Bstep_up jti<74> BIP<49> BUA<55> ^
2026-05-19 13:56:59 +00:00
```
---
2026-05-20 07:01:08 +00:00
## 10. LDAP <20> P SCIM
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 10.1 <20> T<EFBFBD> <54> Provisioning <20> <> <EFBFBD> |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> | | <20> A<EFBFBD> <41> | <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
|------|------|------|
2026-05-20 07:01:08 +00:00
| **SCIM <20> <> ZITADEL <20> <> Gateway** | <20> <> HR / Entra ID / Okta | <20> <> <EFBFBD> ~<7E> <> <EFBFBD> e<EFBFBD> ϥΪ<CFA5> |
| **ZITADEL LDAP IdP** | <20> Τ <EFBFBD> <CEA4> n<EFBFBD> J<EFBFBD> <4A> JIT | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> n<EFBFBD> J<EFBFBD> إ<EFBFBD> member |
| **Directory Sync Worker** | <20> L SCIM <20> <> AD / OpenLDAP | <20> w<EFBFBD> ɦP<C9A6> B + <20> <> ¾<EFBFBD> <C2BE> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 10.2 LDAP <20> ]<5D> w<EFBFBD> ]AD + OpenLDAP<41> ^
2026-05-19 13:56:59 +00:00
```go
type TenantLDAPConfig struct {
TenantID string
Type string // "ad" | "openldap"
Host string
Port int
UseTLS bool
BaseDN string
BindDN string // encrypted
BindPassword string // encrypted
UserFilter string
GroupFilter string
AttrMap LDAPAttrMap
}
type LDAPAttrMap struct {
Username string // AD: sAMAccountName / LDAP: uid
Email string // mail
DisplayName string // displayName / cn
Phone string // telephoneNumber
ExternalID string // objectGUID / entryUUID
Groups string // memberOf
}
```
### 10.3 SCIM
2026-05-20 07:01:08 +00:00
- **SCIM `id` = Gateway Member UID**<2A> ]`AMEX-10000000`<60> ^<5E> X <20> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> F<EFBFBD> HŪ<48> B<EFBFBD> <42> <EFBFBD> t<EFBFBD> Τ @<40> P<EFBFBD> A<EFBFBD> K<EFBFBD> <4B> audit/<2F> 䴩<EFBFBD> <E4B4A9> <EFBFBD> e<EFBFBD> d<EFBFBD> <64>
- SCIM `externalId` = <20> Ȥ<EFBFBD> <C8A4> <EFBFBD> IdP / HR <20> t<EFBFBD> δ<EFBFBD> <CEB4> Ѫ<EFBFBD> <D1AA> ~<7E> <> <EFBFBD> ѧO<D1A7> ]<5D> p Okta user id<69> BEntra object id<69> Bemployee id<69> ^
- `externalId` <20> H `{tenant_id, external_id}` <20> <> idempotent upsert key<65> F<EFBFBD> <46> <EFBFBD> i<EFBFBD> <69> <EFBFBD> ]<5D> Ȥ<EFBFBD> <C8A4> ݪ<EFBFBD> <DDAA> D Gateway UID
- ZITADEL `sub` <EFBFBD> BMongo `_id` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ~<7E> n<EFBFBD> S<EFBFBD> FZITADEL `sub` <20> z<EFBFBD> L SCIM Extension Schema `urn:cloudep:scim:2.0:User:zitadelSub` <20> <> <EFBFBD> Ѭd<D1AC> ߡA<DFA1> K<EFBFBD> <4B> <EFBFBD> <EFBFBD> <EFBFBD> ~<7E> <> troubleshoot
- SCIM Groups PATCH <20> <> `permission.SyncFromScimGroup`
- SCIM deactivate <20> <> `member.suspended` + `auth.RevokeAllForUser`
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 10.4 Directory Sync <20> ~<7E> P<EFBFBD> O<EFBFBD> @<40> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> | <20> ]<5D> w | <20> 欰 |
2026-05-19 17:04:26 +00:00
|------|------|------|
2026-05-20 07:01:08 +00:00
| <20> s<EFBFBD> <73> <EFBFBD> 䤣<EFBFBD> <E4A4A3> <EFBFBD> ~<7E> <> <EFBFBD> v | `MissingThreshold: 3` <EFBFBD> ]<5D> s<EFBFBD> <73> 3 <20> <> cron<6F> ^ | <20> p<EFBFBD> Ʃ<EFBFBD> `members.directory_missing_count` <EFBFBD> F<EFBFBD> <EFBFBD> <EFBFBD> _<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Y<EFBFBD> k<EFBFBD> s |
| <20> 榸<EFBFBD> <E6A6B8> <EFBFBD> ʤW<CAA4> <57> | `MaxChangeRatio: 0.20` | <20> 榸 sync <20> <> <EFBFBD> ʶW<CAB6> L<EFBFBD> ӯ<EFBFBD> <D3AF> <EFBFBD> active members 20% <20> <> ** <EFBFBD> j<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> dry-run** + <20> iĵ<69> A<EFBFBD> ݤH<DDA4> u<EFBFBD> T<EFBFBD> { |
| <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> p | `DryRunOnFirstSync: true` | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> P<EFBFBD> B<EFBFBD> u<EFBFBD> O diff log<6F> A**<2A> <> <EFBFBD> g DB** |
| Dry-run <20> Ҧ<EFBFBD> | `DryRun: true / false` | <20> <> <EFBFBD> {<7B> <> <EFBFBD> v<EFBFBD> T DB<44> A<EFBFBD> u<EFBFBD> <75> <EFBFBD> X diff <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]admin API <20> i<EFBFBD> U<EFBFBD> <55> <EFBFBD> ^ |
| <20> n<EFBFBD> R<EFBFBD> ]<5D> <> ¾<EFBFBD> ^ | guardrail <20> <> <EFBFBD> q<EFBFBD> L<EFBFBD> ~ `status=suspended` <EFBFBD> ]**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> deleted**<2A> ^ | `deleted` <20> ݤH<DDA4> u<EFBFBD> α M<CEB1> <4D> workflow |
| Sync window | `Window: 24h` | <20> w<EFBFBD> ]<5D> C 24h<34> F<EFBFBD> i tenant override |
| <20> iĵ<69> q<EFBFBD> D | `AlertSink: ops_webhook / mail` | IJ<> o dry-run / <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ʲv / <20> s<EFBFBD> Ѯɳq<C9B3> <71> |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> Worker <20> Ұʶ<D2B0> <CAB6> ǡG<C7A1> <47> LDAP snapshot <20> <> <20> p<EFBFBD> <70> diff <20> <> <20> ] guardrail <20> ˬd<CBAC> ]threshold + ratio<69> ^<5E> <> commit <20> <> <EFBFBD> <EFBFBD> dry-run <20> <> <20> g audit log<6F> C
2026-05-19 17:04:26 +00:00
2026-05-19 13:56:59 +00:00
---
2026-05-19 17:04:26 +00:00
## 11. Notification Module
2026-05-20 07:01:08 +00:00
<EFBFBD> <EFBFBD> <EFBFBD> |<7C> G`internal/model/notification/`
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> W<EFBFBD> <EFBFBD> model <20> Ҳա A<D5A1> <41> <EFBFBD> <EFBFBD> <EFBFBD> B<EFBFBD> z<EFBFBD> Ҧ<EFBFBD> **outbound <20> q<EFBFBD> T** <EFBFBD> GEmail<EFBFBD> BSMS<EFBFBD> B<EFBFBD> ]<5D> w<EFBFBD> d<EFBFBD> ^Push<73> BWebhook<6F> C<EFBFBD> Ҧ<EFBFBD> <D2A6> ~<7E> ȼҲա ]member <20> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> ҡBauth step-up<75> Btenant <20> t<EFBFBD> γ q<CEB3> <71> <EFBFBD> Badmin ĵ<> ܵ<EFBFBD> <DCB5> ^**<2A> Τ @**<2A> z<EFBFBD> L `NotifierUseCase` <20> o<EFBFBD> e<EFBFBD> A**<2A> <> **<2A> <> <EFBFBD> <EFBFBD> import provider SDK<44> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 11.1 ¾<> d
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
- Provider <20> <> <EFBFBD> H<EFBFBD> GEmail / SMS / Push / Webhook <20> i<EFBFBD> W<EFBFBD> ߴ <EFBFBD> <DFB4> <EFBFBD>
- Template <20> <> <EFBFBD> V<EFBFBD> G<EFBFBD> t<EFBFBD> h<EFBFBD> y<EFBFBD> t<EFBFBD> ]i18n<38> ^+ <20> ܼƪ`<60> J
- <20> P<EFBFBD> B<EFBFBD> o<EFBFBD> e<EFBFBD> P<EFBFBD> <50> <EFBFBD> B<EFBFBD> Ƶ{<7B> ]idempotency + <20> <> <EFBFBD> <EFBFBD> + DLQ<4C> ^
- <20> q<EFBFBD> <71> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Gpersist <20> <> Mongo<67> ]<5D> e<EFBFBD> F<EFBFBD> <46> <EFBFBD> A<EFBFBD> Bprovider message id<69> Bretry <20> y<EFBFBD> <79> <EFBFBD> ^
- Rate limit / <20> t<EFBFBD> B<EFBFBD> ]<5D> <> <EFBFBD> z<EFBFBD> o<EFBFBD> B<EFBFBD> <42> <EFBFBD> ݥΡ ^
- Hook<6F> G<EFBFBD> <47> audit log <20> P metrics <20> d<EFBFBD> I
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 11.2 <20> Ҳ<EFBFBD> <D2B2> <EFBFBD> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
```
member / auth / tenant / admin
2026-05-20 07:01:08 +00:00
<20> x
<20> <> (NotifierUseCase.Send / Enqueue)
notification <20> w<EFBFBD> w repository (audit + outbox)
<20> x
<20> <> (interface)
2026-05-19 17:04:26 +00:00
internal/library/notification/
2026-05-20 07:01:08 +00:00
<20> u<EFBFBD> w<EFBFBD> w email/ (sendgrid | ses | smtp <20> <> <EFBFBD> @)
<20> u<EFBFBD> w<EFBFBD> w sms/ (twilio | sns | smsapi <20> <> <EFBFBD> @)
<20> |<7C> w<EFBFBD> w push/ (<28> w<EFBFBD> d)
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
**library <20> h**<2A> G<EFBFBD> <47> IO<49> A<EFBFBD> ʸ˦U<CBA6> a SDK<44> F**model <20> h**<2A> G<EFBFBD> y<EFBFBD> {<7B> B<EFBFBD> ҪO<D2AA> Bretry<72> Baudit<69> Bidempotency<63> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 11.3 <20> <> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
```go
type NotifierUseCase interface {
2026-05-20 07:01:08 +00:00
// <20> P<EFBFBD> B<EFBFBD> o<EFBFBD> e<EFBFBD> G<EFBFBD> <47> <EFBFBD> o<EFBFBD> <6F> <EFBFBD> G<EFBFBD> P provider id<69> F<EFBFBD> <46> <EFBFBD> Ѧ^ error
2026-05-19 17:04:26 +00:00
Send(ctx context.Context, req *SendRequest) (*NotificationDTO, error)
2026-05-20 07:01:08 +00:00
// <20> <> <EFBFBD> B<EFBFBD> ƶ<EFBFBD> <C6B6> G<EFBFBD> g Mongo outbox + <20> J channel<65> Aworker <20> Ԩ<EFBFBD> <D4A8> <EFBFBD> <EFBFBD> ա F<D5A1> <46> <EFBFBD> ]<5D> R<EFBFBD> <52>
2026-05-19 17:04:26 +00:00
Enqueue(ctx context.Context, req *SendRequest) (*NotificationDTO, error)
2026-05-20 07:01:08 +00:00
// <20> d<EFBFBD> ߳浧<DFB3> <E6B5A7> <EFBFBD> A
2026-05-19 17:04:26 +00:00
Get(ctx context.Context, tenantID, notificationID string) (*NotificationDTO, error)
}
type SendRequest struct {
TenantID string
2026-05-20 07:01:08 +00:00
UID string // <20> i<EFBFBD> <69> <EFBFBD> š]<5D> t<EFBFBD> γ q<CEB3> <71> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
Channel enum.Channel // email | sms | push | webhook
Kind enum.NotifyKind // verify_email | verify_phone | step_up | system_alert | ...
2026-05-20 07:01:08 +00:00
Target string // <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> }<7D> ]email / phone / device_token / url<72> ^
Locale string // zh-tw | en-us<75> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> w<EFBFBD> <77> tenant.default_locale
Data map[string]any // <20> ҪO<D2AA> ܼ<EFBFBD>
2026-05-19 17:04:26 +00:00
Severity enum.Severity // info | warn | critical
2026-05-20 07:01:08 +00:00
IdempotencyKey string // <20> ~<7E> <> key<65> F<EFBFBD> P key <20> <> <EFBFBD> <EFBFBD> <EFBFBD> o
DoNotPersistBody bool // OTP <20> <> <EFBFBD> ӷP<D3B7> <50> <EFBFBD> e<EFBFBD> <65> <EFBFBD> J<EFBFBD> w<EFBFBD> A<EFBFBD> u<EFBFBD> O metadata
2026-05-19 17:04:26 +00:00
}
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
> **OTP <20> <> <EFBFBD> ӷP<D3B7> <50> <EFBFBD> e**<2A> G`DoNotPersistBody=true` <20> <> notification.body <20> d<EFBFBD> šA<C5A1> u<EFBFBD> O channel/kind/target hash/provider_message_id/status<75> A<EFBFBD> קK audit DB <20> X<EFBFBD> {<7B> <> <EFBFBD> X OTP<54> C
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### 11.4 Entity <20> P Collection
2026-05-19 13:56:59 +00:00
2026-05-19 17:04:26 +00:00
```go
// notification collection
type Notification struct {
ID primitive.ObjectID
TenantID string
UID string
Channel enum.Channel
Kind enum.NotifyKind
2026-05-20 07:01:08 +00:00
TargetHash string // sha256(target)<29> A<EFBFBD> קK<D7A7> <4B> <EFBFBD> X PII
TemplateKey string // <20> <> <EFBFBD> <EFBFBD> TemplateRegistry
2026-05-19 17:04:26 +00:00
Locale string
Provider string // "sendgrid" | "twilio" | ...
ProviderMessageID string
Status enum.NotifyStatus // pending | sent | failed | retrying | dropped
Attempts int
LastError string
2026-05-20 07:01:08 +00:00
IdempotencyKey string // <20> ߤ@<40> <> <EFBFBD> <EFBFBD> {tenant_id, kind, idempotency_key}
2026-05-19 17:04:26 +00:00
Severity enum.Severity
OccurredAt int64
DeliveredAt int64
}
2026-05-19 13:56:59 +00:00
```
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
**Template** <20> <> **in-code registry** <EFBFBD> ]<5D> <> <EFBFBD> O<EFBFBD> w<EFBFBD> <77> <EFBFBD> ^+ provider <20> ݼҪO ID<49> ]<5D> p SendGrid Dynamic Template<74> ^<5E> G
2026-05-19 17:04:26 +00:00
```go
var TemplateRegistry = map[enum.NotifyKind]TemplateSpec{
enum.NotifyVerifyEmail: {
EmailProviderTemplateID: "d-xxxxxxxxxxxxx", // SendGrid
SMSText: "",
RequiredVars: []string{"code", "expires_in"},
},
enum.NotifyVerifyPhone: {
SMSText: "Your verification code is {code} (valid {expires_in}s)",
RequiredVars: []string{"code", "expires_in"},
},
enum.NotifyStepUpEmail: {...},
enum.NotifyStepUpPhone: {...},
enum.NotifySystemAlert: {...},
}
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### 11.5 Idempotency <20> P<EFBFBD> <50> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- `IdempotencyKey` <20> ߤ@<40> <> <EFBFBD> ޡG`{TenantID, Kind, IdempotencyKey}`
- <20> <> <EFBFBD> <EFBFBD> Send <20> P key <20> <> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ^<5E> W<EFBFBD> <57> <EFBFBD> <EFBFBD> <EFBFBD> G<EFBFBD> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> o<EFBFBD> <6F> provider<65> ^
- <20> <> <EFBFBD> B worker <20> <> <EFBFBD> ѵ <EFBFBD> <D1B5> <EFBFBD> <EFBFBD> G<EFBFBD> <47> <EFBFBD> ưh<C6B0> <68> 1s / 5s / 30s / 5min / 30min<69> A<EFBFBD> ̦h 5 <20> <> <EFBFBD> F<EFBFBD> W<EFBFBD> L <20> <> `status=dropped` + audit
- DLQ<4C> G<EFBFBD> <47> <EFBFBD> <EFBFBD> 5 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> O<EFBFBD> d<EFBFBD> b `notification_dlq` collection<6F> Aadmin API <20> i<EFBFBD> <69> <EFBFBD> <EFBFBD> retry
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 11.6 <20> P<EFBFBD> ~<7E> ȼҲժ<D2B2> <D5AA> I<EFBFBD> s<EFBFBD> <73> <EFBFBD> Y
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| <20> I<EFBFBD> s<EFBFBD> <73> | Kind | Channel | <20> Ҧ<EFBFBD> |
2026-05-19 17:04:26 +00:00
|--------|------|---------|------|
2026-05-20 07:01:08 +00:00
| `member.VerificationUseCase` | `verify_email` / `verify_phone` | email / sms | ** <EFBFBD> P<EFBFBD> B**<2A> ]<5D> n<EFBFBD> ߧY<DFA7> <59> <EFBFBD> D<EFBFBD> e<EFBFBD> F / <20> <> <EFBFBD> ѡ ^ |
| `member.StepUpUseCase` | `step_up_email` / `step_up_phone` | email / sms | ** <EFBFBD> P<EFBFBD> B** |
| `member.AdminUseCase` <EFBFBD> ]<5D> <> <EFBFBD> v<EFBFBD> i<EFBFBD> <69> <EFBFBD> ^ | `account_suspended` | email | ** <EFBFBD> <EFBFBD> <EFBFBD> B** |
| `tenant.UseCase` <EFBFBD> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> إߧ<D8A5> <DFA7> <EFBFBD> <EFBFBD> ^ | `tenant_welcome` | email | ** <EFBFBD> <EFBFBD> <EFBFBD> B** |
| ops alert<72> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> ʲv / DLQ <20> <> <EFBFBD> ^ | `ops_alert` | email / webhook | ** <EFBFBD> P<EFBFBD> B**<2A> ]critical<61> ^ |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> **OTP <20> <> <EFBFBD> <EFBFBD> <EFBFBD> P<EFBFBD> B**<2A> A<EFBFBD> _<EFBFBD> h client <20> L<EFBFBD> k<EFBFBD> ^<5E> <> <EFBFBD> uOTP <20> w<EFBFBD> H<EFBFBD> X<EFBFBD> v<EFBFBD> <76> <EFBFBD> <EFBFBD> <EFBFBD> T<EFBFBD> <54> <EFBFBD> ~<7E> F<EFBFBD> <46> <EFBFBD> L<EFBFBD> q<EFBFBD> <71> <EFBFBD> u<EFBFBD> <75> <EFBFBD> <EFBFBD> <EFBFBD> B<EFBFBD> קK<D7A7> <4B> <EFBFBD> C<EFBFBD> ~<7E> <> API<50> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 11.7 <20> P Audit Log <20> <> <EFBFBD> <EFBFBD> <EFBFBD> Y
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> C<EFBFBD> <EFBFBD> Notification <20> g<EFBFBD> J<EFBFBD> ɦP<C9A6> B<EFBFBD> g audit log<6F> G
2026-05-19 17:04:26 +00:00
```
action = notification.sent | notification.failed | notification.dropped
2026-05-20 07:01:08 +00:00
actor = system <20> <> caller uid
2026-05-19 17:04:26 +00:00
target = { kind: notification, id: notification_id, channel, kind }
metadata = { provider, provider_message_id, target_hash }
```
2026-05-20 07:01:08 +00:00
audit log <20> <> <EFBFBD> <EFBFBD> <EFBFBD> Ʀ s body<64> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <20> <> 20.1 critical <20> P<EFBFBD> B<EFBFBD> g<EFBFBD> <67> <EFBFBD> d<EFBFBD> <64> **<2A> <> <EFBFBD> t**<2A> q<EFBFBD> <71> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> Ȥ<EFBFBD> <C8A4> ƾڡ^<5E> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 11.8 <20> w<EFBFBD> <77> <EFBFBD> P PII
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- `Target` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> persist<73> F<EFBFBD> s `TargetHash` <EFBFBD> ]sha256<35> ^<5E> A<EFBFBD> K<EFBFBD> <4B> <EFBFBD> h<EFBFBD> <68> <EFBFBD> Bidempotency<63> F<EFBFBD> <46> <EFBFBD> X<EFBFBD> Ȧb send <20> <> <EFBFBD> U<EFBFBD> ǵ<EFBFBD> provider
- Email/SMS provider API key<65> BTwilio token <20> <> <20> <> `etc/gateway.yaml` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ܼ<EFBFBD> + secret manager
- Webhook <20> q<EFBFBD> D<EFBFBD> j<EFBFBD> <6A> HTTPS + HMAC ñ<> <C3B1> <EFBFBD> ]`X-CloudEP-Signature`<60> ^
2026-05-19 17:04:26 +00:00
---
2026-05-20 07:01:08 +00:00
## 12. <20> iŪ UID <20> ]<5D> p<EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 12.1 <20> 榡
2026-05-19 17:04:26 +00:00
```
{UIDPrefix}-{Sequence}
2026-05-20 07:01:08 +00:00
<EFBFBD> d<EFBFBD> ҡGAMEX-10000000<30> BACME-10000001<30> BACME-10000002
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
**<2A> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> G<EFBFBD> a<EFBFBD> <61> <EFBFBD> <EFBFBD> <EFBFBD> e<EFBFBD> <65> **<2A> ]<5D> <> <EFBFBD> ί<EFBFBD> Body<64> B<EFBFBD> <42> <EFBFBD> <EFBFBD> UUID<49> ^<5E> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> | <20> W<EFBFBD> h | <20> d<EFBFBD> <64> |
2026-05-19 17:04:26 +00:00
|------|------|------|
2026-05-20 07:01:08 +00:00
| `UIDPrefix` | 2~4 <20> <> <EFBFBD> j<EFBFBD> g<EFBFBD> A<EFBFBD> Ӧ<EFBFBD> `tenant.UIDPrefix` <20> <> slug <20> Y<EFBFBD> g | `AMEX` <EFBFBD> B`ACME` |
| `Sequence` | <20> Q<EFBFBD> i<EFBFBD> 컼<EFBFBD> W<EFBFBD> <57> <EFBFBD> ơA**<2A> _<EFBFBD> l `10000000` **<2A> ]<5D> u<EFBFBD> <75> `InitAutoID` <20> y<EFBFBD> N<EFBFBD> ^ | `10000000` |
| <20> <> <EFBFBD> j<EFBFBD> <6A> | <20> T<EFBFBD> w `-` | `AMEX-10000000` |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- <20> H<EFBFBD> <48> <EFBFBD> iŪ<69> B<EFBFBD> ȪA<C8AA> i<EFBFBD> v<EFBFBD> r<EFBFBD> f<EFBFBD> z
- <20> <> <EFBFBD> t UUID / base64 <20> ýX
- **`UIDPrefix` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> x<EFBFBD> ߤ@**<2A> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^<5E> F<EFBFBD> ȪA<C8AA> <41> <EFBFBD> J UID <20> Y<EFBFBD> i<EFBFBD> w<EFBFBD> <77> tenant + member
- <20> <> <EFBFBD> P<EFBFBD> <50> <EFBFBD> ᤣ<EFBFBD> i<EFBFBD> ۦP `UIDPrefix` <EFBFBD> F<EFBFBD> P prefix <20> <> Sequence <20> q `10000000` <20> _<EFBFBD> <5F>
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 12.2 <20> <> <EFBFBD> ͡]Bucket <20> <> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> 䴩<EFBFBD> 毲<EFBFBD> <E6AFB2> 50 <20> U<EFBFBD> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
Redis: member:seq:{tenant_id} counter<65> A<EFBFBD> <41> <EFBFBD> l 10000000
<EFBFBD> C<EFBFBD> <EFBFBD> pod <20> ҰʩίӺɮ<D3BA> INCRBY 500 <20> <> <EFBFBD> @<40> <> bucket<65> A<EFBFBD> b<EFBFBD> O<EFBFBD> <4F> <EFBFBD> 餺<EFBFBD> <E9A4BA> <EFBFBD> <EFBFBD>
2026-05-19 17:04:26 +00:00
UID = tenant.UIDPrefix + "-" + strconv.FormatInt(sequence, 10)
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
- **<2A> õo<C3B5> O<EFBFBD> @**<2A> G`{ tenant_id, uid }` unique index<65> C`EnsureFromOIDC` / `EnsureFromLDAP` / `EnsureFromSCIM` / `CreateUnverified` <20> R<EFBFBD> <52> dup key<65> ]E11000<30> ^<5E> <> fallback `GetByZitadelUserID` <20> <> `GetByEmail` <20> <> <EFBFBD> J<EFBFBD> <4A> member<65> C
- **Pod crash <20> e<EFBFBD> <65> **<2A> Gbucket <20> <> <EFBFBD> <EFBFBD> <EFBFBD> Χ <EFBFBD> <CEA7> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ᥢ<EFBFBD> i<EFBFBD> <69> <EFBFBD> <EFBFBD> <EFBFBD> ]UID <20> <> <EFBFBD> n<EFBFBD> D<EFBFBD> Y<EFBFBD> <59> <EFBFBD> s<EFBFBD> <73> <EFBFBD> B<EFBFBD> <42> <EFBFBD> n<EFBFBD> D<EFBFBD> Y<EFBFBD> 滼<EFBFBD> W<EFBFBD> F<EFBFBD> u<EFBFBD> n<EFBFBD> D<EFBFBD> <44> <EFBFBD> ᤺<EFBFBD> ߤ@<40> ^<5E> C
- **UIDPrefix unique index**<2A> G`tenants.{ uid_prefix: 1 } unique`<60> F<EFBFBD> د<EFBFBD> <D8AF> <EFBFBD> <EFBFBD> ɭY prefix <20> w<EFBFBD> s<EFBFBD> b <20> <> 409<30> C
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 13. <20> <> <EFBFBD> Ƽ ҫ<C6BC> <D2AB> P<EFBFBD> <50> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-19 17:04:26 +00:00
### 13.1 Collections
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| Collection | <20> Ҳ<EFBFBD> | <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
|------------|------|------|
2026-05-20 07:01:08 +00:00
| `members` | member | Profile<6C> ]<5D> t<EFBFBD> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> ҺX<D2BA> С BTOTP cipher<65> BOrigin<69> ^ |
| `identities` | member | zitadel_sub ? uid |
| `tenants` | member | <20> <> <EFBFBD> <EFBFBD> metadata |
| `tenant_ldap_configs` | member | LDAP <20> P<EFBFBD> B<EFBFBD> ]<5D> w<EFBFBD> ]<5D> [<5B> K<EFBFBD> ^ |
| `permissions` | permission | <20> <> <EFBFBD> <EFBFBD> Permission Tree<65> ]<5D> <> <EFBFBD> x seed<65> ^ |
| `roles` | permission | <20> <> <EFBFBD> <EFBFBD> Role<6C> ]`tenant_id` + immutable `key` <EFBFBD> ^ |
| `role_permissions` | permission | Role ? Permission ID |
| `user_roles` | permission | uid ? Role<6C> ]<5D> 䴩<EFBFBD> h<EFBFBD> <68> <EFBFBD> <EFBFBD> <EFBFBD> ^ |
| `role_mappings` | permission | <20> ~<7E> <> Group ? RoleID / Role.Key |
| `notifications` | notification | <20> q<EFBFBD> <71> <EFBFBD> o<EFBFBD> e<EFBFBD> <65> <EFBFBD> <EFBFBD> <EFBFBD> ]idempotency / <20> <> <EFBFBD> <EFBFBD> / audit<69> ^ |
| `notification_dlq` | notification | <20> <> <EFBFBD> <EFBFBD> 5 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> Ѫ<EFBFBD> dead letter queue |
| `audit_logs` | <20> ]<5D> W<EFBFBD> <57> DB<44> ^| <20> <> <EFBFBD> Ҳռ f<D5BC> p<EFBFBD> <70> <EFBFBD> x<EFBFBD> ]TTL 90d<30> A<EFBFBD> <41> 20.1<EFBFBD> ^ |
### 13.2 <20> D<EFBFBD> n<EFBFBD> <6E> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
```javascript
// members
{ tenant_id: 1, uid: 1 } // unique
{ tenant_id: 1, zitadel_user_id: 1 } // unique
{ tenant_id: 1, member_status: 1, create_at: -1 }
// identities
{ tenant_id: 1, zitadel_user_id: 1 } // unique
{ tenant_id: 1, uid: 1 }
{ tenant_id: 1, external_id: 1 }
2026-05-20 07:01:08 +00:00
// permissions<6E> ]<5D> <> <EFBFBD> <EFBFBD> <EFBFBD> ^
2026-05-19 13:56:59 +00:00
{ name: 1 } // unique
{ parent: 1, status: 1 }
{ http_path: 1, http_method: 1 } // sparse
// roles
2026-05-19 17:04:26 +00:00
{ tenant_id: 1, key: 1 } // unique
2026-05-19 13:56:59 +00:00
{ tenant_id: 1, status: 1 }
// role_permissions
2026-05-19 17:04:26 +00:00
{ tenant_id: 1, role_id: 1, permission_id: 1 } // unique
2026-05-19 13:56:59 +00:00
// user_roles
{ tenant_id: 1, uid: 1, role_id: 1 } // unique
{ tenant_id: 1, uid: 1 }
// role_mappings
{ tenant_id: 1, external_source: 1, external_key: 1 } // unique
2026-05-19 17:04:26 +00:00
{ tenant_id: 1, internal_role_id: 1 }
// notifications
2026-05-20 07:01:08 +00:00
{ tenant_id: 1, kind: 1, idempotency_key: 1 } // unique<75> ]<5D> P key <20> <> <EFBFBD> <EFBFBD> <EFBFBD> o<EFBFBD> ^
2026-05-19 17:04:26 +00:00
{ tenant_id: 1, uid: 1, occurred_at: -1 }
2026-05-20 07:01:08 +00:00
{ status: 1, attempts: 1, occurred_at: 1 } // worker <20> <> <EFBFBD> ݭ<EFBFBD> <DDAD> <EFBFBD>
2026-05-19 17:04:26 +00:00
// notification_dlq
{ tenant_id: 1, occurred_at: -1 }
2026-05-20 07:01:08 +00:00
// audit_logs<67> ]<5D> W<EFBFBD> <57> DB / replica set<65> ^
2026-05-19 17:04:26 +00:00
{ tenant_id: 1, occurred_at: -1 }
{ tenant_id: 1, "actor.uid": 1, occurred_at: -1 }
{ tenant_id: 1, action: 1, occurred_at: -1 }
{ occurred_at: 1 } // TTL 90d
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
> Identity <20> M<EFBFBD> g<EFBFBD> H `identities` collection <20> <> source of truth<74> F`members.zitadel_user_id` <20> Y<EFBFBD> O<EFBFBD> d<EFBFBD> A<EFBFBD> u<EFBFBD> @<40> Ϭd<CFAC> ֨<EFBFBD> /denormalized <20> <> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> <41> <EFBFBD> s<EFBFBD> ݥѦP<D1A6> @ transaction <20> θ<EFBFBD> <CEB8> v<EFBFBD> y<EFBFBD> {<7B> <> <EFBFBD> <EFBFBD> <EFBFBD> @<40> P<EFBFBD> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
> **<2A> ɶ<EFBFBD> <C9B6> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> G`CreateAt` / `UpdateAt` <20> Τ @<40> <> **epoch milliseconds<64> ]UTC<54> ^**<2A> C<EFBFBD> <43> <EFBFBD> ~ SCIM `meta.created` / `meta.lastModified` <20> <> SCIM mapper <20> b<EFBFBD> ǦC<C7A6> Ʈ<EFBFBD> <C6AE> <EFBFBD> RFC3339Nano<6E> F<EFBFBD> e<EFBFBD> ݮi<DDAE> ܥ<EFBFBD> client <20> t<EFBFBD> d timezone<6E> C
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
### 13.3 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]100 <20> U+<2B> ^
2026-05-19 13:56:59 +00:00
```
Shard Key: { tenant_id: 1, uid: 1 }
```
2026-05-20 07:01:08 +00:00
<EFBFBD> 毲<EFBFBD> <EFBFBD> 50 <20> U<EFBFBD> |<7C> <> <EFBFBD> <EFBFBD> <EFBFBD> b<EFBFBD> P<EFBFBD> @ chunk<6E> AMongoDB <20> <> <EFBFBD> i<EFBFBD> Ө<EFBFBD> <D3A8> F<EFBFBD> Y<EFBFBD> w<EFBFBD> <77> <EFBFBD> 毲<EFBFBD> <E6AFB2> <EFBFBD> d<EFBFBD> U<EFBFBD> ŦA<C5A6> <41> <EFBFBD> <EFBFBD> hash <20> G<EFBFBD> <47> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> C
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 14. Redis Key <20> R<EFBFBD> W
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### auth<74> ]`internal/model/auth/redis.go`<60> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
auth:jwt:bl:{jti} # <20> <> token <20> ¦W<C2A6> <57> <EFBFBD> ATTL = <20> Ѿl<D1BE> ةR
auth:jwt:pair:{access_jti} # access_jti <20> <> refresh_jti<74> ]<5D> n<EFBFBD> X<EFBFBD> ɳs refresh <20> @<40> _<EFBFBD> Զ¡^
auth:gen:{tenant_id}:{uid} # <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> ĥN<C4A5> <4E>
auth:exchange:nonce:{id_token_jti} # Token Exchange <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ATTL 10min
auth:stepup:used:{jti} # Step-up token <20> 榸<EFBFBD> ʡATTL = step_up_token TTL
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### member<65> ]`internal/model/member/redis.go`<60> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
member:profile:{tenant_id}:{uid} # profile cache<68> ATTL 5~15min
member:sub:{tenant_id}:{sub} # zitadel_sub <20> <> uid<69> ATTL 1h
2026-05-19 13:56:59 +00:00
member:seq:{tenant_id} # UID bucket counter
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
otp:challenge:{tenant_id}:{challenge_id} # {purpose, identifier, code_hash, attempts, expire_at}<7D> ATTL 5min
otp:rate:{tenant_id}:{purpose}:{identifier} # <20> <> <EFBFBD> o<EFBFBD> N<EFBFBD> o 60s
otp:daily:{tenant_id}:{purpose}:{identifier} # <20> <> <EFBFBD> <EFBFBD> <EFBFBD> W<EFBFBD> <57> INCR<43> ATTL 24h
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
totp:enroll:{tenant_id}:{uid} # enroll <20> Ȧs secret_cipher<65> ATTL 10min
totp:used:{tenant_id}:{uid}:{timestep} # TOTP code <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ATTL 90s
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
### notification<6F> ]`internal/model/notification/redis.go`<60> ^
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
notif:idem:{tenant_id}:{kind}:{idempotency_key} # idempotency <20> <> <EFBFBD> G<EFBFBD> ֨<EFBFBD> <D6A8> ATTL 24h
notif:quota:{tenant_id}:{channel} # <20> C<EFBFBD> <43> <EFBFBD> <EFBFBD> <EFBFBD> C<EFBFBD> q<EFBFBD> D quota<74> AINCR + TTL
notif:retry:zset # <20> <> <EFBFBD> B<EFBFBD> <42> <EFBFBD> ձƵ{<7B> ]score = next_retry_at_ms<6D> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
### permission<6F> ]`internal/model/permission/redis.go`<60> ^
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
permission:casbin:rules:{tenant_id} # Casbin policy rules<65> ]List of JSON<4F> ^
permission:tree:open # <20> i<EFBFBD> <69> <EFBFBD> Gopen <20> `<60> I cache
perm:role_perms:{tenant_id}:{role_id} # role <20> <> permission names<65> ATTL 30min
perm:user_roles:{tenant_id}:{uid} # uid <20> <> role keys<79> ATTL 5min
2026-05-19 13:56:59 +00:00
```
---
2026-05-20 07:01:08 +00:00
## 15. <20> W<EFBFBD> һ P<D2BB> ʯ<EFBFBD> <CAAF> ]100 <20> U+ / <20> 毲<EFBFBD> <E6AFB2> 50 <20> U<EFBFBD> ^
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> | <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
|------|------|
2026-05-20 07:01:08 +00:00
| Gateway | <20> L<EFBFBD> <4C> <EFBFBD> A<EFBFBD> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> X<EFBFBD> i |
| MongoDB | Sharding + Replica Set<65> AŪ<41> <C5AA> secondary |
| ListMembers | Cursor <20> <> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> T<EFBFBD> <54> deep offset |
| Authorize | Casbin EnforceEx<45> ]?<3F> s + Redis policy<63> ^ |
| LoadPolicy | ?<3F> <> ?<3F> W<EFBFBD> q<EFBFBD> Fcron 5min <20> <> <EFBFBD> q<EFBFBD> ©<EFBFBD> |
| JWT <20> <> UID | Redis cache 1h |
| Directory Sync | 500 users / batch<63> Arate limit ZITADEL API |
| Access Token TTL | 15min<69> ]<5D> <> <EFBFBD> C<EFBFBD> M<EFBFBD> P<EFBFBD> <50> <EFBFBD> f<EFBFBD> ^ |
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
### <20> e<EFBFBD> q<EFBFBD> ʦ<EFBFBD>
2026-05-19 13:56:59 +00:00
```
2026-05-20 07:01:08 +00:00
100 <20> U members <20> <> ~2KB ? 2GB<47> ]<5D> <> <EFBFBD> t index<65> ^
indexes ? 1~2GB
<EFBFBD> <EFBFBD> <20> 涰<EFBFBD> s<EFBFBD> i<EFBFBD> Ө<EFBFBD> <D3A8> A<EFBFBD> <41> ij 3 node replica set <20> _<EFBFBD> <5F>
2026-05-19 13:56:59 +00:00
```
---
2026-05-20 07:01:08 +00:00
## 16. <20> ؿ<EFBFBD> <D8BF> <EFBFBD> <EFBFBD> c
2026-05-19 13:56:59 +00:00
```
gateway/
2026-05-20 07:01:08 +00:00
<EFBFBD> u<EFBFBD> w<EFBFBD> w generate/api/
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w auth.api
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w member.api
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w permission.api
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w tenant.api
<EFBFBD> x <20> |<7C> w<EFBFBD> w scim.api
<EFBFBD> x
<EFBFBD> u<EFBFBD> w<EFBFBD> w internal/
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w middleware/
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w jwt_revoke.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w tenant_context.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w require_permission.go
<EFBFBD> x <20> x <20> |<7C> w<EFBFBD> w scim_auth.go
<EFBFBD> x <20> x
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w library/
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w zitadel/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w oidc.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w management.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w ldap/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w client.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w attrmap.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w casbin/ # Enforcer <20> <> <EFBFBD> l<EFBFBD> <6C> helper
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w uid/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w encode.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w generator.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w totp/ # RFC 6238 <20> t<EFBFBD> <74> <EFBFBD> k<EFBFBD> BQR <20> ͦ<EFBFBD>
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w totp.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w backup_code.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w crypto/ # AES-GCM secret <20> [<5B> ѱK + KMS
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w secret.go
<EFBFBD> x <20> x <20> |<7C> w<EFBFBD> w notification/ # Provider <20> <> <EFBFBD> @<40> ]<5D> <> IO <20> ʸˡ^
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w email/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w sendgrid.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w ses.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w smtp.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w sms/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w twilio.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w sns.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w smsapi.go
<EFBFBD> x <20> x <20> |<7C> w<EFBFBD> w push/ # <20> w<EFBFBD> d
<EFBFBD> x <20> x
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w model/
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w auth/
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w ...
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w member/ # <20> t verification / step_up / totp usecase
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w ...
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w notification/ # <20> Τ @<40> q<EFBFBD> <71> <EFBFBD> J<EFBFBD> f
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w entity/
<EFBFBD> x <20> x <20> x <20> x <20> |<7C> w<EFBFBD> w notification.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w enum/
<EFBFBD> x <20> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w channel.go
<EFBFBD> x <20> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w kind.go
<EFBFBD> x <20> x <20> x <20> x <20> |<7C> w<EFBFBD> w status.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w repository/
<EFBFBD> x <20> x <20> x <20> x <20> |<7C> w<EFBFBD> w notification.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w usecase/
<EFBFBD> x <20> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w notifier.go
<EFBFBD> x <20> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w template.go
<EFBFBD> x <20> x <20> x <20> x <20> |<7C> w<EFBFBD> w worker.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w config/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w errors.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w redis.go
<EFBFBD> x <20> x <20> |<7C> w<EFBFBD> w permission/
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w entity/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w permission.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w user_role.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role_permission.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w role_mapping.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w enum/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w status.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w permission_type.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w repository/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w permission.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w user_role.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role_permission.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role_mapping.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w casbin_redis_adapter.go # <20> u<EFBFBD> <75> permission-server
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w usecase/
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w permission_tree.go # <20> u<EFBFBD> <75> permission-server
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w rbac.go # Casbin LoadPolicy / Check
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w permission.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role_permission.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w user_role.go
<EFBFBD> x <20> x <20> x <20> u<EFBFBD> w<EFBFBD> w role_mapping.go
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w authorization_query.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w rbac/
<EFBFBD> x <20> x <20> x <20> |<7C> w<EFBFBD> w rule.go # Casbin Rule struct
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w config/
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w errors.go
<EFBFBD> x <20> x <20> u<EFBFBD> w<EFBFBD> w redis.go
<EFBFBD> x <20> x <20> |<7C> w<EFBFBD> w mock/
<EFBFBD> x <20> x
<EFBFBD> x <20> |<7C> w<EFBFBD> w worker/
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w directory_sync/
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w policy_sync/ # <20> i<EFBFBD> <69> <EFBFBD> G<EFBFBD> w<EFBFBD> <77> LoadPolicy
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w notification_retry/ # <20> <> <EFBFBD> B<EFBFBD> <42> <EFBFBD> ա BDLQ <20> <> <EFBFBD> <EFBFBD>
<EFBFBD> x <20> |<7C> w<EFBFBD> w member_anonymize/ # <20> n<EFBFBD> R 30 <20> ѫ<EFBFBD> <D1AB> ΦW<CEA6> ơ]<5D> <> 5.7<EFBFBD> ^
<EFBFBD> x
<EFBFBD> u<EFBFBD> w<EFBFBD> w etc/
<EFBFBD> x <20> u<EFBFBD> w<EFBFBD> w gateway.yaml
<EFBFBD> x <20> |<7C> w<EFBFBD> w rbac.conf # Casbin <20> ҫ<EFBFBD> <D2AB> ]<5D> u<EFBFBD> <75> permission-server<65> ^
<EFBFBD> x
<EFBFBD> |<7C> w<EFBFBD> w docs/
<20> u<EFBFBD> w<EFBFBD> w model.md
<20> |<7C> w<EFBFBD> w identity-member-design.md # <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
```
---
2026-05-20 07:01:08 +00:00
## 17. <20> ]<5D> w<EFBFBD> <77>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
`etc/gateway.yaml` <20> X<EFBFBD> R<EFBFBD> <52> <EFBFBD> ס G
2026-05-19 13:56:59 +00:00
```yaml
Name: gateway
Host: 0.0.0.0
Port: 8888
Auth:
AccessSecret: ${JWT_ACCESS_SECRET}
AccessExpire: 900
RefreshAuth:
AccessSecret: ${JWT_REFRESH_SECRET}
AccessExpire: 604800
Zitadel:
2026-05-20 07:01:08 +00:00
Issuer: https://id.internal.example.com # self-hosted <20> <> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
ClientID: ${ZITADEL_CLIENT_ID}
2026-05-19 17:04:26 +00:00
JWKSUrl: https://id.internal.example.com/oauth/v2/keys
MgmtURL: https://id.internal.example.com/management/v1
2026-05-19 13:56:59 +00:00
MgmtToken: ${ZITADEL_MGMT_TOKEN}
2026-05-20 07:01:08 +00:00
EnforceAdminMFA: true # admin <20> <> role<6C> ]tenant_owner/tenant_admin/platform_super_admin<69> ^<5E> j<EFBFBD> <6A> TOTP
# Self-hosted<65> GLDAP IdP <20> <> ZITADEL <20> <> <EFBFBD> s<EFBFBD> <73> <EFBFBD> ~ AD/OpenLDAP
2026-05-19 17:04:26 +00:00
StepUp:
TokenSecret: ${JWT_STEPUP_SECRET}
TokenTTLSeconds: 300
AllowedActions:
- change_business_email
- change_business_phone
- delete_member
- tenant_admin_force_status
- revoke_all_sessions
Verification:
OTPLength: 6
OTPTTLSeconds: 300
ResendCooldownSeconds: 60
DailyLimit: 10
MaxAttempts: 5
TOTP:
2026-05-20 07:01:08 +00:00
Issuer: CloudEP # <20> <> <EFBFBD> ܦb Authenticator App <20> W<EFBFBD> <57> <EFBFBD> W<EFBFBD> <57>
Algorithm: SHA1 # <20> ۮe Google Authenticator
2026-05-19 17:04:26 +00:00
Digits: 6
PeriodSeconds: 30
2026-05-20 07:01:08 +00:00
Window: 1 # <20> e<EFBFBD> <65> <20> <> 1 <20> <> 30s <20> ϶<EFBFBD>
2026-05-19 17:04:26 +00:00
BackupCodeCount: 10
BackupCodeLength: 12 # hex chars
2026-05-20 07:01:08 +00:00
SecretKEK: ${TOTP_KEK} # AES-256 KEK<45> F<EFBFBD> <46> ij<EFBFBD> <C4B3> KMS / Vault
2026-05-19 17:04:26 +00:00
EnrollTTLSeconds: 600
Notification:
DefaultLocale: zh-tw
Async:
QueueRedisKey: notif:retry:zset
2026-05-20 07:01:08 +00:00
Worker: 4 # worker goroutine <20> <>
2026-05-19 17:04:26 +00:00
MaxRetry: 5
BackoffSeconds: [1, 5, 30, 300, 1800]
2026-05-20 07:01:08 +00:00
RatePerTenant: # <20> C<EFBFBD> <43> <EFBFBD> <EFBFBD> <EFBFBD> q<EFBFBD> D<EFBFBD> t<EFBFBD> B<EFBFBD> ]<5D> <> <EFBFBD> z<EFBFBD> o / <20> <> <EFBFBD> ݥΡ ^
Email: 10000 # <20> C<EFBFBD> <43>
2026-05-19 17:04:26 +00:00
SMS: 5000
Email:
Provider: sendgrid # sendgrid | ses | smtp
APIKey: ${SENDGRID_API_KEY}
From: noreply@example.com
2026-05-20 07:01:08 +00:00
Templates: # <20> <> <EFBFBD> <EFBFBD> TemplateRegistry key <20> <> provider template id
2026-05-19 17:04:26 +00:00
verify_email: d-xxxxxxxxxxxxx
step_up_email: d-yyyyyyyyyyyyy
account_suspended: d-zzzzzzzzzzzzz
tenant_welcome: d-aaaaaaaaaaaaa
SMS:
Provider: twilio # twilio | sns | smsapi
AccountSID: ${TWILIO_ACCOUNT_SID}
AuthToken: ${TWILIO_AUTH_TOKEN}
From: "+1234567890"
Templates:
verify_phone: "Your verification code is {code} (valid {expires_in}s)"
step_up_phone: "Step-up code: {code}"
Push:
2026-05-20 07:01:08 +00:00
Enabled: false # <20> w<EFBFBD> d
2026-05-19 17:04:26 +00:00
Webhook:
HMACSecret: ${NOTIF_WEBHOOK_HMAC}
2026-05-19 13:56:59 +00:00
Mongo:
2026-05-20 07:01:08 +00:00
# <20> <> internal/library/mongo <20> ]<5D> w
2026-05-19 13:56:59 +00:00
Redis:
Host: 127.0.0.1:6379
Type: node
Member:
DefaultLanguage: zh-tw
DefaultCurrency: TWD
Permission:
RBACModelPath: etc/rbac.conf
PolicySyncInterval: 5m
2026-05-20 07:01:08 +00:00
PolicyReloadChannel: casbin:reload # Redis Pub/Sub <20> q<EFBFBD> D<EFBFBD> ]<5D> Y<EFBFBD> ɳq<C9B3> <71> <EFBFBD> A5m cron <20> ©<EFBFBD> <C2A9> ^
2026-05-19 17:04:26 +00:00
PlatformAdminTenantID: ${PLATFORM_ADMIN_TENANT_ID}
PlatformAdminRoleKey: platform_super_admin
2026-05-20 07:01:08 +00:00
PlatformAdminAllowlistUIDs: ${PLATFORM_ADMIN_ALLOWLIST_UIDS} # break-glass <20> Ρ A<CEA1> <41> <EFBFBD> <EFBFBD> audit
2026-05-19 13:56:59 +00:00
CacheTTLSeconds: 300
2026-05-19 17:04:26 +00:00
DirectorySync:
MissingThreshold: 3
MaxChangeRatio: 0.20
DryRunOnFirstSync: true
DefaultWindow: 24h
AlertSink: ${OPS_WEBHOOK_URL}
AuditLog:
Sink: mongo # mongo | otel | dual
Mongo:
2026-05-20 07:01:08 +00:00
DB: gateway_audit # <20> <> ij<EFBFBD> W<EFBFBD> <57> DB instance / replica set
2026-05-19 17:04:26 +00:00
Collection: audit_logs
BatchSize: 100
FlushInterval: 1s
TTLDays: 90
OTEL:
2026-05-20 07:01:08 +00:00
Endpoint: ${OTEL_ENDPOINT} # Sink = otel / dual <20> ɥͮ<C9A5>
2026-05-19 17:04:26 +00:00
RateLimit:
Enabled: true
RedisPrefix: rl
WindowSeconds: 60
Rules:
- Match: /api/v1/auth/*
ByIP: 60 # 60 req / min / IP
2026-05-20 07:01:08 +00:00
ByUID: 30 # 30 req / min / UID<49> ]<5D> w<EFBFBD> n<EFBFBD> J<EFBFBD> ɡ ^
2026-05-19 17:04:26 +00:00
- Match: /api/v1/auth/step-up/*
ByUID: 10
- Match: /scim/v2/*
2026-05-20 07:01:08 +00:00
ByToken: 6000 # 6000 req / min / SCIM token<65> ]<5D> <> 100rps<70> ^
2026-05-19 17:04:26 +00:00
- Match: /api/v1/*
2026-05-20 07:01:08 +00:00
ByUID: 600 # <20> @<40> <> API <20> W<EFBFBD> <57>
2026-05-19 17:04:26 +00:00
ByIP: 1200
2026-05-19 13:56:59 +00:00
```
---
2026-05-20 07:01:08 +00:00
## 18. <20> <> <EFBFBD> I<EFBFBD> <49> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> q | <20> <> <EFBFBD> e | <20> <> <EFBFBD> X |
2026-05-19 13:56:59 +00:00
|------|------|------|
2026-05-20 07:01:08 +00:00
| **P0** | <20> ؿ<EFBFBD> <D8BF> <EFBFBD> <EFBFBD> [<5B> Bentity<74> Bredis key<65> Bconfig<69> B**`make seed-platform-admin` CLI**<2A> ]<5D> ح<EFBFBD> <D8AD> <EFBFBD> platform admin uid + role<6C> ^ | <20> i<EFBFBD> ҰʡB<CAA1> i<EFBFBD> s Mongo/Redis<69> A<EFBFBD> <41> <EFBFBD> x admin <20> i<EFBFBD> n<EFBFBD> J |
| **P1** | UID generator + ProvisioningUseCase<73> ]OIDC/LDAP/SCIM <20> T<EFBFBD> <54> <EFBFBD> <EFBFBD> <EFBFBD> ^+ token exchange | <20> i<EFBFBD> n<EFBFBD> J<EFBFBD> <4A> <EFBFBD> o JWT + <20> iŪ UID |
| **P2** | JWT middleware + jti <20> ¦W<C2A6> <57> + auth_gen + logout/refresh | <20> <> <EFBFBD> <EFBFBD> Token <20> ͩR<CDA9> g<EFBFBD> <67> |
| **P3** | Permission seed + PermissionTree + Casbin RBAC + Redis Adapter | <20> i LoadPolicy / Check |
| **P3.5** | Notification Module<6C> ]<5D> Τ @<40> J<EFBFBD> f + Email/SMS Provider<65> ^+ Verification + Step-up MFA + **TOTP** | <20> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> + TOTP step-up + <20> <> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> u<EFBFBD> <75> |
| **P4** | member profile API + <20> w<EFBFBD> ] Role seed + CasbinRBACMiddleware | `/members/me` + API <20> <> <EFBFBD> v<EFBFBD> ͮ<EFBFBD> |
| **P5** | RolePermission + UserRole + B2B Role CRUD + Permission <20> Ŀ<EFBFBD> API | <20> <> <EFBFBD> ᧹<EFBFBD> <E1A7B9> <EFBFBD> ۩w<DBA9> q |
| **P6** | Tenant <20> إ<EFBFBD> + ZITADEL CreateOrg + LDAP <20> ]<5D> w | <20> h<EFBFBD> <68> <EFBFBD> <EFBFBD> |
| **P7** | Directory Sync Worker<65> ]AD + OpenLDAP<41> ^+ <20> <> 10.4 guardrail | <20> <> <EFBFBD> ~<7E> ؿ<EFBFBD> <D8BF> P<EFBFBD> B<EFBFBD> ]<5D> ~<7E> P<EFBFBD> O<EFBFBD> @<40> <> <EFBFBD> ơ^ |
| **P8** | SCIM 2.0 endpoint + Group <20> M<EFBFBD> g | <20> <> <EFBFBD> ~ provisioning |
| **P8.5** | Audit log sink<6E> ]Mongo <20> W<EFBFBD> <57> collection<6F> ^+ Rate Limit middleware<72> ]<5D> <> <20> <> 20<32> ^ | <20> i<EFBFBD> f<EFBFBD> p / <20> <> <EFBFBD> ݥ<EFBFBD> |
| **P9** | <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]100 <20> U seed<65> ^<5E> Bsharding<6E> B<EFBFBD> <42> <EFBFBD> u<EFBFBD> BJWT kid <20> h<EFBFBD> <68> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> | <20> W<EFBFBD> u<EFBFBD> dz<EFBFBD> |
2026-05-19 17:04:26 +00:00
---
2026-05-20 07:01:08 +00:00
## 19. <20> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ƶ<EFBFBD>
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| # | ij<> D | ** <EFBFBD> M<EFBFBD> <EFBFBD> ** | <20> ]<5D> p<EFBFBD> v<EFBFBD> T |
2026-05-19 17:04:26 +00:00
|---|------|----------|----------|
2026-05-20 07:01:08 +00:00
| 1 | UID <20> 榡 | ** `{Prefix}-{Sequence}` **<2A> A<EFBFBD> p `AMEX-10000000` | <20> <> 12<31> FSequence <20> _<EFBFBD> <5F> `10000000` |
| 2 | SCIM <20> <> <EFBFBD> <EFBFBD> | ** `/scim/v2/tenants/{tenant_id}/...` ** | <20> <> 7.5<EFBFBD> B<EFBFBD> <EFBFBD> 10.3 |
| 3 | ZITADEL <20> <> <EFBFBD> p | **Self-hosted** | <20> <> 3.3<EFBFBD> FLDAP <20> <> <EFBFBD> <EFBFBD> /VPN <20> s<EFBFBD> u |
| 4 | <20> v<EFBFBD> <76> <EFBFBD> ܧ<EFBFBD> <DCA7> ͮ<EFBFBD> | **UserRole <20> ܧ<EFBFBD> `INCR auth_gen`<60> FRolePermission <20> ܧ<EFBFBD> reload policy + cache invalidate** | <20> <> 4.5<EFBFBD> B<EFBFBD> <EFBFBD> 6.11 |
| 5 | B2C <20> <> <EFBFBD> <EFBFBD> | ** <EFBFBD> <EFBFBD> Ū seed <20> ҪO**<2A> A<EFBFBD> <41> <EFBFBD> i<EFBFBD> ۩w<DBA9> q Role | <20> <> 6.12<EFBFBD> FB2C <20> T<EFBFBD> <54> Role CRUD API |
| 6 | Refresh Token | ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> + <20> <> refresh jti <20> ¦W<C2A6> <57> ** | <20> <> 4.5 Refresh <20> <> <EFBFBD> <EFBFBD> |
| 7 | Casbin <20> h<EFBFBD> <68> <EFBFBD> <EFBFBD> <EFBFBD> j<EFBFBD> <6A> | **policy <20> a `tenant_id` + immutable `role_key`** | <20> <> 6.7<EFBFBD> F<EFBFBD> קK<EFBFBD> P<EFBFBD> W role <20> <EFBFBD> <F3AFB2A4> ìV |
| 8 | SCIM externalId | ** <EFBFBD> O<EFBFBD> d<EFBFBD> <EFBFBD> <EFBFBD> Ȥ<EFBFBD> <EFBFBD> ݥ~<7E> <> <EFBFBD> ѧO<D1A7> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Gateway UID** | <20> <> 10.3<EFBFBD> FGateway UID <20> @<40> <> SCIM id <20> <> extension |
| 9 | Platform Admin bypass | ** <EFBFBD> <EFBFBD> <EFBFBD> x role + allowlist<73> A<EFBFBD> <41> <EFBFBD> <EFBFBD> audit** | <20> <> 6.7<EFBFBD> B<EFBFBD> <EFBFBD> 8.2<EFBFBD> F<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> b Casbin matcher |
| 10 | UIDPrefix | ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> x<EFBFBD> ߤ@**<2A> ]`tenants.uid_prefix` unique index<65> ^ | <20> <> 12.2 |
| 11 | JWT Claims <20> <> <EFBFBD> e | ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> role / permission <20> ַ<EFBFBD> **<2A> A<EFBFBD> C<EFBFBD> <43> <EFBFBD> d cache | <20> <> 4.3 |
| 12 | Refresh Token Reuse | ** <EFBFBD> <EFBFBD> refresh <20> G<EFBFBD> <47> <EFBFBD> ϥ<EFBFBD> = <20> s<EFBFBD> <73> <20> <> INCR auth_gen + audit** | <20> <> 4.5 |
| 13 | Token Exchange <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> | **id_token nonce SETNX + iat 5 <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> f** | <20> <> 4.5 |
| 14 | Logout <20> <> <EFBFBD> <EFBFBD> | **Issue <20> <> redis <20> O access?refresh jti pair** | <20> <> 4.5 |
| 15 | RolePermission API <20> y<EFBFBD> N | **PUT <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> N** `{ permission_names: [...] }` + <20> j<EFBFBD> <6A> <EFBFBD> a tenant_id | <20> <> 6.8<EFBFBD> B<EFBFBD> <EFBFBD> 7.3<EFBFBD> B<EFBFBD> <EFBFBD> 9.3 |
| 16 | <20> ~<7E> <> <EFBFBD> ӷ<EFBFBD> UserRole | ** <EFBFBD> <EFBFBD> source <20> j<EFBFBD> <6A> Replace**<2A> Amanual <20> ä<EFBFBD> <C3A4> Q<EFBFBD> ~ | <20> <> 6.10 |
| 17 | PlainCode <20> <> <EFBFBD> @ | **Casbin <20> B<EFBFBD> ~<7E> d `.plain_code` <20> <> <EFBFBD> <EFBFBD> ** <EFBFBD> A<EFBFBD> h role allow <20> <> <EFBFBD> G<EFBFBD> <47> OR | <20> <> 6.9 |
| 18 | Permission.Name | ** <EFBFBD> إ߫ᤣ<EFBFBD> i<EFBFBD> <EFBFBD> <EFBFBD> W**<2A> F<EFBFBD> o<EFBFBD> <6F> <EFBFBD> <EFBFBD> `status=close` + <20> s<EFBFBD> <73> | <20> <> 6.4 |
2026-05-21 06:45:35 +00:00
| 19 | <20> <> <EFBFBD> U<EFBFBD> <55> <EFBFBD> | | **B2C** <EFBFBD> GGateway <20> Τ @ `/auth/register*` <EFBFBD> ]Email + Social<61> Ainvite <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ^<5E> F**B2B**<2A> GLDAP / SCIM <20> <> <EFBFBD> g register API<50> Fplatform-native usecase <20> w<EFBFBD> Ω<EFBFBD> Email <20> <> <EFBFBD> U | <20> <> 3.4<EFBFBD> B[auth-unified-registration.md](./auth-unified-registration.md) |
2026-05-20 07:01:08 +00:00
| 20 | <20> <> <EFBFBD> <EFBFBD> vs <20> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> Ҥ<EFBFBD> <D2A4> h | **ZITADEL <20> n<DEB5> J<EFBFBD> <4A> <EFBFBD> <EFBFBD> <EFBFBD> FGateway member <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ~<7E> <> email / phone** | <20> <> 1.2<EFBFBD> B<EFBFBD> <EFBFBD> 5.4 |
| 21 | Step-up MFA | ** <EFBFBD> ҥ<EFBFBD> **<2A> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> I action <20> <> 5min <20> 榸<EFBFBD> <E6A6B8> `step_up_token` | <20> <> 5.6<EFBFBD> B<EFBFBD> <EFBFBD> 9.6 |
| 22 | OTP <20> 뻼<EFBFBD> q<EFBFBD> D | ** <EFBFBD> ۰ e**<2A> ]<5D> z<EFBFBD> L Notification Module <20> ] Email / SMS Provider<65> ^ | <20> <> 5.5<EFBFBD> B<EFBFBD> <EFBFBD> 11<EFBFBD> B<EFBFBD> <EFBFBD> 17 |
| 23 | MFA <20> j<EFBFBD> <EFBFBD> <EEB5A6> | ** <EFBFBD> <EFBFBD> <EFBFBD> x<EFBFBD> j<EFBFBD> <EFBFBD> admin role <20> <> ZITADEL TOTP**<2A> F<EFBFBD> @<40> <> user <20> w<EFBFBD> ]<5D> <> <EFBFBD> j<EFBFBD> <6A> <EFBFBD> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> <49> Step-up | <20> <> 3.5 |
| 24 | KYC | ** <EFBFBD> <EFBFBD> <EFBFBD> b<EFBFBD> 쪩<EFBFBD> d<EFBFBD> <EFBFBD> ** | <20> X |
| 25 | <20> ~<7E> <> TOTP<54> ]Authenticator App<70> ^ | ** <EFBFBD> ҥ<EFBFBD> **<2A> AGateway <20> ۦs AES-GCM <20> [<5B> K secret<65> F<EFBFBD> P ZITADEL <20> <> <EFBFBD> <EFBFBD> TOTP <20> W<EFBFBD> <57> | <20> <> 5.8 |
| 26 | Step-up <20> q<EFBFBD> D<EFBFBD> u<EFBFBD> <75> <EFBFBD> <EFBFBD> | **TOTP > SMS > Email** <EFBFBD> FStart <20> ɨ<EFBFBD> enrolled <20> <> <EFBFBD> A<EFBFBD> D<EFBFBD> q<EFBFBD> D<EFBFBD> A<EFBFBD> i<EFBFBD> <69> client `prefer_channel` <20> мg | <20> <> 5.6 |
| 27 | Notification Module | <20> W<EFBFBD> <57> model <20> Ҳ<EFBFBD> `internal/model/notification/` <EFBFBD> A**<2A> Ҧ<EFBFBD> outbound <20> q<EFBFBD> T**<2A> Τ @<40> <> `NotifierUseCase` <EFBFBD> Flibrary <20> h<EFBFBD> <68> provider <20> <> IO <20> ʸ<EFBFBD> | <20> <> 11 |
| 28 | OTP <20> J<EFBFBD> w<EFBFBD> <77> <EFBFBD> <EFBFBD> | OTP / step-up <20> <> <EFBFBD> ӷP<D3B7> <50> <EFBFBD> e `DoNotPersistBody=true` <EFBFBD> Anotification <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ȯd metadata<74> ]target_hash<73> Bprovider_message_id<69> Bstatus<75> ^ | <20> <> 11.3<EFBFBD> B<EFBFBD> <EFBFBD> 11.8 |
| 29 | UseCase <20> <> <EFBFBD> h | **Atomic primitives + Composite** <20> <> <EFBFBD> h<EFBFBD> G<EFBFBD> <47> <EFBFBD> l<EFBFBD> ʧ@<40> ]Profile / Lifecycle / Provisioning / OTP / TOTP<54> ^<5E> i<EFBFBD> <69> <EFBFBD> N<EFBFBD> զ X<D5A6> FComposite<74> ]Verification / StepUp<55> ^<5E> <> <EFBFBD> `<60> βզ X<D5A6> w<EFBFBD> ʸˡFlogic <20> i<EFBFBD> <69> <EFBFBD> ܸ<EFBFBD> <DCB8> | | <20> <> 5.2 |
| 30 | OTP <20> ]<5D> p | **Purpose-agnostic atomic primitive** <EFBFBD> G`OTPUseCase.Generate / Verify / Invalidate`<60> F`purpose` <20> <> <EFBFBD> ѥγ ~<7E> ]registration_email / business_email / step_up / ...<2E> ^<5E> Acaller <20> ۭt<DBAD> 뻼<EFBFBD> P<EFBFBD> <50> <EFBFBD> <EFBFBD> <EFBFBD> Ƨ @<40> <> | <20> <> 5.2.1<EFBFBD> B<EFBFBD> <EFBFBD> 5.2.4<EFBFBD> B<EFBFBD> <EFBFBD> 5.9 |
| 31 | Provisioning <20> <> <EFBFBD> <EFBFBD> | `EnsureMember` <20> ** `EnsureFromOIDC` / `EnsureFromLDAP` / `EnsureFromSCIM` ** <20> T<EFBFBD> <54> atomic<69> F<EFBFBD> <46> <EFBFBD> P<EFBFBD> ӷ<EFBFBD> <D3B7> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 褬<DEBF> <E8A4AC> <EFBFBD> <EFBFBD> <EFBFBD> X | <20> <> 5.2.1 |
| 32 | <20> <> <EFBFBD> x<EFBFBD> <78> <EFBFBD> U<EFBFBD> <55> <EFBFBD> A | <20> [<5B> ^ `unverified` <20> <> <EFBFBD> A<EFBFBD> A**<2A> <> ** platform-native <20> <> <EFBFBD> |<7C> |<7C> X<EFBFBD> {<7B> FOIDC / LDAP / SCIM <20> <> <EFBFBD> <EFBFBD> `active` | <20> <> 5.3 |
| A | SCIM `id` | **SCIM `id` = Gateway UID** <EFBFBD> ]<5D> HŪ<48> B<EFBFBD> <42> <EFBFBD> t<EFBFBD> Τ @<40> P<EFBFBD> ^<5E> F`externalId` <20> d<EFBFBD> <64> <EFBFBD> Ȥ<EFBFBD> <C8A4> ݡFZITADEL `sub` <20> <> extension `urn:cloudep:scim:2.0:User:zitadelSub` | <20> <> 10.3 |
| B | Casbin <20> h pod <20> P<EFBFBD> B | **Redis Pub/Sub <20> Y<EFBFBD> ɳq<C9B3> <71> + 5min cron <20> <> <EFBFBD> q reload <20> ©<EFBFBD> ** <EFBFBD> ]<5D> <> <EFBFBD> O<EFBFBD> I<EFBFBD> Apod <20> <> <EFBFBD> Ҥ<EFBFBD> <D2A4> |<7C> ^ | <20> <> 6.11 |
| C | Tenant <20> إ߶<D8A5> <DFB6> <EFBFBD> | **Gateway <20> <> <EFBFBD> <EFBFBD> tenant <20> <> <EFBFBD> Z<EFBFBD> ]`status=provisioning`<60> ^<5E> <> <20> I<EFBFBD> s ZITADEL Mgmt <20> <> Org <20> <> <20> ^<5E> <> `org_id` <20> <> `status=active`<60> F<EFBFBD> <46> <EFBFBD> Ѩ<EFBFBD> <D1A8> <EFBFBD> <EFBFBD> v cron <20> <> <EFBFBD> թΤ H<CEA4> u<EFBFBD> <75> failed** | <20> <> 3.1<EFBFBD> B<EFBFBD> <EFBFBD> 7.4 |
| D | Platform Admin Bootstrap | ** `make seed-platform-admin` CLI**<2A> ]<5D> ح<EFBFBD> <D8AD> <EFBFBD> platform admin uid + role<6C> ^<5E> <> <EFBFBD> D<EFBFBD> F`PLATFORM_ADMIN_ALLOWLIST_UIDS` <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ܼƧ @ break-glass<73> A**<2A> j<EFBFBD> <6A> audit** | <20> <> 18 P0 |
| E | Hybrid <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> y | ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> æs**<2A> G`Member.Origin`<60> ]<5D> D<EFBFBD> ӷ<EFBFBD> <D3B7> Gzitadel_local / ldap / scim<69> ^+ `UserRole.Source` <EFBFBD> ]<5D> C<EFBFBD> <43> role <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ӷ<EFBFBD> <D3B7> ^<5E> Fsync replace <20> <> source<63> B<EFBFBD> <42> Ū<EFBFBD> <C5AA> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> origin | <20> <> 3.2<EFBFBD> B<EFBFBD> <EFBFBD> 5<EFBFBD> B<EFBFBD> <EFBFBD> 6.10 |
| F | SCIM endpoint <20> <> <EFBFBD> v | ** <EFBFBD> 쪩 tenant <20> <> SCIM Token <20> <> <EFBFBD> v**<2A> ]read+write<74> ^+ IP allowlist + rate limit + token rotation<6F> Fv2 <20> A<EFBFBD> [ `scim.users.write` / `scim.groups.write` scope | <20> <> 7.5 |
| G | Audit log sink | ** <EFBFBD> W<EFBFBD> <EFBFBD> Mongo `audit_logs` collection**<2A> ]<5D> <> ij<EFBFBD> W<EFBFBD> <57> DB instance <20> ο W<CEBF> <57> replica set<65> ^+ TTL 90 <20> <> + <20> <> <EFBFBD> B batch flush<73> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> I<EFBFBD> ƥ<EFBFBD> <C6A5> P<EFBFBD> B<EFBFBD> g<EFBFBD> F<EFBFBD> i<EFBFBD> <69> OTEL log <20> <> <EFBFBD> g<EFBFBD> k<EFBFBD> <6B> | <20> <> 4.5<EFBFBD> B<EFBFBD> <EFBFBD> 8.2<EFBFBD> B<EFBFBD> <EFBFBD> 20 |
| H | <20> b<EFBFBD> <62> <EFBFBD> R<EFBFBD> <52> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> | ** <EFBFBD> n<EFBFBD> R 30 <20> ѫ<EFBFBD> <D1AB> ΦW<CEA6> <57> **<2A> G<EFBFBD> ߧY `status=deleted` + <20> M<EFBFBD> P token + ZITADEL disable<6C> F30 <20> <> cron <20> ΦW<CEA6> <57> PII <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]email/phone/displayName/avatar/zitadel_sub/business_*<2A> ^<5E> F<EFBFBD> O<EFBFBD> d uid/tenant_id/timestamps/audit <20> s<EFBFBD> <73> <EFBFBD> <EFBFBD> | <20> <> 5.3<EFBFBD> B<EFBFBD> <EFBFBD> 5.7 |
| I | Member <20> <> <EFBFBD> <EFBFBD> SoT | ** <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 쵦<EFBFBD> <EFBFBD> **<2A> G<EFBFBD> <47> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]zitadel_sub<75> BIdP email/name<6D> BZITADEL status<75> ^<5E> <> ZITADEL <20> <> <EFBFBD> ǡF<C7A1> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ]business_email/phone<6E> Blanguage<67> Bcurrency<63> Bavatar<61> ^<5E> <> Gateway <20> <> <EFBFBD> ǡFprovisioning <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]external_id<69> Bldap_dn<64> ^<5E> <> <20> ӷ<EFBFBD> <D3B7> t<EFBFBD> ά<EFBFBD> <CEAC> <EFBFBD> | <20> <> 5<EFBFBD> B<EFBFBD> <42> 9.1 |
| J | Directory Sync <20> ~<7E> P<EFBFBD> O<EFBFBD> @ | ** <EFBFBD> s 3 <20> <> <EFBFBD> ]<5D> s<EFBFBD> <73> 3 <20> ѡ ^<5E> 䤣<EFBFBD> <E4A4A3> <EFBFBD> ~ suspend**<2A> B<EFBFBD> 榸 sync <20> <> <EFBFBD> <EFBFBD> > 20% <20> ۰ <EFBFBD> <DBB0> <EFBFBD> dry-run + <20> iĵ<69> B<EFBFBD> <42> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> p<EFBFBD> j<EFBFBD> <6A> dry-run<75> B<EFBFBD> R<EFBFBD> <52> <EFBFBD> <EFBFBD> cron <20> q<EFBFBD> L<EFBFBD> <4C> <EFBFBD> <EFBFBD> guardrail | <20> <> 10.4 |
| K | Rate Limiting | **go-zero middleware + Redis sliding-window <20> h<EFBFBD> <68> ** <EFBFBD> GIP / UID / TenantSCIMToken <20> T<EFBFBD> h<EFBFBD> F`/auth/*` <20> C IP 60rpm + <20> C UID 30rpm<70> F`/scim/*` <20> C token 100rps<70> F<EFBFBD> @<40> <> API <20> C UID 600rpm<70> FOTP <20> <> <20> <> 5.5 <20> J<EFBFBD> <4A> <EFBFBD> N<EFBFBD> o | <20> <> 17 RateLimit<69> B<EFBFBD> <42> 20 |
| L | JWT Secret Rotation | ** <EFBFBD> 䴩 `kid` header + <20> h key <20> æs**<2A> GAccess / Refresh / Step-up <20> U<EFBFBD> ۿW<DBBF> <57> key set<65> Fñ<46> o<EFBFBD> γ ̷s kid<69> A<EFBFBD> <41> <EFBFBD> Ҩ<EFBFBD> active kid <20> W<EFBFBD> <57> <EFBFBD> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> y<EFBFBD> {<7B> G<EFBFBD> o<EFBFBD> s kid <20> <> <20> s token <20> ηs kid <20> <> <20> <> <EFBFBD> <EFBFBD> token expire <20> <> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> kid | <20> <> 4.4 |
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## 20. Audit Log <20> P Rate Limit
2026-05-19 13:56:59 +00:00
2026-05-19 17:04:26 +00:00
### 20.1 Audit Log
2026-05-20 07:01:08 +00:00
**Sink<6E> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^**<2A> G<EFBFBD> W<EFBFBD> <57> Mongo `audit_logs` collection<6F> ]<5D> <> ij**<2A> W<EFBFBD> <57> DB instance** <20> <> replica set<65> A<EFBFBD> קK OLTP <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ^<5E> C
2026-05-19 17:04:26 +00:00
```go
type AuditLog struct {
ID primitive.ObjectID
TenantID string
Action string // member.created | role.assigned | step_up.confirmed ...
Actor Actor // {uid, role_keys, ip, ua, jti}
Target Target // {kind: member|role|tenant, id, before, after}
Severity enum.Severity // info | warn | critical
Result enum.Result // success | denied | error
2026-05-20 07:01:08 +00:00
Reason string // <20> <> <EFBFBD> ѭ<EFBFBD> <D1AD> ] / denied <20> z<EFBFBD> <7A>
Metadata bson.M // <20> ʺA<CABA> <41> <EFBFBD> <EFBFBD> <EFBFBD> A<EFBFBD> p step_up_jti<74> Bscim_op<6F> Bsource
2026-05-19 17:04:26 +00:00
OccurredAt int64 // epoch ms
}
```
2026-05-20 07:01:08 +00:00
**<2A> g<EFBFBD> J<EFBFBD> <4A> <EFBFBD> <EFBFBD> <EFBFBD> G**
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| Severity | <20> Ҧ<EFBFBD> | <20> <> <EFBFBD> ѳB<D1B3> z |
2026-05-19 17:04:26 +00:00
|----------|------|---------|
2026-05-20 07:01:08 +00:00
| `critical` <EFBFBD> ]<5D> <> <EFBFBD> v<EFBFBD> B<EFBFBD> R<EFBFBD> <52> <EFBFBD> Bstep-up<75> BPlatform Admin bypass<73> B<EFBFBD> v<EFBFBD> <76> <EFBFBD> M<EFBFBD> P<EFBFBD> ^ | ** <EFBFBD> P<EFBFBD> B**<2A> g<EFBFBD> J<EFBFBD> F<EFBFBD> g<EFBFBD> <67> <EFBFBD> ѫh<D1AB> <68> <EFBFBD> ӷ~<7E> Ⱦާ@<40> ^<5E> u | <20> ڵ<EFBFBD> <DAB5> ШD<D0A8> A<EFBFBD> קK<D7A7> L<EFBFBD> <4C> <EFBFBD> <EFBFBD> <EFBFBD> q<EFBFBD> L |
| `info` <EFBFBD> ]Ū<> <C5AA> <EFBFBD> B<EFBFBD> v<EFBFBD> <76> <EFBFBD> q<EFBFBD> L<EFBFBD> ^ | ** <EFBFBD> <EFBFBD> <EFBFBD> B**<2A> Gbuffered channel <20> <> batch insert<72> ]`BatchSize=100`<60> B`FlushInterval=1s`<60> ^ | drop + metrics<63> ]<5D> iĵ<69> A<EFBFBD> <41> <EFBFBD> <EFBFBD> <EFBFBD> v<EFBFBD> T<EFBFBD> ~<7E> ȡ^ |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- TTL index<65> G`{ OccurredAt: 1 }` TTL 90 <20> ѡ F<D1A1> W<EFBFBD> L<EFBFBD> h<EFBFBD> k<EFBFBD> ɡ ]<5D> i<EFBFBD> <69> OTEL log <20> <> <EFBFBD> g<EFBFBD> O<EFBFBD> d<EFBFBD> <64> <EFBFBD> [<5B> ^
- Index<65> G`{ TenantID: 1, OccurredAt: -1 }`<60> B`{ TenantID: 1, Actor.uid: 1, OccurredAt: -1 }`<60> B`{ TenantID: 1, Action: 1, OccurredAt: -1 }`
- **<2A> ΦW<CEA6> Ƥ<EFBFBD> <C6A4> v<EFBFBD> T audit**<2A> Gactor / target uid <20> <> <EFBFBD> O<EFBFBD> d<EFBFBD> ]<5D> Y<EFBFBD> <59> member <20> w<EFBFBD> ΦW<CEA6> ơ^<5E> A<EFBFBD> F<EFBFBD> <46> <EFBFBD> u<EFBFBD> ̤֥<CCA4> <D6A5> n PII + <20> s<EFBFBD> <73> <EFBFBD> ʡv
2026-05-19 17:04:26 +00:00
### 20.2 Rate Limit
2026-05-20 07:01:08 +00:00
**<2A> N<DEB3> <EFBFBD> ]<5D> w<EFBFBD> M<EFBFBD> <4D> <EFBFBD> ^**<2A> Ggo-zero middleware<72> ]<5D> ۻs / <20> l<EFBFBD> ͡^+ Redis sliding-window<6F> C
2026-05-19 17:04:26 +00:00
```
Key: rl:{dimension}:{key}:{path_pattern} # dimension = ip | uid | scim_token
2026-05-20 07:01:08 +00:00
Value: ZSET<45> ]timestamp_ms : nonce<63> ^TTL = WindowSeconds
2026-05-19 17:04:26 +00:00
```
2026-05-20 07:01:08 +00:00
**<2A> t<EFBFBD> <74> <EFBFBD> k**<2A> G
2026-05-19 17:04:26 +00:00
```
1. now := time.Now().UnixMilli()
2. ZREMRANGEBYSCORE rl:... 0 (now - window_ms)
3. count := ZCARD rl:...
2026-05-20 07:01:08 +00:00
4. if count >= limit <20> <> 429 + Retry-After
2026-05-19 17:04:26 +00:00
5. ZADD rl:... now {random}
6. EXPIRE rl:... window
```
2026-05-20 07:01:08 +00:00
**<2A> <> <EFBFBD> h<EFBFBD> R<EFBFBD> <52> <EFBFBD> W<EFBFBD> h<EFBFBD> ]<5D> <> <EFBFBD> Ǥǰt<C7B0> ^<5E> G**
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> | | <20> <> <EFBFBD> <EFBFBD> | <20> W<EFBFBD> <57> |
2026-05-19 17:04:26 +00:00
|------|------|------|
| `/api/v1/auth/step-up/*` | UID | 10 req/min |
| `/api/v1/auth/*` | IP / UID | 60 / 30 req/min |
2026-05-20 07:01:08 +00:00
| `/scim/v2/*` | SCIM token | 6000 req/min<69> ]<5D> <> 100rps<70> ^ |
| `/api/v1/*` <EFBFBD> ]<5D> <> <EFBFBD> l<EFBFBD> ^ | UID / IP | 600 / 1200 req/min |
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- **<2A> <> <EFBFBD> } endpoint**<2A> ]exchange / refresh<73> ^<5E> H IP <20> <> <EFBFBD> D<EFBFBD> BUID <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> <EFBFBD> n<EFBFBD> J<EFBFBD> ɵL UID<49> ^
- <20> R<EFBFBD> <52> <EFBFBD> <EFBFBD> <EFBFBD> ^ `429` + `Retry-After: {seconds}` + `X-RateLimit-Remaining`
- OTP / <20> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> Ҩ<EFBFBD> <20> <> 5.5 <20> <> `verify:rate` / `verify:daily` <EFBFBD> A**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> g RateLimit middleware<72> ]<5D> קK<D7A7> N<EFBFBD> o<EFBFBD> Q<EFBFBD> <51> <EFBFBD> ӡ^
- <20> ]<5D> w<EFBFBD> <77> <20> <> 17 `RateLimit`
2026-05-19 17:04:26 +00:00
---
2026-05-20 07:01:08 +00:00
## <20> <> <EFBFBD> <EFBFBD> A<> G<EFBFBD> P model.md <20> <> <EFBFBD> <EFBFBD> <EFBFBD> Y
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
- <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> G**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> **<2A> ]<5D> [<5B> c<EFBFBD> B<EFBFBD> y<EFBFBD> {<7B> BAPI<50> B<EFBFBD> v<EFBFBD> <76> <EFBFBD> ҫ<EFBFBD> <D2AB> ^
- [model.md ](./model.md )<29> G**<2A> <> <EFBFBD> <EFBFBD> <EFBFBD> g**<2A> ]entity / repository / usecase <20> {<7B> <> <EFBFBD> X<EFBFBD> W<EFBFBD> d<EFBFBD> ^
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
<EFBFBD> <EFBFBD> <EFBFBD> @<40> ɨ<EFBFBD> <C9A8> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> f<EFBFBD> t<EFBFBD> ϥΡ C
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## <20> <> <EFBFBD> <EFBFBD> B<> GServiceContext <20> ո ˯<D5B8> <CBAF> <EFBFBD>
2026-05-19 13:56:59 +00:00
```go
type ServiceContext struct {
Config config.Config
Validator validate.Validate
2026-05-20 07:01:08 +00:00
// library clients<74> ]<5D> <> IO<49> A<EFBFBD> º<EFBFBD> <C2BA> ʸ˥~<7E> <> SDK<44> ^
2026-05-19 17:04:26 +00:00
Zitadel *zitadel.Client
EmailSender libemail.Sender
SMSSender libsms.Sender
2026-05-20 07:01:08 +00:00
SecretCipher libcrypto.Cipher // TOTP secret <20> [<5B> ѱK
2026-05-19 17:04:26 +00:00
TOTPGen libtotp.Generator
2026-05-19 13:56:59 +00:00
// usecases
AuthUC authusecase.TokenUseCase
2026-05-19 17:04:26 +00:00
StepUpTokenUC authusecase.StepUpTokenUseCase
2026-05-19 13:56:59 +00:00
MemberProvUC memberusecase.ProvisioningUseCase
MemberProfileUC memberusecase.ProfileUseCase
MemberAdminUC memberusecase.AdminUseCase
2026-05-19 17:04:26 +00:00
VerificationUC memberusecase.VerificationUseCase
StepUpUC memberusecase.StepUpUseCase
TOTPUC memberusecase.TOTPUseCase
2026-05-19 13:56:59 +00:00
TenantUC memberusecase.TenantUseCase
ScimUC memberusecase.ScimUseCase
2026-05-19 17:04:26 +00:00
// notification module
NotifierUC notifusecase.NotifierUseCase
2026-05-20 07:01:08 +00:00
// permission usecases<65> ]<5D> <> <EFBFBD> <EFBFBD> permission-server <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ^
2026-05-19 13:56:59 +00:00
PermRBACUC permusecase.RBACUseCase
PermUC permusecase.PermissionUseCase
RoleUC permusecase.RoleUseCase
RolePermUC permusecase.RolePermissionUseCase
UserRoleUC permusecase.UserRoleUseCase
RoleMappingUC permusecase.RoleMappingUseCase
AuthQueryUC permusecase.AuthorizationQueryUseCase
}
```
---
2026-05-20 07:01:08 +00:00
## <20> <> <EFBFBD> <EFBFBD> C<> Gpermission-server <20> E<EFBFBD> <45> <EFBFBD> <EFBFBD> <EFBFBD> ӡ]<5D> {<7B> <> <EFBFBD> X<EFBFBD> š^
2026-05-19 13:56:59 +00:00
2026-05-19 17:04:26 +00:00
2026-05-20 07:01:08 +00:00
| permission-server <20> ɮ<EFBFBD> | Gateway <20> ؼ<EFBFBD> | <20> E<EFBFBD> <45> <EFBFBD> 覡 |
2026-05-19 13:56:59 +00:00
|------------------------|--------------|----------|
2026-05-20 07:01:08 +00:00
| `pkg/usecase/permission_tree.go` | `model/permission/usecase/permission_tree.go` | <20> X<EFBFBD> G<EFBFBD> <47> <EFBFBD> ˷h<CBB7> <68> |
| `pkg/usecase/casbin_redis_rbac.go` | `model/permission/usecase/rbac.go` | <20> [ `tenant_id` + `role_key` <20> <> <EFBFBD> <EFBFBD> |
| `pkg/repository/casbin_redis_adapter.go` | `model/permission/repository/casbin_redis_adapter.go` | <20> אּ tenant-scoped policy key |
| `pkg/domain/rbac/rule.go` | `model/permission/rbac/rule.go` | <20> <> <EFBFBD> ˷h<CBB7> <68> |
| `etc/rbac.conf` | `etc/rbac.conf` | <20> [<5B> J tenant request / policy <20> <> <EFBFBD> <EFBFBD> |
| `pkg/usecase/role.go` | `model/permission/usecase/role.go` | `ClientID` <EFBFBD> <EFBFBD> `TenantID` |
| `pkg/usecase/role_permission.go` | `model/permission/usecase/role_permission.go` | <20> [ `tenant_id` <20> <> <EFBFBD> b<EFBFBD> P<EFBFBD> d<EFBFBD> ߺ <EFBFBD> <DFBA> <EFBFBD> |
| `pkg/usecase/user_role.go` | `model/permission/usecase/user_role.go` | <20> <> <EFBFBD> 䴩<EFBFBD> h<EFBFBD> <68> <EFBFBD> <EFBFBD> |
| `pkg/usecase/token.go` | ** `model/auth/usecase/token.go` ** | <20> <> <EFBFBD> b permission <20> Ҳ<EFBFBD> |
| `generate/database/seeders/*_permission*` | `generate/database/seeders/` <20> <> Mongo seed | <20> אּ Gateway seed job |
2026-05-19 13:56:59 +00:00
---
2026-05-20 07:01:08 +00:00
## <20> q<D7AD> <71> <EFBFBD> <EFBFBD>
2026-05-19 13:56:59 +00:00
2026-05-20 07:01:08 +00:00
| <20> <> <EFBFBD> <EFBFBD> | <20> <> <EFBFBD> <EFBFBD> | <20> <> <EFBFBD> <EFBFBD> |
2026-05-19 13:56:59 +00:00
|------|------|------|
2026-05-20 07:01:08 +00:00
| 2026-05-19 | 0.1.0 | <20> <> <EFBFBD> Z<EFBFBD> Gauth + member + permission<6F> ]B2B <20> ۩w<DBA9> q<EFBFBD> ^+ ZITADEL/LDAP/SCIM |
| 2026-05-19 | 0.2.0 | <20> <> <EFBFBD> <EFBFBD> app-cloudep-permission-server<65> GCasbin RBAC<41> BPermission Tree<65> BRole/RolePermission |
| 2026-05-19 | 0.3.0 | <20> w<EFBFBD> w<EFBFBD> <77> <20> <> 19<31> ]1<> V6<56> ^<5E> GUID <20> e<EFBFBD> <65> <EFBFBD> 榡<EFBFBD> BSCIM tenant_id <20> <> <EFBFBD> ѡ BZITADEL self-hosted<65> Bauth_gen <20> j<EFBFBD> <6A> <EFBFBD> <EFBFBD> <EFBFBD> s<EFBFBD> BB2C <20> <> Ū<EFBFBD> BRefresh <20> <> <EFBFBD> <EFBFBD> |
| 2026-05-19 | 0.4.0 | <20> ɱj<C9B1> h<EFBFBD> <68> <EFBFBD> <EFBFBD> Casbin<69> Bimmutable Role.Key<65> BSCIM externalId<49> BPlatform Admin bypass <20> P<EFBFBD> v<EFBFBD> <76> <EFBFBD> ͮĵ<CDAE> <C4B5> <EFBFBD> |
| 2026-05-20 | 0.5.0 | Best-practice <20> <> <EFBFBD> ġGJWT <20> <> <EFBFBD> <EFBFBD> role <20> ַӡBRefresh Reuse Detection<6F> BToken Exchange Nonce<63> BLogout pair<69> BRolePermission tenant <20> <> <EFBFBD> b + PUT <20> <> <EFBFBD> q<EFBFBD> <71> <EFBFBD> N<EFBFBD> B<EFBFBD> ~<7E> <> <EFBFBD> ӷ<EFBFBD> source <20> j<EFBFBD> <6A> <EFBFBD> BPlainCode <20> E<EFBFBD> X<EFBFBD> BPermission.Name <20> <> <EFBFBD> i<EFBFBD> <69> <EFBFBD> BUIDPrefix <20> <> <EFBFBD> <EFBFBD> <EFBFBD> x<EFBFBD> ߤ@<40> BRole.Key <20> W<EFBFBD> h<EFBFBD> B<EFBFBD> <42> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ƭ<EFBFBD> A<> <41> B<EFBFBD> <42> C |
| 2026-05-20 | 0.6.0 | <20> ɤJ<C9A4> ~<7E> <> <EFBFBD> <EFBFBD> <EFBFBD> Ҥ<EFBFBD> <D2A4> h<EFBFBD> GGateway <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ѵ <EFBFBD> <D1B5> U API<50> ]<5D> <> 3.4<EFBFBD> ^<5E> F<EFBFBD> s<EFBFBD> W<EFBFBD> ~<7E> <> Email / Phone <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> 5.4<EFBFBD> B<EFBFBD> <EFBFBD> 9.5<EFBFBD> ^<5E> FStep-up MFA <20> ҥΡ ]<5D> <> 5.6<EFBFBD> B<EFBFBD> <EFBFBD> 9.6<EFBFBD> ^<5E> FOTP <20> ۰ e Email + SMS Provider<65> ]<5D> <> 5.5<EFBFBD> B<EFBFBD> <EFBFBD> 17 Notification<6F> ^<5E> F<EFBFBD> <46> <EFBFBD> x admin <20> j<EFBFBD> <6A> ZITADEL TOTP<54> ]<5D> <> 3.5<EFBFBD> ^<5E> F<EFBFBD> s<EFBFBD> W<EFBFBD> <57> <EFBFBD> <EFBFBD> Redis key<65> BAPI<50> B<EFBFBD> ]<5D> w<EFBFBD> B<EFBFBD> M<EFBFBD> <4D> <EFBFBD> C 19<31> V24 |
| 2026-05-20 | 0.7.0 | <20> ݨM<DDA8> <4D> A<> VL <20> <> <EFBFBD> Ʃ<EFBFBD> <C6A9> O<EFBFBD> GSCIM id = Gateway UID + ZITADEL sub extension<6F> ]<5D> <> 10.3<EFBFBD> ^<5E> FCasbin <20> h pod Pub/Sub + 5min cron <20> ©<EFBFBD> <C2A9> ]<5D> <> 6.11<EFBFBD> ^<5E> FTenant <20> إ<EFBFBD> saga<67> ]<5D> <> 3.1<EFBFBD> ^<5E> FPlatform Admin seed CLI<4C> ]<5D> <> 18 P0<50> ^<5E> FMember.Origin + UserRole.Source <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ]<5D> <> 5.4<EFBFBD> B<EFBFBD> <EFBFBD> 6.10<EFBFBD> ^<5E> FSCIM token <20> <> <EFBFBD> v + IP allowlist<73> ]<5D> <> 7.5<EFBFBD> ^<5E> F<EFBFBD> W<EFBFBD> <57> audit_logs collection + TTL 90d<30> ]<5D> <> 20.1<EFBFBD> ^<5E> F<EFBFBD> n<EFBFBD> R 30 <20> ѰΦW<CEA6> ơ]<5D> <> 5.7<EFBFBD> ^<5E> F<EFBFBD> <46> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> SoT<6F> ]<5D> <> 5.3<EFBFBD> ^<5E> FDirectory Sync guardrail<69> ]<5D> <> 10.4<EFBFBD> ^<5E> FRedis sliding-window rate limit<69> ]<5D> <> 20.2<EFBFBD> ^<5E> FJWT kid <20> h key <20> æs<C3A6> ]<5D> <> 4.4<EFBFBD> ^ |
| 2026-05-20 | 0.8.0 | <20> <> <EFBFBD> X<EFBFBD> W<EFBFBD> <57> **Notification Module** <EFBFBD> ]<5D> <> 11<31> ^<5E> G<EFBFBD> Ҧ<EFBFBD> outbound <20> q<EFBFBD> T<EFBFBD> Τ @<40> J<EFBFBD> f<EFBFBD> B<EFBFBD> t idempotency / <20> <> <EFBFBD> <EFBFBD> / DLQ / <20> ҪO / <20> h<EFBFBD> y<EFBFBD> B<EFBFBD> ӷP<D3B7> <50> <EFBFBD> e `DoNotPersistBody` <EFBFBD> F<EFBFBD> s<EFBFBD> W ** <EFBFBD> ~<7E> <> TOTP**<2A> ]<5D> <> 5.8<EFBFBD> ^<5E> 䴩 Google Authenticator<6F> A<EFBFBD> P ZITADEL <20> <> <EFBFBD> <EFBFBD> TOTP <20> W<EFBFBD> ߡFstep-up <20> q<EFBFBD> D<EFBFBD> u<EFBFBD> <75> <EFBFBD> ǧאּ **TOTP > SMS > Email** <EFBFBD> ]<5D> <> 5.6<EFBFBD> ^<5E> F<EFBFBD> ؿ<EFBFBD> <D8BF> BServiceContext<78> BMongo collections<6E> BRedis key<65> B<EFBFBD> ]<5D> w<EFBFBD> ɡ B<C9A1> <42> <EFBFBD> I<EFBFBD> <49> <EFBFBD> ǡB<C7A1> M<EFBFBD> <4D> <EFBFBD> C 25<32> V28 <20> P<EFBFBD> B<EFBFBD> <42> <EFBFBD> s<EFBFBD> F<EFBFBD> <46> 11<31> V<EFBFBD> <56> 19 <20> <> <EFBFBD> `<60> s<EFBFBD> <73> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> +1 |
| 2026-05-20 | 0.9.0 | **UseCase <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ᵲ<EFBFBD> ]<5D> ~<7E> <> <EFBFBD> <EFBFBD> <DEBF> Ȥ<EFBFBD> <C8A4> <EFBFBD> <EFBFBD> @<40> ^** <EFBFBD> G<EFBFBD> <EFBFBD> 5.2 <20> <> <EFBFBD> g<EFBFBD> <67> Atomic primitives + Composite <20> <> <EFBFBD> h<EFBFBD> F<EFBFBD> s<EFBFBD> W `OTPUseCase` <EFBFBD> ]purpose-agnostic atomic<69> ^<5E> B`LifecycleUseCase`<60> ]CreateUnverified / Activate / Suspend / Reactivate / SoftDelete / AbortPending<6E> ^<5E> F`ProvisioningUseCase` <20> <> `EnsureFromOIDC / LDAP / SCIM` <20> T<EFBFBD> <54> <EFBFBD> <EFBFBD> <EFBFBD> F`ProfileUseCase` <20> [ `SetBusinessEmailVerified` / `SetBusinessPhoneVerified` atomic<69> F<EFBFBD> [<5B> ^ `unverified` <20> <> <EFBFBD> A<EFBFBD> ]<5D> <> platform-native <20> <> <EFBFBD> |<7C> ^<5E> F<EFBFBD> ɧ<EFBFBD> Member entity <20> <> <EFBFBD> <EFBFBD> <EFBFBD> BEnum <20> <> <EFBFBD> ס BRequest DTO<54> F<EFBFBD> s<EFBFBD> W <20> <> 5.9 <20> s<EFBFBD> ƥܨҡ]5 case<73> ^<5E> F<EFBFBD> <46> 14 OTP Redis key <20> <> purpose-based<65> F<EFBFBD> M<EFBFBD> <4D> <EFBFBD> C 19 <20> ץ<EFBFBD> <D7A5> B<EFBFBD> s<EFBFBD> W 29<32> V32 |
2026-05-21 06:45:35 +00:00
| 2026-05-21 | 1.0.0 | **Gateway <20> Τ @<40> <> <EFBFBD> U<EFBFBD> w<EFBFBD> <77> <EFBFBD> @** <EFBFBD> G<EFBFBD> q <20> <> 3.4<EFBFBD> ]<5D> אּ<EFBFBD> <EFACB0> <EFBFBD> S `/auth/register*` <EFBFBD> ^<5E> F<EFBFBD> <46> 7.1 <20> ɻ<EFBFBD> <C9BB> w<EFBFBD> <77> <EFBFBD> @ auth <20> <> <EFBFBD> ѡ F<D1A1> <46> 8.1 <20> O<EFBFBD> <4F> CloudEP JWT + dev header fallback<63> F<EFBFBD> <46> 9.1 <20> <> <EFBFBD> <EFBFBD> login / token exchange<67> F<EFBFBD> <46> 5.9 Case A <20> Ь <EFBFBD> <D0AC> w<EFBFBD> <77> <EFBFBD> @<40> F<EFBFBD> M<EFBFBD> <4D> <EFBFBD> C 19 <20> <> <EFBFBD> s<EFBFBD> C<EFBFBD> Ԩ<EFBFBD> [auth-unified-registration.md ](./auth-unified-registration.md ) |