Skip to content

Commit 022180a

Browse files
Merge pull request #296 from pilcrowonpaper/next
Release v3.6.0
2 parents 507fa4c + 3f09466 commit 022180a

File tree

10 files changed

+314
-2
lines changed

10 files changed

+314
-2
lines changed

.RELEASE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- Add Mastodon client ([#288](https://github.com/pilcrowonpaper/arctic/pull/288)).
2+
- Add Autodesk Platform Services client ([#292](https://github.com/pilcrowonpaper/arctic/pull/292)).
3+
- Strava: Add `refreshAccessToken()` method ([#295](https://github.com/pilcrowonpaper/arctic/pull/295)).

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Arctic does not strictly follow semantic versioning. While we aim to only introd
3838
- Atlassian
3939
- Auth0
4040
- Authentik
41+
- Autodesk Platform Services
4142
- Battle.net
4243
- Bitbucket
4344
- Box
@@ -63,6 +64,7 @@ Arctic does not strictly follow semantic versioning. While we aim to only introd
6364
- Line
6465
- Linear
6566
- LinkedIn
67+
- Mastodon
6668
- MercadoLibre
6769
- MercadoPago
6870
- Microsoft Entra ID

docs/malta.config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
["Atlassian", "/providers/atlassian"],
2525
["Auth0", "/providers/auth0"],
2626
["Authentik", "/providers/authentik"],
27+
["Autodesk Platform Services", "/providers/autodesk"],
2728
["Battle.net", "/providers/battlenet"],
2829
["Bitbucket", "/providers/bitbucket"],
2930
["Box", "/providers/box"],
@@ -49,6 +50,7 @@
4950
["Line", "/providers/line"],
5051
["Linear", "/providers/linear"],
5152
["LinkedIn", "/providers/linkedin"],
53+
["Mastodon", "/providers/mastodon"],
5254
["MercadoLibre", "/providers/mercadolibre"],
5355
["MercadoPago", "/providers/mercadopago"],
5456
["Microsoft Entra ID", "/providers/microsoft-entra-id"],

docs/pages/providers/autodesk.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
title: "Autodesk Platform Services"
3+
---
4+
5+
# Autodesk Platform Services
6+
7+
OAuth 2.0 provider for [Autodesk Platform Services](https://aps.autodesk.com/en/docs/oauth/v2/developers_guide/overview/).
8+
9+
Also see the [OAuth 2.0 with PKCE](/guides/oauth2-pkce) guide.
10+
11+
## Initialization
12+
13+
Pass the client secret for confidential clients.
14+
15+
```ts
16+
import * as arctic from "arctic";
17+
18+
const autodesk = new arctic.Autodesk(clientId, clientSecret, redirectURI);
19+
const autodesk = new arctic.Autodesk(clientId, null, redirectURI);
20+
```
21+
22+
## Create authorization URL
23+
24+
```ts
25+
import * as arctic from "arctic";
26+
27+
const state = arctic.generateState();
28+
const codeVerifier = arctic.generateCodeVerifier();
29+
const scopes = ["openid", "user:read", "data:read"];
30+
const url = autodesk.createAuthorizationURL(state, codeVerifier, scopes);
31+
```
32+
33+
The list of scopes Autodesk Platform Services supports can be found at the [Developer's Guide/Scopes](https://aps.autodesk.com/en/docs/oauth/v2/developers_guide/scopes/) page.
34+
35+
## Validate authorization code
36+
37+
`validateAuthorizationCode()` will either return an [`OAuth2Tokens`](/reference/main/OAuth2Tokens), or throw one of [`OAuth2RequestError`](/reference/main/OAuth2RequestError), [`ArcticFetchError`](/reference/main/ArcticFetchError), [`UnexpectedResponseError`](/reference/main/UnexpectedResponseError), or [`UnexpectedErrorResponseBodyError`](/reference/main/UnexpectedErrorResponseBodyError). Autodesk Platform Services returns an access token, the access token expiration, and a refresh token.
38+
39+
```ts
40+
import * as arctic from "arctic";
41+
42+
try {
43+
const tokens = await autodesk.validateAuthorizationCode(code, codeVerifier);
44+
const accessToken = tokens.accessToken();
45+
const accessTokenExpiresAt = tokens.accessTokenExpiresAt();
46+
const refreshToken = tokens.refreshToken();
47+
} catch (e) {
48+
if (e instanceof arctic.OAuth2RequestError) {
49+
// Invalid authorization code, credentials, or redirect URI
50+
const code = e.code;
51+
// ...
52+
}
53+
if (e instanceof arctic.ArcticFetchError) {
54+
// Failed to call `fetch()`
55+
const cause = e.cause;
56+
// ...
57+
}
58+
// Parse error
59+
}
60+
```
61+
62+
## Refresh access tokens
63+
64+
Use `refreshAccessToken()` to get a new access token using a refresh token. Autodesk Platform Services returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()`
65+
66+
```ts
67+
import * as arctic from "arctic";
68+
69+
try {
70+
// Pass an empty `scopes` array to keep using the same scopes.
71+
const tokens = await autodesk.refreshAccessToken(refreshToken, scopes);
72+
const accessToken = tokens.accessToken();
73+
const accessTokenExpiresAt = tokens.accessTokenExpiresAt();
74+
} catch (e) {
75+
if (e instanceof arctic.OAuth2RequestError) {
76+
// Invalid authorization code, credentials, or redirect URI
77+
}
78+
if (e instanceof arctic.ArcticFetchError) {
79+
// Failed to call `fetch()`
80+
}
81+
// Parse error
82+
}
83+
```
84+
85+
## Revoke tokens
86+
87+
Use `revokeToken()` to revoke a token. You need to specify wether the token is an `access_token` or a `refresh_token`. This can throw the same errors as `validateAuthorizationCode()`.
88+
89+
```ts
90+
try {
91+
await autodesk.revokeToken(token, token_type);
92+
} catch (e) {
93+
if (e instanceof arctic.OAuth2RequestError) {
94+
// Invalid authorization code, credentials, or redirect URI
95+
}
96+
if (e instanceof arctic.ArcticFetchError) {
97+
// Failed to call `fetch()`
98+
}
99+
// Parse error
100+
}
101+
```
102+
103+
## OpenID Connect
104+
105+
Use OpenID Connect with the `openid` scope to get the user's profile with an ID token or the [`userinfo` endpoint](https://aps.autodesk.com/en/docs/profile/v1/reference/profile/oidcuserinfo/). Arctic provides [`decodeIdToken()`](/reference/main/decodeIdToken) for decoding the token's payload.
106+
107+
See the endpoint documentation for the token claims.
108+
109+
```ts
110+
const scopes = ["openid"];
111+
const url = autodesk.createAuthorizationURL(state, codeVerifier, scopes);
112+
```
113+
114+
```ts
115+
import * as arctic from "arctic";
116+
117+
const tokens = await autodesk.validateAuthorizationCode(code, codeVerifier);
118+
const idToken = tokens.idToken();
119+
const claims = arctic.decodeIdToken(idToken);
120+
```
121+
122+
```ts
123+
const response = await fetch("https://api.userprofile.autodesk.com/userinfo", {
124+
headers: {
125+
Authorization: `Bearer ${accessToken}`
126+
}
127+
});
128+
const user = await response.json();
129+
```

docs/pages/providers/mastodon.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
title: "Mastodon"
3+
---
4+
5+
# Mastodon
6+
7+
OAuth 2.0 provider for Mastodon.
8+
9+
Also see the [OAuth 2.0 with PKCE](/guides/oauth2-pkce) guide.
10+
11+
## Initialization
12+
13+
The `baseURL` parameter is the full URL where the Mastodon instance is hosted.
14+
15+
```ts
16+
import * as arctic from "arctic";
17+
18+
const baseURL = "https://mastodon.social";
19+
const mastodon = new arctic.Mastodon(baseURL, clientId, clientSecret, redirectURI);
20+
```
21+
22+
## Create authorization URL
23+
24+
```ts
25+
import * as arctic from "arctic";
26+
27+
const state = arctic.generateState();
28+
const codeVerifier = arctic.generateCodeVerifier();
29+
const scopes = ["read", "write"];
30+
const url = mastodon.createAuthorizationURL(state, codeVerifier, scopes);
31+
```
32+
33+
## Validate authorization code
34+
35+
`validateAuthorizationCode()` will either return an [`OAuth2Tokens`](/reference/main/OAuth2Tokens), or throw one of [`OAuth2RequestError`](/reference/main/OAuth2RequestError), [`ArcticFetchError`](/reference/main/ArcticFetchError), [`UnexpectedResponseError`](/reference/main/UnexpectedResponseError), or [`UnexpectedErrorResponseBodyError`](/reference/main/UnexpectedErrorResponseBodyError). Mastodon will only return an access token (no expiration).
36+
37+
```ts
38+
import * as arctic from "arctic";
39+
40+
try {
41+
const tokens = await mastodon.validateAuthorizationCode(code, codeVerifier);
42+
const accessToken = tokens.accessToken();
43+
} catch (e) {
44+
if (e instanceof arctic.OAuth2RequestError) {
45+
// Invalid authorization code, credentials, or redirect URI
46+
const code = e.code;
47+
// ...
48+
}
49+
if (e instanceof arctic.ArcticFetchError) {
50+
// Failed to call `fetch()`
51+
const cause = e.cause;
52+
// ...
53+
}
54+
// Parse error
55+
}
56+
```
57+
58+
## Revoke tokens
59+
60+
Use `revokeToken()` to revoke a token. This can throw the same errors as `validateAuthorizationCode()`.
61+
62+
```ts
63+
try {
64+
await mastodon.revokeToken(token);
65+
} catch (e) {
66+
if (e instanceof arctic.OAuth2RequestError) {
67+
// Invalid authorization code, credentials, or redirect URI
68+
}
69+
if (e instanceof arctic.ArcticFetchError) {
70+
// Failed to call `fetch()`
71+
}
72+
// Parse error
73+
}
74+
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "arctic",
33
"type": "module",
4-
"version": "3.5.0",
4+
"version": "3.6.0",
55
"description": "OAuth 2.0 clients for popular providers",
66
"main": "dist/index.js",
77
"types": "dist/index.d.ts",

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { Apple } from "./providers/apple.js";
44
export { Atlassian } from "./providers/atlassian.js";
55
export { Auth0 } from "./providers/auth0.js";
66
export { Authentik } from "./providers/authentik.js";
7+
export { Autodesk } from "./providers/autodesk.js";
78
export { BattleNet } from "./providers/battlenet.js";
89
export { Bitbucket } from "./providers/bitbucket.js";
910
export { Box } from "./providers/box.js";
@@ -29,6 +30,7 @@ export { Lichess } from "./providers/lichess.js";
2930
export { Line } from "./providers/line.js";
3031
export { Linear } from "./providers/linear.js";
3132
export { LinkedIn } from "./providers/linkedin.js";
33+
export { Mastodon } from "./providers/mastodon.js";
3234
export { MercadoLibre } from "./providers/mercadolibre.js";
3335
export { MercadoPago } from "./providers/mercadopago.js";
3436
export { MicrosoftEntraId } from "./providers/microsoft-entra-id.js";

src/providers/autodesk.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { CodeChallengeMethod, OAuth2Client } from "../client.js";
2+
3+
import type { OAuth2Tokens } from "../oauth2.js";
4+
5+
const authorizationEndpoint = "https://developer.api.autodesk.com/authentication/v2/authorize";
6+
const tokenEndpoint = "https://developer.api.autodesk.com/authentication/v2/token";
7+
const tokenRevocationEndpoint = "https://developer.api.autodesk.com/authentication/v2/revoke";
8+
9+
export class Autodesk {
10+
private client: OAuth2Client;
11+
12+
constructor(clientId: string, clientSecret: string | null, redirectURI: string) {
13+
this.client = new OAuth2Client(clientId, clientSecret, redirectURI);
14+
}
15+
16+
public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL {
17+
const url = this.client.createAuthorizationURLWithPKCE(
18+
authorizationEndpoint,
19+
state,
20+
CodeChallengeMethod.S256,
21+
codeVerifier,
22+
scopes
23+
);
24+
return url;
25+
}
26+
27+
public async validateAuthorizationCode(
28+
code: string,
29+
codeVerifier: string
30+
): Promise<OAuth2Tokens> {
31+
const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier);
32+
return tokens;
33+
}
34+
35+
public async refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens> {
36+
const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []);
37+
return tokens;
38+
}
39+
40+
public async revokeToken(token: string): Promise<void> {
41+
await this.client.revokeToken(tokenRevocationEndpoint, token);
42+
}
43+
}

src/providers/mastodon.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { OAuth2Client, CodeChallengeMethod } from "../client.js";
2+
import { joinURIAndPath } from "../request.js";
3+
4+
import type { OAuth2Tokens } from "../oauth2.js";
5+
6+
export class Mastodon {
7+
private authorizationEndpoint: string;
8+
private tokenEndpoint: string;
9+
private tokenRevocationEndpoint: string;
10+
11+
private client: OAuth2Client;
12+
13+
constructor(baseURL: string, clientId: string, clientSecret: string, redirectURI: string) {
14+
this.authorizationEndpoint = joinURIAndPath(baseURL, "/api/v1/oauth/authorize");
15+
this.tokenEndpoint = joinURIAndPath(baseURL, "/api/v1/oauth/token");
16+
this.tokenRevocationEndpoint = joinURIAndPath(baseURL, "/api/v1/oauth/revoke");
17+
this.client = new OAuth2Client(clientId, clientSecret, redirectURI);
18+
}
19+
20+
public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL {
21+
const url = this.client.createAuthorizationURLWithPKCE(
22+
this.authorizationEndpoint,
23+
state,
24+
CodeChallengeMethod.S256,
25+
codeVerifier,
26+
scopes
27+
);
28+
return url;
29+
}
30+
31+
public async validateAuthorizationCode(
32+
code: string,
33+
codeVerifier: string
34+
): Promise<OAuth2Tokens> {
35+
const tokens = await this.client.validateAuthorizationCode(
36+
this.tokenEndpoint,
37+
code,
38+
codeVerifier
39+
);
40+
return tokens;
41+
}
42+
43+
public async revokeToken(token: string): Promise<void> {
44+
await this.client.revokeToken(this.tokenRevocationEndpoint, token);
45+
}
46+
}

src/providers/strava.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js";
33
import type { OAuth2Tokens } from "../oauth2.js";
44

55
const authorizationEndpoint = "https://www.strava.com/oauth/authorize";
6-
const tokenEndpoint = "https://www.strava.com/oauth/token";
6+
const tokenEndpoint = "https://www.strava.com/api/v3/oauth/token";
77

88
export class Strava {
99
private clientId: string;
@@ -40,4 +40,15 @@ export class Strava {
4040
const tokens = await sendTokenRequest(request);
4141
return tokens;
4242
}
43+
44+
public async refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens> {
45+
const body = new URLSearchParams();
46+
body.set("grant_type", "refresh_token");
47+
body.set("refresh_token", refreshToken);
48+
body.set("client_id", this.clientId);
49+
body.set("client_secret", this.clientSecret);
50+
const request = createOAuth2Request(tokenEndpoint, body);
51+
const tokens = await sendTokenRequest(request);
52+
return tokens;
53+
}
4354
}

0 commit comments

Comments
 (0)