Skip to content

Commit

Permalink
Merge pull request #49 from pilcrowOnPaper/osu-provider
Browse files Browse the repository at this point in the history
Add osu! provider
  • Loading branch information
pilcrowonpaper authored Jan 27, 2024
2 parents c15b242 + 87e09fe commit 8a823d7
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const tokens = await github.validateAuthorizationCode(code);
- Microsoft Entra ID
- Notion
- Okta
- osu!
- Patreon
- Reddit
- Salesforce
Expand Down
1 change: 1 addition & 0 deletions docs/malta.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
["Microsoft Entra ID", "/providers/microsoft-entra-id"],
["Notion", "/providers/notion"],
["Okta", "/providers/okta"],
["osu!", "/providers/osu"],
["Patreon", "/providers/patreon"],
["Reddit", "/providers/reddit"],
["Salesforce", "/providers/salesforce"],
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/providers/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { GitHub } from "arctic";

const github = new GitHub(clientId, clientSecret, {
// optional
redirectURI
redirectURI // required when multiple redirect URIs are defined
});
```

Expand Down
45 changes: 45 additions & 0 deletions docs/pages/providers/osu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: "osu!"
---

# osu!

For usage, see [OAuth 2.0 provider](/guides/oauth2).

```ts
import { Osu } from "arctic";

const osu = new Osu(clientId, clientSecret, {
// optional
redirectURI // required when multiple redirect URIs are defined
});
```

```ts
const url: URL = await osu.createAuthorizationURL(state, {
// optional
scopes
});
const tokens: OsuTokens = await osu.validateAuthorizationCode(code);
const tokens: OsuTokens = await osu.refreshAccessToken(refreshToken);
```

## Get user profile

Use the [`/me` endpoint`](https://osu.ppy.sh/docs/index.html#get-own-data).

```ts
const url = await osu.createAuthorizationURL(state, {
scopes: ["identify"] // implicitly granted by osu!
});
```

```ts
const tokens = await osu.validateAuthorizationCode(code);
const response = await fetch("https://osu.ppy.sh/api/v2/me", {
headers: {
Authorization: `Bearer ${tokens.accessToken}`
}
});
const user = await response.json();
```
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { LinkedIn } from "./providers/linkedin.js";
export { MicrosoftEntraId } from "./providers/microsoft-entra-id.js";
export { Notion } from "./providers/notion.js";
export { Okta } from "./providers/okta.js";
export { Osu } from "./providers/osu.js";
export { Patreon } from "./providers/patreon.js";
export { Reddit } from "./providers/reddit.js";
export { Salesforce } from "./providers/salesforce.js";
Expand Down Expand Up @@ -53,6 +54,7 @@ export type { LinkedInTokens } from "./providers/linkedin.js";
export type { MicrosoftEntraIdTokens } from "./providers/microsoft-entra-id.js";
export type { NotionTokens } from "./providers/notion.js";
export type { OktaTokens } from "./providers/okta.js";
export type { OsuTokens } from "./providers/osu.js";
export type { PatreonTokens } from "./providers/patreon.js";
export type { RedditTokens } from "./providers/reddit.js";
export type { SalesforceTokens } from "./providers/salesforce.js";
Expand Down
75 changes: 75 additions & 0 deletions src/providers/osu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { OAuth2Client } from "oslo/oauth2";
import { TimeSpan, createDate } from "oslo";

import type { OAuth2Provider } from "../index.js";

const authorizeEndpoint = "https://osu.ppy.sh/oauth/authorize";
const tokenEndpoint = "https://osu.ppy.sh/oauth/token";

export class Osu implements OAuth2Provider {
private client: OAuth2Client;
private clientSecret: string;

constructor(
clientId: string,
clientSecret: string,
options?: {
redirectURI?: string;
}
) {
this.client = new OAuth2Client(clientId, authorizeEndpoint, tokenEndpoint, {
redirectURI: options?.redirectURI
});
this.clientSecret = clientSecret;
}

public async createAuthorizationURL(
state: string,
options?: {
scopes?: string[];
}
): Promise<URL> {
return await this.client.createAuthorizationURL({
state,
scopes: options?.scopes ?? []
});
}

public async validateAuthorizationCode(code: string): Promise<OsuTokens> {
const result = await this.client.validateAuthorizationCode<TokenResponseBody>(code, {
authenticateWith: "request_body",
credentials: this.clientSecret
});
const tokens: OsuTokens = {
accessToken: result.access_token,
refreshToken: result.refresh_token,
accessTokenExpiresAt: createDate(new TimeSpan(result.expires_in, "s"))
};
return tokens;
}

public async refreshAccessToken(refreshToken: string): Promise<OsuTokens> {
const result = await this.client.refreshAccessToken<TokenResponseBody>(refreshToken, {
authenticateWith: "request_body",
credentials: this.clientSecret
});
const tokens: OsuTokens = {
accessToken: result.access_token,
refreshToken: result.refresh_token,
accessTokenExpiresAt: createDate(new TimeSpan(result.expires_in, "s"))
};
return tokens;
}
}

interface TokenResponseBody {
access_token: string;
expires_in: number;
refresh_token: string;
}

export interface OsuTokens {
accessToken: string;
refreshToken: string;
accessTokenExpiresAt: Date;
}

0 comments on commit 8a823d7

Please sign in to comment.