# OAuth ויישומי מפתחים

> רישום וניהול של אפליקציות OAuth ב-Nodaro עם client.developerApps, והחלפת קודים, ביטול אסימונים וקריאת נתונים למסך ההסכמה עם client.oauth.

Source: https://nodaro.ai/he/docs/developers/sdk/developer-apps

**`client.developerApps`** מנהל את אפליקציות ה-OAuth שבבעלותכם: אפליקציות שמאפשרות למשתמשי Nodaro אחרים לתת לתוכנה שלכם גישה לחשבונות שלהם. המשאב **`client.oauth`** מכסה את צד השרת של זרימת OAuth 2.0: הוא מחליף קוד הרשאה באסימון גישה, מבטל אסימונים ומחזיר את הנתונים הציבוריים שמסך ההסכמה מציג. זרימת ההסכמה המלאה מתוארת בדף [אפליקציות OAuth](https://nodaro.ai/docs/developers/oauth).

## מתודות
| מתודה | מה היא עושה |
| --- | --- |
| [`developerApps.list()`](#developerappslist) | רשימת האפליקציות שלכם |
| [`developerApps.get(id)`](#developerappsgetid) | קריאת אפליקציה אחת |
| [`developerApps.create(input)`](#developerappscreateinput) | רישום אפליקציה וקבלת הסוד שלה |
| [`developerApps.update(id, input)`](#developerappsupdateid-input) | שינוי אפליקציה |
| [`developerApps.delete(id)`](#developerappsdeleteid) | מחיקת אפליקציה |
| [`developerApps.rotateSecret(id)`](#developerappsrotatesecretid) | החלפת הסוד של אפליקציה בסוד חדש |
| [`oauth.exchangeCode(input)`](#oauthexchangecodeinput) | החלפת קוד הרשאה באסימון גישה |
| [`oauth.revoke(token)`](#oauthrevoketoken) | ביטול אסימון גישה |
| [`oauth.getAppInfo(clientId, redirectUri?)`](#oauthgetappinfoclientid-redirecturi) | קריאת הנתונים הציבוריים של אפליקציה למסך הסכמה |

## הרשאות
אפליקציה מבקשת הרשאות, ומשתמש מאשר אותן. השרת מקבל את ההרשאות האלה:

| הרשאה | מה היא מאפשרת |
| --- | --- |
| `workflows:read` | קריאת תהליכים |
| `workflows:write` | יצירה ושינוי של תהליכים |
| `workflows:execute` | הרצת תהליכים |
| `jobs:read` | קריאת משימות |
| `assets:read` | קריאת דמויות, מיקומים, אובייקטים ונכסים אחרים |
| `assets:write` | יצירה ושינוי של נכסים |
| `credits:read` | קריאת יתרת הקרדיטים |
| `apps:read` | קריאת אפליקציות שפורסמו |
| `pipelines:read` | קריאת פייפליינים |
| `pipelines:execute` | התחלה וביטול של פייפליינים |
| `pipelines:approve` | אישור ודחייה של שלבים בפייפליין |
| `presets:read` | קריאת ערכות שמורות |
| `workspaces:read` | רשימת סביבות העבודה שהמשתמש חבר בהן |
| `workspaces:write` | בחירת סביבת העבודה שבה האפליקציה פועלת |

הדף [אפליקציות OAuth](https://nodaro.ai/docs/developers/oauth#scopes) מציג מה כתוב במסך ההסכמה לכל הרשאה. קריאה בלי ההרשאה שהיא דורשת נכשלת עם `ForbiddenError`, והשדה `missingScope` של השגיאה מציין את ההרשאה החסרה. ראו את הסעיף [הרשאות (scopes) והרשאות חסרות](https://nodaro.ai/docs/developers/sdk/auth#scopes-and-missing-permissions) בדף אימות.

## client.developerApps
רק הבעלים של אפליקציה יכולים לקבל את הפרטים שלה או לשנות אותה. סודות מוחזרים פעם אחת בדיוק.

### developerApps.list()‎
מחזירה את רשימת האפליקציות שלכם.

```ts
list(): Promise<{ data: DeveloperApp[] }>
```

```ts
const { data: apps } = await client.developerApps.list()
const mine = apps.filter((app) => (app.kind ?? "user") === "user")
```

ל-`DeveloperApp` יש `id`, `name`, `description`, `logoUrl`, `homepageUrl`, `redirectUris`, `allowedOrigins`, `scopesRequested`, `clientId`, `status` (`active`, `suspended` או `pending_review`), `kind`, `createdAt` ו-`updatedAt`. הערך של `kind` הוא `"user"` באפליקציה שרשמתם. הערכים האחרים, `dynamic_mcp`, `first_party_mcp` ו-`community_instance`, מציינים לקוחות שרשמו את עצמם. רק אפליקציות מסוג `"user"` נספרות במגבלה של חמש אפליקציות.

### developerApps.get(id)‎
מחזירה אפליקציה אחת. הסוד אף פעם לא כלול בתגובה.

```ts
get(id: string): Promise<{ data: DeveloperApp }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האפליקציה." },
}}
/>

```ts
const { data: app } = await client.developerApps.get(appId)
```

### developerApps.create(input)‎
רושמת אפליקציה. התגובה כוללת את `clientSecret`. שמרו אותו עכשיו: השרת שומר רק גיבוב שלו.

```ts
create(input: CreateDeveloperAppInput): Promise<{ data: DeveloperApp & { clientSecret: string } }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "שם האפליקציה, כפי שמשתמשים רואים אותו במסך ההסכמה." },
redirectUris: { type: 'string[]', required: true, description: "1–10 כתובות, כל אחת מהן כתובת HTTPS או כתובת http://localhost." },
scopesRequested: { type: 'DeveloperAppScope[]', required: true, description: "הרשאה אחת לפחות." },
allowedOrigins: { type: 'string[]', description: "עד 5 origins, בלי נתיב, בלי מחרוזת שאילתה ובלי fragment, שמותר להם לקרוא ל-API מדפדפן עם האסימונים של האפליקציה הזו." },
description: { type: 'string', description: "תיאור למסך ההסכמה." },
homepageUrl: { type: 'string', description: "דף הבית של האפליקציה שלכם." },
logoUrl: { type: 'string', description: "הלוגו של האפליקציה שלכם." },
}}
/>

```ts
const { data } = await client.developerApps.create({
name: "My integration",
redirectUris: ["https://example.com/oauth/callback"],
scopesRequested: ["workflows:read", "workflows:execute"],
})
console.log(data.clientId, data.clientSecret) // save both now
```

אפשר לרשום חמש אפליקציות. רישום של אפליקציה שישית נכשל עם `400 limit_reached`.

### developerApps.update(id, input)‎
משנה אפליקציה. שלחו רק את השדות שאתם משנים; הכללים של `create()` חלים על כל אחד מהם.

```ts
update(id: string, input: UpdateDeveloperAppInput): Promise<{ data: DeveloperApp }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האפליקציה." },
name: { type: 'string', description: "שם האפליקציה." },
description: { type: 'string', description: "התיאור." },
homepageUrl: { type: 'string', description: "דף הבית." },
logoUrl: { type: 'string', description: "הלוגו." },
redirectUris: { type: 'string[]', description: "הרשימה החדשה והמלאה של כתובות ההפניה." },
allowedOrigins: { type: 'string[]', description: "הרשימה החדשה והמלאה של ה-origins שמותר להם לקרוא מדפדפן." },
scopesRequested: { type: 'DeveloperAppScope[]', description: "הרשימה החדשה והמלאה של ההרשאות." },
}}
/>

```ts
await client.developerApps.update(appId, {
redirectUris: ["https://example.com/oauth/callback", "https://staging.example.com/oauth/callback"],
})
```

### developerApps.delete(id)‎
מוחקת אפליקציה.

```ts
delete(id: string): Promise<{ success: true }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האפליקציה." },
}}
/>

```ts
await client.developerApps.delete(appId)
```

כשהמזהה לא קיים או שהאפליקציה לא שלכם, המתודה זורקת `NotFoundError`.

### developerApps.rotateSecret(id)‎
יוצרת סוד לקוח חדש, והסוד הישן מפסיק לעבוד מיד. הסוד החדש מוחזר פעם אחת בלבד.

```ts
rotateSecret(id: string): Promise<{ clientSecret: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה האפליקציה." },
}}
/>

```ts
const { clientSecret } = await client.developerApps.rotateSecret(appId)
```

עדכנו מיד את הסוד בשרת שלכם, כי החלפות של קוד באסימון עם הסוד הישן מפסיקות לעבוד.

## client.oauth
נקודות הקצה של OAuth 2.0 שהשרת של האפליקציה שלכם קורא להן. שמות השדות כתובים ב-snake_case, כמו בתקן OAuth.

### oauth.exchangeCode(input)‎
מחליפה את קוד ההרשאה מההפניה של מסך ההסכמה באסימון גישה (`POST /v1/oauth/token`). ה-SDK מוסיף בשבילכם את `grant_type: "authorization_code"`.

**לעולם אל תקראו למתודה הזו מדפדפן.** הבקשה כוללת את סוד הלקוח שלכם, שחייב להישאר בשרת שלכם.

```ts
exchangeCode(input: {
client_id: string
client_secret: string
code: string
redirect_uri: string
}): Promise<{ access_token: string; token_type: "Bearer"; scope: string; expires_in: number }>
```

<TypeTable
type={{
client_id: { type: 'string', required: true, description: "מזהה הלקוח של האפליקציה שלכם." },
client_secret: { type: 'string', required: true, description: "סוד הלקוח של האפליקציה שלכם." },
code: { type: 'string', required: true, description: "הקוד מההפניה של מסך ההסכמה." },
redirect_uri: { type: 'string', required: true, description: "אותה כתובת הפניה שבה השתמשתם כדי לקבל את הקוד." },
}}
/>

```ts

const tokens = await client.oauth.exchangeCode({
client_id: process.env.NODARO_CLIENT_ID!,
client_secret: process.env.NODARO_CLIENT_SECRET!,
code: req.query.code as string,
redirect_uri: "https://example.com/oauth/callback",
})

// Act for the user who approved your app
const userClient = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(tokens.access_token),
})
```

השדה `scope` מפרט את ההרשאות שאושרו, מופרדות ברווחים, והשדה `expires_in` הוא משך התוקף של האסימון בשניות.

### oauth.revoke(token)‎
מבטלת אסימון גישה (`POST /v1/oauth/revoke`, לפי RFC 7009). המתודה תמיד מחזירה `{ success: true }`, גם לאסימון לא מוכר, כי התקן אוסר לחשוף אם אסימון היה תקף.

```ts
revoke(token: string): Promise<{ success: true }>
```

<TypeTable
type={{
token: { type: 'string', required: true, description: "אסימון הגישה לביטול." },
}}
/>

```ts
await client.oauth.revoke(accessToken)
```

### oauth.getAppInfo(clientId, redirectUri?)‎
מחזירה את הנתונים הציבוריים של אפליקציה למסך הסכמה (`GET /v1/oauth/app-info`). המתודה לא צריכה אסימון.

```ts
getAppInfo(clientId: string, redirectUri?: string): Promise<OAuthAppInfo>
```

<TypeTable
type={{
clientId: { type: 'string', required: true, description: "מזהה הלקוח של האפליקציה." },
redirectUri: { type: 'string', description: "כתובת הפניה לבדיקה. התגובה מציינת אז אם בדיוק הכתובת הזו רשומה." },
}}
/>

```ts
const info = await client.oauth.getAppInfo(clientId, "https://yourapp.com/oauth/callback")
if (!info.redirectUriRegistered) throw new Error("Unregistered redirect URI")
```

בתגובה יש `name`, `description`, `logoUrl`, `homepageUrl`, `scopesRequested` ו-`redirectUriRegistered`. הערך של `redirectUriRegistered` הוא `true` רק לכתובת שרשומה בדיוק כפי שהיא, ו-`null` כשלא שולחים `redirectUri`. כך מסך הסכמה יכול לדחות הפניה לכתובת שלא נרשמה, בלי לחשוף את רשימת הכתובות.

## שאלות נפוצות

### איך רושמים אפליקציית OAuth עם ה-SDK?

קראו ל-client.developerApps.create עם שם, עם כתובת הפניה אחת לפחות ועם ההרשאות שאתם צריכים. התגובה כוללת את clientId ואת clientSecret. הסוד מוצג פעם אחת בלבד, ולכן שמרו אותו מיד.

### האם אפשר להחליף קוד OAuth באסימון בדפדפן?

לא. המתודה client.oauth.exchangeCode צריכה את סוד הלקוח שלכם, שחייב להישאר בשרת שלכם. בצעו את ההחלפה בקוד שרת, ותנו לדפדפן רק את מה שהוא צריך.

### כמה אפליקציות OAuth אפשר לרשום?

חמש אפליקציות שאתם רושמים בעצמכם. רישום של אפליקציה שישית נכשל עם 400 limit_reached. אפליקציות שרשמו את עצמן, כגון לקוחות MCP, לא נספרות במגבלה.

### מה קורה כשמחליפים את סוד הלקוח?

המתודה client.developerApps.rotateSecret יוצרת סוד חדש, והסוד הישן מפסיק לעבוד מיד. הסוד החדש מוחזר פעם אחת בלבד.
