Skip to content

Commit

Permalink
Add OAuth utils
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieudutour authored and thomaslombart committed Jan 10, 2024
1 parent 87d8d13 commit 3348090
Show file tree
Hide file tree
Showing 16 changed files with 1,727 additions and 905 deletions.
4 changes: 4 additions & 0 deletions docs/utils-reference/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
- [getAvatarIcon](utils-reference/icons/getAvatarIcon.md)
- [getFavicon](utils-reference/icons/getFavicon.md)
- [getProgressIcon](utils-reference/icons/getProgressIcon.md)
- [OAuth](utils-reference/oauth/README.md)
- [OAuthService](utils-reference/oauth/OAuthService.md)
- [withAccessToken](utils-reference/oauth/withAccessToken.md)
- [getAccessToken](utils-reference/oauth/getAccessToken.md)
- [React hooks](utils-reference/react-hooks/README.md)
- [useCachedState](utils-reference/react-hooks/useCachedState.md)
- [usePromise](utils-reference/react-hooks/usePromise.md)
Expand Down
233 changes: 233 additions & 0 deletions docs/utils-reference/oauth/OAuthService.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# `OAuthService`

The `OAuthService` class is designed to abstract the OAuth authorization process using the PKCE (Proof Key for Code Exchange) flow, simplifying the integration with various OAuth providers such as Asana, GitHub, and others.

Use [OAuthServiceOptions](#OAuthServiceOptions) to configure the `OAuthService` class.

## Signature

```ts
constructor(options: OAuthServiceOptions): OAuthService
```

### Methods

#### `authorize`

Initiates the OAuth authorization process or refreshes existing tokens if necessary. Returns a promise that resolves with the access token from the authorization flow.

##### Signature

```typescript
authorize(): Promise<string>;
```

##### Example

```typescript
const accessToken = await oauthService.authorize();
```

### Properties

Some 3rd-party providers are exposed by default to make it easy to authenticate with them. Here's the full list:

- Asana
- GitHub
- Google
- Jira
- Linear
- Slack
- Zoom

#### Asana

##### Signature

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

#### GitHub

##### Signature

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

#### Google

##### Signature

```ts
const google: OAuthService
```
##### Example
```tsx
const google = OAuthService.google({
clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID
scope: 'https://www.googleapis.com/auth/drive.readonly', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
});
```

#### Jira

##### Signature

```ts
const jira: OAuthService
```
##### Example
```tsx
const jira = OAuthService.jira({
clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID
scope: 'read:jira-user read:jira-work', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
});
```

#### Linear

##### Signature

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

#### Slack

##### Signature

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

#### Zoom

##### Signature

```ts
const zoom: OAuthService
```
##### Example
```tsx
const zoom = OAuthService.zoom({
clientId: 'custom-client-id', // Optional: If omitted, defaults to a pre-configured client ID
scope: '', // Specify the scopes your application requires
personalAccessToken: 'personal-access-token', // Optional: For accessing the API directly
});
```

## Example

```ts
const client = new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
providerName: "GitHub",
providerIcon: "extension_icon.png",
providerId: "github",
description: "Connect your GitHub account",
});

const github = new OAuthService({
client,
clientId: "7235fe8d42157f1f38c0",
scopes: "notifications repo read:org read:user read:project",
authorizeUrl: "https://github.oauth.raycast.com/authorize",
tokenUrl: "https://github.oauth.raycast.com/token",
});
```

## Subclassing

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

Here's an example where `LinearOAuthService` subclasses `OAuthService`:

```ts
export class LinearOAuthService extends OAuthService {
constructor(options: ClientConstructor) {
super({
client: new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
providerName: "Linear",
providerIcon: "linear.png",
providerId: "linear",
description: "Connect your Linear 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` | The PKCE Client defined using `OAuth.PKCEClient` from `@raycast/api` | `OAuth.PKCEClient` |
| `clientId` | The app's client ID | `string` |
| `scope` | The scope of the access requested from the provider | `string` |
| `authorizeUrl` | The URL to start the OAuth flow | `string` |
| `tokenUrl` | The URL to exchange the authorization code for an access token | `string` |
| `refreshTokenUrl` | (Optional) The URL to refresh the access token if applicable | `string` |
| `personalAccessToken` | (Optional) A personal token if the provider supports it | `string` |
| `extraParameters` | (Optional) The extra parameters you may need for the authorization request | `Record<string, string>` |
| `bodyEncoding` | (Optional) Specifies the format for sending the body of the request. | `json` \| `url-encoded` |
62 changes: 62 additions & 0 deletions docs/utils-reference/oauth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# OAuth

Authenticating with OAuth in Raycast extensions is 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](utils-reference/oauth/OAuthService.md) and providers we provide out of the box (e.g GitHub with `OAuthService.github`)
2. Bringing authentication to React components using [withAccessToken](utils-reference/oauth/withAccessToken.md) and [`getAccessToken`](utils-reference/oauth/withAccessToken.md#getAccessToken)

Here are two different use-cases where you can use the utilities.

## 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:

```tsx
import { Detail, LaunchProps } from "@raycast/api";
import { withAccessToken, getAccessToken, OAuthService } from "@raycast/utils";

const github = OAuthService.github({
scopes: "notifications repo read:org read:user read:project"
});

function AuthorizedComponent(props: LaunchProps) {
const { token } = getAccessToken();
return <Detail markdown={`Access token: ${token}`} />;
}

export default withAccessToken(github)(AuthorizedComponent);
```

## Using your own client

```tsx
import { OAuth, Detail, LaunchProps } from "@raycast/api";
import { withAccessToken, getAccessToken, OAuthService } from "@raycast/utils/oauth";

const client = new OAuth.PKCEClient({
redirectMethod: OAuth.RedirectMethod.Web,
providerName: "Your Provider Name",
providerIcon: "provider_icon.png",
providerId: "yourProviderId",
description: "Connect your {PROVIDER_NAME} account",
});

const provider = new OAuthService({
client,
clientId: "YOUR_CLIENT_ID",
scopes: "YOUR SCOPES",
authorizeUrl: "YOUR_AUTHORIZE_URL",
tokenUrl: "YOUR_TOKEN_URL",
});

function AuthorizedComponent(props: LaunchProps) {
const { token } = getAccessToken();
return <Detail markdown={`Access token: ${token}`} />;
}

export default withAccessToken({ authorize: provider.authorize })(AuthorizedComponent);
```

## Additional information

If you need more information, please take a look at the subpages: [OAuthService](utils-reference/oauth/OAuthService.md) and [withAccessToken](utils-reference/oauth/withAccessToken.md)
38 changes: 38 additions & 0 deletions docs/utils-reference/oauth/getAccessToken.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# `getAccessToken`

Utility function designed for retrieving authorization tokens within a component. It ensures that your React components have the necessary authentication state, either through OAuth or a personal access token.

{% hint style="info" %}
`getAccessToken` **must** be used within components that are nested inside a component wrapped with [`withAccessToken`](utils-reference/oauth/withAccessToken.md). Otherwise, the function will fail with an error.
{% endhint %}

## Signature

```tsx
function getAccessToken(): {
token: string;
type: "oauth" | "personal";
}
```

### Return

The function returns an object containing the following properties:
- `token`: A string representing the access token.
- `type`: An optional string that indicates the type of token retrieved. It can either be `oauth` for OAuth tokens or `personal` for personal access tokens.

## Example

Here's a simple example:

```tsx
import { Detail } from "@raycast/api";
import { authorize } from "./oauth"

function AuthorizedComponent() {
const { token } = getAccessToken();
return <Detail markdown={`Access token: ${token}`} />;
}

export default withAccessToken({ authorize })(AuthorizedComponent);
```
Loading

0 comments on commit 3348090

Please sign in to comment.