feat: add organization and member management APIs for admin and internal use

This commit is contained in:
Chris
2026-03-30 01:23:02 +08:00
parent c6cb9d6818
commit f5848a360f
17 changed files with 861 additions and 65 deletions

View File

@@ -2,14 +2,16 @@
## 1. 目標與邊界
- 網域:`memberapi.ose.tw`
- 角色:會員中心後端真相來源(User + Permission
- 角色:會員中心後端真相來源(Member + Organization + Permission
- 範圍:
- user upsert(以 `authentik_sub` 為跨系統主鍵)
- permission grant/revoke
- permission snapshot 提供給其他系統
- user/member 管理(以 `authentik_sub` 為跨系統主鍵)
- organization 管理
- member-organization 關聯管理
- permission grant/revoke 與 snapshot
- internal 查詢 API 提供其他系統
- 不在本服務處理:
- Authentik OIDC 流程頁與 UI
- 前端互動邏輯
- 前端 UI/互動流程
- Authentik hosted login page 本身
## 2. 技術棧
- Python 3.12
@@ -18,76 +20,79 @@
- PostgreSQLpsycopg
- Pydantic Settings
## 3. 後端目錄(已建立)
## 3. 後端目錄
- `backend/app/main.py`
- `backend/app/api/`
- `auth.py`
- `me.py`
- `admin.py`permission
- `admin_entities.py`member/org
- `internal.py`
- `admin.py`
- `backend/app/core/config.py`
- `backend/app/db/session.py`
- `internal_entities.py`
- `backend/app/models/`
- `user.py`
- `permission.py`
- `organization.py`
- `member_organization.py`
- `api_client.py`
- `backend/app/repositories/`
- `users_repo.py`
- `permissions_repo.py`
- `backend/app/security/api_client_auth.py`
- `backend/scripts/init_schema.sql`
- `backend/.env.example`
- `backend/.env.production.example`
- `organizations_repo.py`
- `member_organizations_repo.py`
## 4. 資料模型
- `users`
- `id`, `authentik_sub`(unique), `email`, `display_name`, `is_active`, timestamps
- `id`, `authentik_sub`(unique), `authentik_user_id`, `email`, `display_name`, `is_active`, timestamps
- `permissions`
- `id`, `user_id`, `scope_type`, `scope_id`, `module`, `action`, `created_at`
- unique constraint: `(user_id, scope_type, scope_id, module, action)`
- unique: `(user_id, scope_type, scope_id, module, action)`
- `organizations`
- `id`, `org_code`(unique), `name`, `tax_id`, `status`, timestamps
- `member_organizations`
- `id`, `member_id`, `organization_id`, `created_at`
- unique: `(member_id, organization_id)`
- `api_clients`(由 `docs/API_CLIENTS_SQL.sql` 建立)
- `client_key`, `api_key_hash`, `status`, allowlist, expires/rate-limit 欄位
## 5. API 設計MVP
## 5. API 設計
- 健康檢查
- `GET /healthz`
- 使用者路由Bearer token
- 登入
- `GET /auth/oidc/url`
- `POST /auth/oidc/exchange`
- 使用者路由Bearer
- `GET /me`
- `GET /me/permissions/snapshot`
- Bearer token 由 Authentik JWT + JWKS 驗證,並以 `sub` 自動 upsert user
- 內部路由(系統對系統)
- 管理路由(`X-Client-Key` + `X-API-Key`
- `POST /admin/permissions/grant`
- `POST /admin/permissions/revoke`
- `GET|POST|PATCH /admin/organizations...`
- `GET|POST|PATCH /admin/members...`
- `GET|POST|DELETE /admin/members/{member_id}/organizations...`
- 內部路由(`X-Internal-Secret`
- `POST /internal/users/upsert-by-sub`
- `GET /internal/permissions/{authentik_sub}/snapshot`
- `POST /internal/authentik/users/ensure`
- header: `X-Internal-Secret`
- 管理路由(後台/API client
- `POST /admin/permissions/grant`
- `POST /admin/permissions/revoke`
- headers: `X-Client-Key`, `X-API-Key`
- `GET /internal/members`
- `GET /internal/members/by-sub/{authentik_sub}`
- `GET /internal/organizations`
- `GET /internal/organizations/by-code/{org_code}`
- `GET /internal/members/{member_id}/organizations`
## 6. 安全策略
- `admin` 路由強制 API client 驗證
- client 必須存在且 `status=active`
- `expires_at` 未過期
- `api_key_hash` 驗證(支援 `sha256:<hex>` 與 bcrypt/argon2
- allowlist 驗證origin/ip/path
- `internal` 路由使用 `X-Internal-Secret` 做服務間驗證
- `me` 路由使用 Authentik Access Token 驗證:
- 使用 `AUTHENTIK_JWKS_URL``AUTHENTIK_ISSUER` 推導 JWKS
- 可選 `AUTHENTIK_AUDIENCE` 驗證 aud claim
- Authentik Admin 整合:
- 使用 `AUTHENTIK_BASE_URL + AUTHENTIK_ADMIN_TOKEN`
- 可透過 `/internal/authentik/users/ensure` 建立或更新 Authentik user
- 建議上線前:
-`.env` 範本中的明文密碼改為部署平台 secret
- API key 全部改為 argon2/bcrypt hash
- `admin` 路由API client 驗證status/過期/hash/allowlist
- `internal` 路由:`X-Internal-Secret`
- `me` 路由Auth token + JWKS 驗簽
- `/me` 補值:若 token 無 `email/name`,會呼叫 `userinfo` 補齊
## 7. 與其他系統資料流
1. mkt/admin 後端登入後,以 token `sub` 呼叫 `/internal/users/upsert-by-sub`
2. 權限調整走 `/admin/permissions/grant|revoke`
3. 需要授權判斷時,呼叫 `/internal/permissions/{sub}/snapshot`
4. mkt 系統可本地快取 snapshot並做定時補償
1. 其他系統登入後,以 token `sub` 呼叫 `/internal/users/upsert-by-sub`
2. 需要組織/會員資料時,走 `/internal/members*``/internal/organizations*`
3. 權限查詢走 `/internal/permissions/{sub}/snapshot`
4. 後台人員調整權限走 `/admin/permissions/grant|revoke`
## 8. 下一階段建議
- 入 Alembic migration
- 為 permission/action 加 enum 與驗證規則
- 增加 audit log誰在何時授權/撤銷)
- 加入 rate-limit 與可觀測性metrics + request id
## 8. 下一階段建議
- 入 Alembic migration
- 加 audit log誰在何時做了 member/org/permission 變更)
- 補上整合測試與 rate-limit metrics

View File

@@ -2,13 +2,21 @@
Base URL`https://memberapi.ose.tw`
## 0. 帳號密碼登入
### POST `/auth/login`
## 0. OIDC 登入(目前主流程)
### GET `/auth/oidc/url?redirect_uri=<frontend-callback-url>`
200 Response:
```json
{
"authorize_url": "https://auth.ose.tw/application/o/authorize/..."
}
```
### POST `/auth/oidc/exchange`
Request:
```json
{
"username": "your-authentik-username",
"password": "your-password"
"code": "authorization-code",
"redirect_uri": "http://localhost:5173/auth/callback"
}
```
@@ -22,11 +30,6 @@ Request:
}
```
401 Response:
```json
{ "detail": "invalid_username_or_password" }
```
## 1. 使用者資訊
### GET `/me`
Headers:
@@ -134,7 +137,89 @@ Request:
{ "status": "ok" }
```
## 6. 常見錯誤碼
## 6. 組織管理admin
### GET `/admin/organizations`
Headers:
- `X-Client-Key: <client_key>`
- `X-API-Key: <plain_api_key>`
Query:
- `keyword` (optional)
- `status` (optional: `active|inactive`)
- `limit` (default `50`)
- `offset` (default `0`)
### POST `/admin/organizations`
```json
{
"org_code": "ose-main",
"name": "Ose Main",
"tax_id": "12345678",
"status": "active"
}
```
### PATCH `/admin/organizations/{org_id}`
```json
{
"name": "Ose Main Updated",
"status": "inactive"
}
```
### POST `/admin/organizations/{org_id}/activate`
### POST `/admin/organizations/{org_id}/deactivate`
## 7. 會員管理admin
### GET `/admin/members`
Headers:
- `X-Client-Key: <client_key>`
- `X-API-Key: <plain_api_key>`
Query:
- `keyword` (optional)
- `is_active` (optional: `true|false`)
- `limit` (default `50`)
- `offset` (default `0`)
### POST `/admin/members`
```json
{
"authentik_sub": "authentik-sub-123",
"email": "user@example.com",
"display_name": "User Name",
"is_active": true
}
```
### PATCH `/admin/members/{member_id}`
```json
{
"display_name": "New Name",
"is_active": false
}
```
### POST `/admin/members/{member_id}/activate`
### POST `/admin/members/{member_id}/deactivate`
## 8. 會員/組織關聯admin
### GET `/admin/members/{member_id}/organizations`
### POST `/admin/members/{member_id}/organizations/{org_id}`
### DELETE `/admin/members/{member_id}/organizations/{org_id}`
## 9. 系統對系統查詢internal
Headers:
- `X-Internal-Secret: <internal_shared_secret>`
Endpoints:
- `GET /internal/members`
- `GET /internal/members/by-sub/{authentik_sub}`
- `GET /internal/organizations`
- `GET /internal/organizations/by-code/{org_code}`
- `GET /internal/members/{member_id}/organizations`
## 10. 常見錯誤碼
- `401 invalid_client`
- `401 invalid_api_key`
- `401 client_expired`

View File

@@ -17,9 +17,9 @@
- `會員管理`:會員清單、邀請/建立會員、編輯會員、停用會員、指派組織
- `權限管理`:保留現有 grant/revoke可作為管理員進階頁
## 3. 建議後端 APIv1
## 3. 後端 APIv1,已開
### Organization
### Organizationadmin
- `GET /admin/organizations`
- `POST /admin/organizations`
- `GET /admin/organizations/{org_id}`
@@ -27,7 +27,7 @@
- `POST /admin/organizations/{org_id}/activate`
- `POST /admin/organizations/{org_id}/deactivate`
### Member
### Memberadmin
- `GET /admin/members`
- `POST /admin/members`
- `GET /admin/members/{member_id}`
@@ -35,11 +35,18 @@
- `POST /admin/members/{member_id}/activate`
- `POST /admin/members/{member_id}/deactivate`
### Member x Organization
### Member x Organizationadmin
- `GET /admin/members/{member_id}/organizations`
- `POST /admin/members/{member_id}/organizations/{org_id}`
- `DELETE /admin/members/{member_id}/organizations/{org_id}`
### Internal 查詢 API給其他系統
- `GET /internal/members`
- `GET /internal/members/by-sub/{authentik_sub}`
- `GET /internal/organizations`
- `GET /internal/organizations/by-code/{org_code}`
- `GET /internal/members/{member_id}/organizations`
## 4. 建議資料表(最小可行)
- `organizations`
- `id` (uuid)