Skip to content

Commit

Permalink
OAuth utils: Improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaslombart committed Feb 2, 2024
1 parent d829827 commit 310603f
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 110 deletions.
164 changes: 94 additions & 70 deletions docs/utils-reference/oauth/OAuthService.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ Initiates the OAuth authorization process or refreshes existing tokens if necess

##### Signature

```typescript
authorize(): Promise<string>;
```ts
OAuthService.authorize(): Promise<string>;
```

##### Example
Expand All @@ -50,7 +50,7 @@ const accessToken = await oauthService.authorize();

### Built-in Services

We expose by default some services using `OAuthService` to make it easy to authenticate with them:
Some services are exposed as static properties in `OAuthService` to make it easy to authenticate with them:

- [Asana](#asana)
- [GitHub](#github)
Expand All @@ -60,131 +60,130 @@ We expose by default some services using `OAuthService` to make it easy to authe
- [Slack](#slack)
- [Zoom](#zoom)

Some of these services already have a default client configured so that you only have to specify the permission scopes.
Asana, GitHub, Linear, and Slack already have an OAuth app configured by Raycast so that you can use them right of the box by specifing only the permission scopes. You are still free to create an OAuth app for them if you want.

Google, Jira and Zoom don't have an OAuth app configured by Raycast so you'll have to create one if you want to use them.

Use [ProviderOptions](#provideroptions) or [ProviderWithDefaultClientOptions](#providerwithdefaultclientoptions) to configure these built-in services.

#### Asana

##### Signature

```ts
OAuthService.asana: (options: ProviderWithDefaultClientOptions) => OAuthService
```

##### Example

```tsx
const asana = OAuthService.asana({
clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for Asana
scope: 'default', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
});
const asana = OAuthService.asana({ scope: 'default' })
```

#### GitHub

##### Signature

```ts
OAuthService.github: (options: ProviderWithDefaultClientOptions) => OAuthService
```

##### Example

```tsx
const github = OAuthService.github({
clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for GitHub
scope: 'repo user', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
});
const github = OAuthService.github({ scope: 'repo user' })
```

#### Google

{% hint style="info" %}
Google has verification processes based on the required scopes for your extension. Therefore, you need to configure your own client for it.
{% endhint %}
##### Signature

```ts
OAuthService.google: (options: ProviderOptions) => OAuthService
```

##### Example

```tsx
const google = OAuthService.google({
clientId: 'custom-client-id',
authorizeUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scope: 'https://www.googleapis.com/auth/drive.readonly', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
scope: 'https://www.googleapis.com/auth/drive.readonly',
});
```

#### Jira

{% hint style="info" %}
Jira requires scopes to be enabled manually in the OAuth app settings. Therefore, you need to configure your own client for it.
{% endhint %}
##### Signature

```ts
OAuthService.jira: (options: ProviderOptions) => OAuthService
```

##### Example

```tsx
const jira = OAuthService.jira({
clientId: 'custom-client-id',
authorizeUrl: 'https://auth.atlassian.com/authorize',
tokenUrl: 'https://api.atlassian.com/oauth/token',
scope: 'read:jira-user read:jira-work offline_access', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
scope: 'read:jira-user read:jira-work offline_access'
});
```

#### Linear

##### Signature

```ts
OAuthService.linear: (options: ProviderOptions) => OAuthService
```

##### Example

```tsx
const linear = OAuthService.linear({
clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for Linear
scope: 'read write', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
});
const linear = OAuthService.linear({ scope: 'read write' })
```

#### Slack

##### Signature

```ts
OAuthService.slack: (options: ProviderWithDefaultClientOptions) => OAuthService
```

##### Example

```tsx
const slack = OAuthService.slack({
clientId: 'custom-client-id', // Optional: If omitted, defaults to Raycast's client ID for Slack
scope: 'emoji:read', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
});
const slack = OAuthService.slack({ scope: 'emoji:read' })
```

#### Zoom

{% hint style="info" %}
Zoom requires scopes to be enabled manually in the OAuth app settings. Therefore, you need to configure your own client for it.
{% endhint %}
##### Signature

```ts
OAuthService.zoom: (options: ProviderOptions) => OAuthService
```

##### Example

```tsx
const zoom = OAuthService.zoom({
clientId: 'custom-client-id',
authorizeUrl: 'https://zoom.us/oauth/authorize',
tokenUrl: 'https://zoom.us/oauth/token',
scope: 'meeting:write', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
scope: 'meeting:write',
personalAccessToken: 'personal-access-token',
});
```

## Subclassing

You can subclass `OAuthService` to create a tailored service for other OAuth providers by setting predefined defaults.

Here's an example:

```ts
export class CustomOAuthService extends OAuthService {
constructor(options: ClientConstructor) {
super({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
providerName: "PROVIDER_NAME",
providerIcon: "provider.png",
providerId: "PROVIDER-ID",
description: "Connect your {PROVIDER_NAME} account",
}),
clientId: "YOUR_CLIENT_ID",
authorizeUrl: "YOUR_AUTHORIZE_URL",
tokenUrl: "YOUR_TOKEN_URL",
scope: "YOUR_SCOPES"
extraParameters: {
actor: "user",
},
});
}
}
```

## Types

### OAuthServiceOptions

Here's an updated markdown table with a "Type" column:

| Property Name | Description | Type |
|---------------|-------------|------|
| client<mark style="color:red;">*</mark> | The PKCE Client defined using `OAuth.PKCEClient` from `@raycast/api` | `OAuth.PKCEClient` |
Expand All @@ -194,5 +193,30 @@ Here's an updated markdown table with a "Type" column:
| tokenUrl<mark style="color:red;">*</mark> | The URL to exchange the authorization code for an access token | `string` |
| refreshTokenUrl | The URL to refresh the access token if applicable | `string` |
| personalAccessToken | A personal token if the provider supports it | `string` |
| onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` |
| extraParameters | The extra parameters you may need for the authorization request | `Record<string, string>` |
| bodyEncoding | Specifies the format for sending the body of the request. | `json` \| `url-encoded` |

### ProviderOptions

| Property Name | Description | Type |
|---------------|-------------|------|
| clientId<mark style="color:red;">*</mark> | The app's client ID | `string` |
| scope<mark style="color:red;">*</mark> | The scope of the access requested from the provider | `string` |
| authorizeUrl<mark style="color:red;">*</mark> | The URL to start the OAuth flow | `string` |
| tokenUrl<mark style="color:red;">*</mark> | The URL to exchange the authorization code for an access token | `string` |
| refreshTokenUrl | The URL to refresh the access token if applicable | `string` |
| personalAccessToken | A personal token if the provider supports it | `string` |
| onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` |

### ProviderWithDefaultClientOptions

| Property Name | Description | Type |
|---------------|-------------|------|
| scope<mark style="color:red;">*</mark> | The scope of the access requested from the provider | `string` |
| clientId | The app's client ID | `string` |
| authorizeUrl | The URL to start the OAuth flow | `string` |
| tokenUrl | The URL to exchange the authorization code for an access token | `string` |
| refreshTokenUrl | The URL to refresh the access token if applicable | `string` |
| personalAccessToken | A personal token if the provider supports it | `string` |
| onAuthorize | A callback function that is called once the user has been properly logged in through OAuth when used with `withAccessToken` | `string` |
10 changes: 6 additions & 4 deletions docs/utils-reference/oauth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

Dealing with OAuth can be tedious. So we've built a set of utilities to make that task way easier. There's two part to our utilities:

1. Authenticating with the service using [OAuthService](./OAuthService.md) or some built-in providers (e.g GitHub with `OAuthService.github`)
1. Authenticating with a service using [OAuthService](./OAuthService.md) or built-in providers (e.g GitHub with `OAuthService.github`)
2. Bringing authentication to Raycast commands using [withAccessToken](./withAccessToken.md) and [`getAccessToken`](./getAccessToken.md)

Here are two different use-cases where you can use the utilities.
`OAuthService`, `withAccessToken`, and `getAccessToken` are designed to work together. You'll find below two different use-cases for which you can use these utils.

## Using a built-in provider

We provide 3rd party providers that you can use out of the box such as GitHub or Linear. Here's how you can use them:
We provide built-in providers that you can use out of the box, such as GitHub or Linear. You don't need to configure anything for them apart from the scope your extension requires.

```tsx
import { Detail, LaunchProps } from "@raycast/api";
Expand All @@ -27,6 +27,8 @@ function AuthorizedComponent(props: LaunchProps) {
export default withAccessToken(github)(AuthorizedComponent);
```

You can see our different providers in the following page: [OAuthService](./OAuthService.md)

## Using your own client

```tsx
Expand All @@ -44,7 +46,7 @@ const client = new OAuth.PKCEClient({
const provider = new OAuthService({
client,
clientId: "YOUR_CLIENT_ID",
scopes: "YOUR SCOPES",
scopes: "YOUR_SCOPES",
authorizeUrl: "YOUR_AUTHORIZE_URL",
tokenUrl: "YOUR_TOKEN_URL",
});
Expand Down
2 changes: 0 additions & 2 deletions docs/utils-reference/oauth/getAccessToken.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ The function returns an object containing the following properties:

## Example

Here's a simple example:

```tsx
import { Detail } from "@raycast/api";
import { authorize } from "./oauth"
Expand Down
15 changes: 10 additions & 5 deletions docs/utils-reference/oauth/withAccessToken.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ function withAccessToken<T = any>(
### Arguments

`options` is an object containing:
- `options.authorize` is a function that initiates the OAuth token retrieval process. It returns a promise that resolves to an access token.
- `options.personalAccessToken` is an optional string that represents an already obtained personal access token. When `options.personalAccessToken` is provided, it uses that token. Otherwise, it calls `options.authorize` to fetch an OAuth token asynchronously.
- `options.client` is an optional instance of a PKCE Client that you can create using Raycast API. This client is used to return the `idToken` as part of the `onAuthorize` callback below.
- `options.onAuthorize` is an optional callback function that is called once the user has been properly logged in through OAuth. This function is called with the `token`, its type (whether it comes from an OAuth flow or if it's a personal access token) and an `idToken` if `options.client` is provided and if it's returned in the initial token set.
- `options.authorize`: a function that initiates the OAuth token retrieval process. It returns a promise that resolves to an access token.
- `options.personalAccessToken`: an optional string that represents an already obtained personal access token. When `options.personalAccessToken` is provided, it uses that token. Otherwise, it calls `options.authorize` to fetch an OAuth token asynchronously.
- `options.client`: an optional instance of a PKCE Client that you can create using Raycast API. This client is used to return the `idToken` as part of the `onAuthorize` callback below.
- `options.onAuthorize`: an optional callback function that is called once the user has been properly logged in through OAuth. This function is called with the `token`, its type (`oauth` if it comes from an OAuth flow or `personal` if it's a personal access token), and `idToken` if it's returned from `options.client`'s initial token set.

### Return

Expand All @@ -30,7 +30,6 @@ Note that the access token isn't injected into the wrapped component props. Inst

## Example


{% tabs %}
{% tab title="view.tsx" %}

Expand Down Expand Up @@ -108,4 +107,10 @@ type WithAccessTokenParameters = {
personalAccessToken?: string;
onAuthorize?: (params: OnAuthorizeParams) => void;
};
```

### WithAccessTokenComponentOrFn

```ts
type WithAccessTokenComponentOrFn<T = any> = ((params: T) => Promise<void> | void) | React.ComponentType<T>;
```
Loading

0 comments on commit 310603f

Please sign in to comment.