# 登入

Mini App 可以用 Micromeet 帳號讓正在使用 Cowork 的人登入，再從它自己的後端以這個人的身分呼叫 Micromeet。頁面始終拿不到密碼、token 或 Mini App 的金鑰：Cowork 只給頁面一個一次性的 code，由 Mini App 的後端拿它換 token。

只有 Micromeet 簽章過、而且至少有一個驗證過的伺服器網域的已安裝 Mini App 能讓人登入（請見[打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md)）。內建 Mini App 沒有這個模組。

## MAESTROSDK.auth.login(params?) <Badge type="warning" text="尚未發布" />

向 Cowork 要一個一次性 code，對應這個 Mini App 和目前登入 Cowork 的人。

### 參數

| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
| `scopes` | `string[]` | 否 | Mini App 要的權限，例如 `['profile:read']`。不傳就是 Micromeet 允許這個 Mini App 的全部權限 |

第一次呼叫時，Cowork 會給這個人看一張卡：Mini App 的名稱和開發者、它要的權限、將要使用的帳號；按鈕是 **授權** 和 **取消**，預設是取消。卡片只出現在這個人正在看的分頁上。授權過一次之後，同樣的權限再呼叫會直接拿到 code，不再跳出卡片。

### 回傳值

`Promise<{ ok: true; code: string; region: 'SG' | 'HK' | 'ID'; expiresIn: number } | { ok: false; error: string }>`

code 只能用一次，5 分鐘內有效（`expiresIn` 單位為秒）。把 `code` 和 `region` 傳給你的後端。

| `error` | 意思 |
|---|---|
| `unsigned` | 封裝沒有 Micromeet 的簽章，或頁面不是已安裝的 Mini App |
| `tampered` | 安裝後 Mini App 的檔案被改過；Cowork 已關閉它 |
| `host_signed_out` | 沒有人登入 Cowork，或頁面是為另一個帳號開啟的。如果這正是使用者在看的分頁，Cowork 會開啟它的登入畫面 |
| `user_cancelled` | 使用者取消、分頁在背景，或卡片開著時換了帳號 |
| `app_not_available` | 這個 AppID 在本區沒有登記，或已停用 |
| `version_not_available` | 這個版本沒有登記或已停用：請更新 Mini App |
| `no_server_domain` | 這個版本沒有驗證過的伺服器網域，或其中有網域已不再有效（請見[打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md)） |
| `scope_denied` | 要了 Micromeet 沒有允許這個 Mini App 的權限，或 `scopes` 不是字串清單 |
| `rate_limited` | 一分鐘內要的 code 太多；請稍後再試 |
| `network` | Cowork 連不上 Micromeet |

## MAESTROSDK.auth.status() <Badge type="warning" text="尚未發布" />

`Promise<{ signed: boolean; host: 'cowork'; hostSignedIn: boolean }>`。封裝的簽章有效且檔案都沒被改過時 `signed` 為 `true`；有人登入 Cowork 時 `hostSignedIn` 為 `true`。可以依此決定要不要顯示登入按鈕。

## 在 Mini App 的後端

後端保存 Micromeet 登記 AppID 時給的 client secret，Mini App 登記在幾個區就有幾組；不要放進頁面或封裝裡。後端執行在 Mini App 驗證過的某個伺服器網域上（請見[打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md)）。一步步的範例請見[開發登入 Mini App](https://docs.micromeet.ai/zh-tw/miniapp-sdk/sign-in-guide.md)。

### 頁面送來的請求

頁面的 origin 是 `miniapp://<appId>`。回應它的請求時帶上 `Access-Control-Allow-Origin: miniapp://<appId>`，預檢請求（`OPTIONS`）也要允許它用到的方法和請求標頭；否則請求在頁面裡就失敗了，到不了你的程式。Micromeet 的 token 留在後端，給頁面你自己的工作階段。

### 後端工具

`@micromeet-ai/miniapp-sdk/server`：ESM 和 CommonJS 都能用，需要 Node.js 18 或更新版本。

```js
import { createMiniappAuthClient, MiniappAuthError } from '@micromeet-ai/miniapp-sdk/server';

const auth = createMiniappAuthClient({ appId: 'ai.example.app', clientSecret: process.env.MINIAPP_SECRET_HK });
const tokens = await auth.exchangeCode({ code, region });
const me = await auth.profile({ accessToken: tokens.accessToken, region: tokens.region });
```

| 選項 | 必填 | 說明 |
|---|---|---|
| `appId` | 是 | AppID |
| `clientSecret` | 是 | 這個 client 要呼叫的那個區的 secret；每個區建立一個 client |
| `coreUrl` | 否 | `(region) => 位址`：呼叫哪個 Micromeet。預設依區呼叫正式環境 |
| `fetch` | 否 | 取代全域的 `fetch` |
| `timeoutMs` | 否 | 每個請求的逾時，預設 15000 |

| 函式 | 回傳 |
|---|---|
| `exchangeCode({ code, region })` | token：`accessToken`、`expiresIn`（秒）、`refreshToken`、`scope`（清單）、`user: { ref, name, email }`、`institution: { iid, name }`、`region` |
| `refresh({ refreshToken, region })` | 一組新的 token；舊的 refresh token 失效，保存新的 |
| `revoke({ refreshToken, region })` | 無；讓這個人登出 Mini App |
| `profile({ accessToken, region })` | `{ ref, name, email, institution: { iid, name }, region }`（`profile:read`） |
| `call({ accessToken, region, path, body? })` | 向接受 Mini App token 的 Micromeet 介面送出 `POST`，回傳它的 `data` |

出錯時每個函式都會拋出 `MiniappAuthError`，帶有 Micromeet 的 `code` 和 HTTP `status`（請見下面的「錯誤」）。

### Token

| Token | 有效期限 | 用途 |
|---|---|---|
| Access token | 15 分鐘 | 呼叫接受 Mini App token 的介面時放在 `Authorization: Bearer` |
| Refresh token | 30 天 | `refresh` 換一組新的；每個 refresh token 只能用一次 |

- 之後的每次呼叫都用 code 帶來的那個區；token 只在它的區有效。
- token 代表這個人，以及他登入時在 Cowork 裡選取的機構。
- `user.ref` 是這個人在這個 Mini App 裡的固定編號（換一個 Mini App 就是另一個）：用它當你的使用者鍵值。
- 變更密碼、登出所有裝置、離開機構、版本停用或 Mini App 停用後，token 在下一次請求時失效。

### Scope 與介面

| Scope | 介面 | 回傳 |
|---|---|---|
| `profile:read` | `POST /share/miniapp/profile` | `{ ref, name, email, institution: { iid, name }, region }` |

token 只能用在這張表裡的介面上，只限這個人授權的 scope，也不會超出這個人在 Micromeet 裡本來能做的事。

### 錯誤

| `code` | HTTP | 發生了什麼 |
|---|---|---|
| `invalid_client` | 401 | 這個區的 AppID 或 secret 不對，或 secret 已被更換 |
| `invalid_grant` | 400 | code 已用過、超過 5 分鐘或不是這個 AppID 的；refresh token 已用過、已撤銷或超過 30 天；或這個人變更了密碼、登出了所有裝置、離開了機構：重新登入 |
| `app_not_available` | 400 | 這個 Mini App 在這個區已停用 |
| `version_not_available` | 400 | 這個人登入時用的版本已停用：請更新 Mini App |
| `needLogin` | 401 | 呼叫介面時：access token 過期或已失效。呼叫 `refresh`，失敗就重新登入 |
| `forbidden` | 403 | 呼叫介面時：token 沒有這個介面的 scope，或這個人在 Micromeet 裡不能做這件事 |
| `network` | — | 後端工具連不上 Micromeet（`status` 為 0） |
| `params_invalid` | 400 或 — | `grant_type` 不對；或後端工具收到未知的區、不是介面的 `path`（`status` 為 0，不送出請求） |

### HTTP 呼叫

其他語言的後端做同樣的呼叫。每個都是向 code 所在區的 Micromeet 送出 `POST`，請求本文是 JSON：

| 區 | 位址 |
|---|---|
| SG | `https://crms-api.micromeet.ai` |
| HK | `https://crms-api-hk.micromeet.ai` |
| ID | `https://crms-api-id.micromeet.ai` |

| 路徑 | 請求本文 | 回應裡的 `data` |
|---|---|---|
| `/miniapp-open/auth/token` | `{ "grant_type": "authorization_code", "app_id", "client_secret", "code" }` | `{ access_token, token_type: "Bearer", expires_in, refresh_token, scope, user: { ref, name, email }, institution: { iid, name }, region }`；`scope` 以空格分隔 |
| `/miniapp-open/auth/token` | `{ "grant_type": "refresh_token", "app_id", "client_secret", "refresh_token" }` | 同上 |
| `/miniapp-open/auth/revoke` | `{ "app_id", "client_secret", "refresh_token" }` | `{ revoked: true }` |
| `/share/miniapp/profile` | `{}`，帶 `Authorization: Bearer <access_token>` | `{ ref, name, email, institution: { iid, name }, region }` |

成功時回應 `{ "code": 0, "success": true, "data": … }`；失敗時回應 `{ "code": "<錯誤>", "msg": "…", "success": false }`，HTTP 狀態請見上面的「錯誤」。

## 相關

- [打包與簽章](https://docs.micromeet.ai/zh-tw/miniapp-sdk/build.md): 打包 Mini App，交給 Micromeet 簽章
- [概覽](https://docs.micromeet.ai/zh-tw/miniapp-sdk/overview.md): Mini App SDK 的全部模組
