├── .eslintignore ├── .eslintrc.cjs ├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ ├── github-pages.yaml │ ├── publish-docs.yaml │ └── publish.yaml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── CNAME ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── arctic.js.org └── index.html ├── docs ├── .gitignore ├── malta.config.json └── pages │ ├── guides │ ├── generic-oauth2-client.md │ ├── migrate-v2.md │ ├── migrate-v3.md │ ├── oauth2-pkce.md │ └── oauth2.md │ ├── index.md │ ├── providers │ ├── 42.md │ ├── amazon-cognito.md │ ├── anilist.md │ ├── apple.md │ ├── atlassian.md │ ├── auth0.md │ ├── authentik.md │ ├── autodesk.md │ ├── battlenet.md │ ├── bitbucket.md │ ├── box.md │ ├── bungie.md │ ├── coinbase.md │ ├── discord.md │ ├── donation-alerts.md │ ├── dribbble.md │ ├── dropbox.md │ ├── epicgames.md │ ├── etsy.md │ ├── facebook.md │ ├── figma.md │ ├── gitea.md │ ├── github.md │ ├── gitlab.md │ ├── google.md │ ├── intuit.md │ ├── kakao.md │ ├── keycloak.md │ ├── kick.md │ ├── lichess.md │ ├── line.md │ ├── linear.md │ ├── linkedin.md │ ├── mastodon.md │ ├── mercadolibre.md │ ├── mercadopago.md │ ├── microsoft-entra-id.md │ ├── myanimelist.md │ ├── naver.md │ ├── notion.md │ ├── okta.md │ ├── osu.md │ ├── patreon.md │ ├── polar.md │ ├── reddit.md │ ├── roblox.md │ ├── salesforce.md │ ├── shikimori.md │ ├── slack.md │ ├── spotify.md │ ├── startgg.md │ ├── strava.md │ ├── synology.md │ ├── tiktok.md │ ├── tiltify.md │ ├── tumblr.md │ ├── twitch.md │ ├── twitter.md │ ├── vk.md │ ├── withings.md │ ├── workos.md │ ├── yahoo.md │ ├── yandex.md │ └── zoom.md │ └── reference │ ├── index.md │ └── main │ ├── ArcticFetchError.md │ ├── CodeChallengeMethod.md │ ├── OAuth2Client │ ├── createAuthorizationURL.md │ ├── createAuthorizationURLWithPKCE.md │ ├── index.md │ ├── refreshAccessToken.md │ ├── revokeToken.md │ └── validateAuthorizationCode.md │ ├── OAuth2RequestError.md │ ├── OAuth2Tokens │ ├── accessToken.md │ ├── accessTokenExpiresAt.md │ ├── accessTokenExpiresInSeconds.md │ ├── hasRefreshToken.md │ ├── idToken.md │ ├── index.md │ └── refreshToken.md │ ├── UnexpectedErrorResponseBodyError.md │ ├── UnexpectedResponseError.md │ ├── decodeIdToken.md │ ├── generateCodeVerifier.md │ ├── generateState.md │ └── index.md ├── package.json ├── src ├── client.ts ├── index.ts ├── oauth2.ts ├── oidc.test.ts ├── oidc.ts ├── providers │ ├── 42.ts │ ├── amazon-cognito.ts │ ├── anilist.ts │ ├── apple.ts │ ├── atlassian.ts │ ├── auth0.ts │ ├── authentik.ts │ ├── autodesk.ts │ ├── battlenet.ts │ ├── bitbucket.ts │ ├── box.ts │ ├── bungie.ts │ ├── coinbase.ts │ ├── discord.ts │ ├── donation-alerts.ts │ ├── dribbble.ts │ ├── dropbox.ts │ ├── epicgames.ts │ ├── etsy.ts │ ├── facebook.ts │ ├── figma.ts │ ├── gitea.ts │ ├── github.ts │ ├── gitlab.ts │ ├── google.ts │ ├── intuit.ts │ ├── kakao.ts │ ├── keycloak.ts │ ├── kick.ts │ ├── lichess.ts │ ├── line.ts │ ├── linear.ts │ ├── linkedin.ts │ ├── mastodon.ts │ ├── mercadolibre.ts │ ├── mercadopago.ts │ ├── microsoft-entra-id.ts │ ├── myanimelist.ts │ ├── naver.ts │ ├── notion.ts │ ├── okta.ts │ ├── osu.ts │ ├── patreon.ts │ ├── polar.ts │ ├── reddit.ts │ ├── roblox.ts │ ├── salesforce.ts │ ├── shikimori.ts │ ├── slack.ts │ ├── spotify.ts │ ├── startgg.ts │ ├── strava.ts │ ├── synology.ts │ ├── tiktok.ts │ ├── tiltify.ts │ ├── tumblr.ts │ ├── twitch.ts │ ├── twitter.ts │ ├── vk.ts │ ├── withings.ts │ ├── workos.ts │ ├── yahoo.ts │ ├── yandex.ts │ └── zoom.ts ├── request.test.ts ├── request.ts ├── utils.test.ts └── utils.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/** -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | "@typescript-eslint/no-explicit-any": "off", 4 | "@typescript-eslint/no-empty-function": "off", 5 | "@typescript-eslint/ban-types": "off", 6 | "@typescript-eslint/no-unused-vars": [ 7 | "error", 8 | { 9 | argsIgnorePattern: "^_", 10 | varsIgnorePattern: "^_", 11 | caughtErrorsIgnorePattern: "^_" 12 | } 13 | ], 14 | "@typescript-eslint/no-empty-interface": "off", 15 | "@typescript-eslint/explicit-function-return-type": "error", 16 | "no-async-promise-executor": "off", 17 | "no-useless-catch": "off" 18 | }, 19 | parser: "@typescript-eslint/parser", 20 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 21 | plugins: ["@typescript-eslint"], 22 | env: { 23 | node: true 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: pilcrowOnPaper 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | --- 4 | 5 | DO NOT DELETE THIS SECTION. 6 | 7 | Thank you for creating a pull request! 8 | 9 | If your pull request is just making changes to the docs, please create it against the `main` branch. 10 | 11 | If your pull request is making changes to the library source code, please create it against the `next` branch. If your pull request adds a new feature to the library, please open a new issue first. 12 | 13 | If you're unsure, you can just create it against the `main` branch. 14 | 15 | - [ ] Please tick this box if you’ve read and understood this section.. 16 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yaml: -------------------------------------------------------------------------------- 1 | name: "Publish GitHub pages" 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | permissions: 8 | contents: write 9 | pages: write 10 | id-token: write 11 | 12 | jobs: 13 | publish: 14 | name: Publish docs 15 | runs-on: ubuntu-latest 16 | environment: 17 | name: github-pages 18 | url: ${{ steps.deployment.outputs.page_url }} 19 | steps: 20 | - name: Set up actions 21 | uses: actions/checkout@v3 22 | - name: Upload pages artifact 23 | uses: actions/upload-pages-artifact@v3 24 | with: 25 | path: "arctic.js.org" 26 | - name: Deploy to GitHub Pages 27 | id: deployment 28 | uses: actions/deploy-pages@v4 29 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yaml: -------------------------------------------------------------------------------- 1 | name: "Publish v3 docs" 2 | on: [push] 3 | 4 | env: 5 | CLOUDFLARE_API_TOKEN: ${{secrets.CLOUDFLARE_API_TOKEN_V2}} 6 | 7 | jobs: 8 | publish-docs: 9 | name: Publish docs 10 | runs-on: ubuntu-latest 11 | if: github.repository == 'pilcrowonpaper/arctic' && github.ref == 'refs/heads/main' 12 | steps: 13 | - name: setup actions 14 | uses: actions/checkout@v4 15 | - name: setup node 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20.5.1 19 | registry-url: https://registry.npmjs.org 20 | - name: install malta 21 | working-directory: docs 22 | run: | 23 | curl -o malta.tgz -L https://github.com/pilcrowonpaper/malta/releases/latest/download/linux-amd64.tgz 24 | tar -xvzf malta.tgz 25 | - name: build 26 | working-directory: docs 27 | run: ./linux-amd64/malta build 28 | - name: install wrangler 29 | run: npm i -g wrangler 30 | - name: deploy 31 | run: wrangler pages deploy docs/dist --project-name arctic --branch main 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: "Publish v3" 2 | on: [push] 3 | 4 | env: 5 | AURI_GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 6 | AURI_NPM_TOKEN: ${{secrets.AURI_NPM_TOKEN}} 7 | 8 | jobs: 9 | publish: 10 | name: Publish package and release with Auri 11 | runs-on: ubuntu-latest 12 | if: github.repository == 'pilcrowonpaper/arctic' && github.ref == 'refs/heads/main' 13 | permissions: 14 | contents: write 15 | id-token: write 16 | steps: 17 | - name: Setup actions 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ github.ref }} 21 | - name: Setup Node 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | registry-url: "https://registry.npmjs.org/" 26 | - name: Install dependencies 27 | run: npm install 28 | - name: Build and publish package and release 29 | run: npx auri publish 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | dist 3 | node_modules 4 | .DS_Store 5 | package-lock.json 6 | .history/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | pnpm-lock.yaml 6 | package-lock.json 7 | yarn.lock 8 | 9 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "trailingComma": "none", 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | arctic.js.org -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor manual 2 | 3 | ## Repository 4 | 5 | We have two main branches, `main` and `next`. `main` contains the source code of the latest published version of the library and docs. `next` contains the source code of the upcoming version. 6 | 7 | We also have `v3` etc branch for upcoming major releases. 8 | 9 | ## Contributing to the docs 10 | 11 | We welcome all contributions to the docs. Arctic uses [Malta](https://malta-cli.pages.dev) for generating documentation sites. All pages are markdown files located in the `docs/pages` directory. Make sure to update `malta.config.json` if you need a page to appear in the sidebar. We are aware of the limitations with using a very basic docs generator, but we believe it's good enough for this project. 12 | 13 | PRs for changes to the docs should made against the `main` branch. 14 | 15 | ## Contributing to the source code 16 | 17 | We are open to most contributions, but please open a new issue before creating a pull request, especially for new features. It's likely your PR will be rejected if not. We have intentionally limited the scope of the project and we would like to keep the package lean. 18 | 19 | PRs for changes to the library source code should made against the `next` branch. 20 | 21 | ### Set up 22 | 23 | Install dependencies with PNPM. 24 | 25 | ``` 26 | pnpm i 27 | ``` 28 | 29 | ### Testing 30 | 31 | Run `pnpm test` to run tests and `pnpm build` to build the package. 32 | 33 | ``` 34 | pnpm test 35 | 36 | pnpm build 37 | ``` 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 pilcrowOnPaper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arctic 2 | 3 | **Documentation: [arcticjs.dev](https://arcticjs.dev)** 4 | 5 | Arctic is a collection of OAuth 2.0 clients for popular providers. Only the authorization code flow is supported. Built on top of the Fetch API, it's light weight, fully-typed, and runtime-agnostic. 6 | 7 | ``` 8 | npm install arctic 9 | ``` 10 | 11 | ```ts 12 | import * as arctic from "arctic"; 13 | 14 | const github = new arctic.GitHub(clientId, clientSecret, redirectURI); 15 | 16 | const state = arctic.generateState(); 17 | const scopes = ["user:email"]; 18 | const authorizationURL = github.createAuthorizationURL(state, scopes); 19 | 20 | // ... 21 | 22 | const tokens = await github.validateAuthorizationCode(code); 23 | const accessToken = tokens.accessToken(); 24 | ``` 25 | 26 | > Arctic only supports providers that follow the OAuth 2.0 spec (including PKCE and token revocation). 27 | 28 | ## Semver 29 | 30 | Arctic does not strictly follow semantic versioning. While we aim to only introduce breaking changes in major versions, we may introduce them in a minor update if a provider updates their API in a non-backward compatible way. However, they will never be introduced in a patch update. 31 | 32 | ## Supported providers 33 | 34 | - 42 School 35 | - Amazon Cognito 36 | - AniList 37 | - Apple 38 | - Atlassian 39 | - Auth0 40 | - Authentik 41 | - Autodesk Platform Services 42 | - Battle.net 43 | - Bitbucket 44 | - Box 45 | - Bungie 46 | - Coinbase 47 | - Discord 48 | - DonationAlerts 49 | - Dribbble 50 | - Dropbox 51 | - Etsy 52 | - Epic Games 53 | - Facebook 54 | - Figma 55 | - Gitea 56 | - GitHub 57 | - GitLab 58 | - Google 59 | - Intuit 60 | - Kakao 61 | - KeyCloak 62 | - Kick 63 | - Lichess 64 | - Line 65 | - Linear 66 | - LinkedIn 67 | - Mastodon 68 | - MercadoLibre 69 | - MercadoPago 70 | - Microsoft Entra ID 71 | - MyAnimeList 72 | - Naver 73 | - Notion 74 | - Okta 75 | - osu! 76 | - Patreon 77 | - Polar 78 | - Reddit 79 | - Roblox 80 | - Salesforce 81 | - Shikimori 82 | - Slack 83 | - Spotify 84 | - Start.gg 85 | - Strava 86 | - Synology 87 | - TikTok 88 | - Tiltify 89 | - Tumblr 90 | - Twitch 91 | - Twitter 92 | - VK 93 | - WorkOS 94 | - Yahoo 95 | - Yandex 96 | - Zoom 97 | -------------------------------------------------------------------------------- /arctic.js.org/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Arctic documentation 7 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 |

The site has been moved arcticjs.dev.

29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /docs/pages/guides/migrate-v2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Migrate to v2" 3 | --- 4 | 5 | # Migrate to v2 6 | 7 | Arctic v2 is here! This update changes how tokens are handled and introduces various small improvements. Behind the scenes, it's also fully type-safe now! We used to heavily rely on type assertion but this upgrade adds proper `in` and `typeof` checks! 8 | 9 | ``` 10 | npm install arctic@2 11 | ``` 12 | 13 | ## Authorization URL 14 | 15 | `createAuthorizationURL()` is no longer asynchronous and you can pass the scopes array directly. 16 | 17 | ```ts 18 | const scopes = ["user:email", "repo"]; 19 | const url = github.createAuthorizationURL(state, scopes); 20 | ``` 21 | 22 | ## Authorization code validation 23 | 24 | `validateAuthorizationCode()` returns an [`OAuth2Token`](/reference/main/OAuth2Token) instead of a simple object. To get the access token, call the `accessToken()` method. These methods will throw an error if the field doesn't exist. 25 | 26 | ```ts 27 | const tokens = await github.validateAuthorizationCode(code); 28 | const accessToken = tokens.accessToken(); 29 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 30 | const refreshToken = tokens.refreshToken(); 31 | const idToken = tokens.idToken(); 32 | ``` 33 | 34 | Use `hasRefreshToken()` to check if the `refresh_token` field exists. 35 | 36 | ```ts 37 | if (tokens.hasRefreshToken()) { 38 | const refreshToken = tokens.refreshToken(); 39 | } 40 | ``` 41 | 42 | `validateAuthorizationCode()` throws one of [`OAuth2RequestError`](/reference/main/OAuth2RequestError), [`ArcticFetchError`](/reference/main/ArcticFetchError), or `Error`. 43 | 44 | ```ts 45 | import * as arctic from "arctic"; 46 | 47 | try { 48 | const tokens = await github.validateAuthorizationCode(code); 49 | const accessToken = tokens.accessToken(); 50 | } catch (e) { 51 | if (e instanceof arctic.OAuth2RequestError) { 52 | // Invalid authorization code, credentials, or redirect URI 53 | const code = e.code; 54 | // ... 55 | } 56 | if (e instanceof arctic.ArcticFetchError) { 57 | // Failed to call `fetch()` 58 | const cause = e.cause; 59 | // ... 60 | } 61 | // Parse error 62 | } 63 | ``` 64 | 65 | ## OpenID Connect 66 | 67 | Providers no longer include the `openid` scope by default. 68 | 69 | ```ts 70 | const scopes = ["openid", "profile"]; 71 | const url = google.createAuthorizationURL(state, codeVerifier, scopes); 72 | ``` 73 | 74 | ## Initialization 75 | 76 | The initialization parameters have changed for a few providers. See each provider's guide for details. 77 | 78 | - [Apple](/providers/apple) 79 | - [GitHub](/providers/github) 80 | - [GitLab](/providers/gitlab) 81 | - [Microsoft Entra ID](/providers/microsoft-entra-id) 82 | - [MyAnimeList](/providers/myanimelist) 83 | - [Okta](/providers/okta) 84 | - [osu!](/providers/osu) 85 | - [Salesforce](/providers/salesforce) 86 | 87 | ## Token revocation 88 | 89 | Token revocation API has been added for providers that support it. 90 | 91 | ```ts 92 | await google.revokeToken(token); 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/pages/guides/migrate-v3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Migrate to v3" 3 | --- 4 | 5 | # Migrate to v3 6 | 7 | Arctic v3 is here! This is a small major release that adds support for public OAuth clients. There are only a few breaking changes and most breaking changes are small. 8 | 9 | ``` 10 | npm install arctic@latest 11 | ``` 12 | 13 | ## Public clients 14 | 15 | For providers that support public clients, you now have the option to pass `null` as the `clientSecret` value. 16 | 17 | ```ts 18 | import * as arctic from "arctic"; 19 | 20 | const keycloak = new arctic.KeyCloak(clientId, null, redirectURI); 21 | ``` 22 | 23 | Providers that support PKCE only for public clients now have an optional `codeVerifier` parameter in `createAuthorizationURL()` and `validateAuthorizationCode()` methods. For existing providers that use confidential clients, pass `null`. 24 | 25 | ```ts 26 | // Confidential clients (existing projects) 27 | const url = discord.createAuthorizationURL(state, null, scopes); 28 | const tokens = await discord.validateAuthorizationCode(code, null); 29 | 30 | // Public clients 31 | const url = discord.createAuthorizationURL(state, codeVerifier, scopes); 32 | const tokens = await discord.validateAuthorizationCode(code, codeVerifier); 33 | ``` 34 | 35 | Providers affected by this breaking change are: Auth0, Discord, Spotify, and WorkOS. 36 | 37 | ## Self-hosted providers 38 | 39 | All providers that can be self-hosted now use a unified `baseURL` parameter in their constructors. This is a breaking change only for the GitLab and Authentik provider. 40 | 41 | ```ts 42 | import * as arctic from "arctic"; 43 | 44 | // Must include the protocol, can include path segments 45 | const baseURL = "https://my-instance.com/auth"; 46 | const gitlab = new arctic.GitLab(baseURL, clientId, clientSecret, redirectURI); 47 | ``` 48 | 49 | ## Custom domain providers 50 | 51 | All providers that can be hosted under a custom domain now use a unified `domain` parameter in their constructors. This is a breaking change only for the AWS Cognito provider. 52 | 53 | ```ts 54 | import * as arctic from "arctic"; 55 | 56 | // Must not include the protocol or path segments 57 | const domain = "my-domain.com"; 58 | const cognito = new arctic.AmazonCognito(domain, clientId, clientSecret, redirectURI); 59 | ``` 60 | 61 | ## Other changes and details 62 | 63 | Please see the [changelog](https://github.com/pilcrowonpaper/arctic/releases/tag/v3.0.0) for details and other small changes. 64 | -------------------------------------------------------------------------------- /docs/pages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Arctic v3 documentation" 3 | --- 4 | 5 | # Arctic v3 documentation 6 | 7 | _See [v1.arcticjs.dev](https://v1.arcticjs.dev) for the v1 docs._ 8 | 9 | _See [v2.arcticjs.dev](https://v2.arcticjs.dev) for the v2 docs._ 10 | 11 | Arctic is a collection of OAuth 2.0 clients for popular providers. Only the authorization code flow is supported. Built on top of the Fetch API, it's light weight, fully-typed, and runtime-agnostic. 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const github = new arctic.GitHub(clientId, clientSecret, redirectURI); 17 | 18 | const state = arctic.generateState(); 19 | const scopes = ["user:email"]; 20 | const authorizationURL = github.createAuthorizationURL(state, scopes); 21 | 22 | // ... 23 | 24 | const tokens = await github.validateAuthorizationCode(code); 25 | const accessToken = tokens.accessToken(); 26 | ``` 27 | 28 | > Arctic only supports providers that follow the OAuth 2.0 spec (including PKCE and token revocation). 29 | 30 | ## Installation 31 | 32 | ``` 33 | npm install arctic 34 | ``` 35 | 36 | ### Polyfill 37 | 38 | If you're using Node.js 18, you'll need to polyfill the Web Crypto API. This is not required in Node.js 20, Bun, Deno, and Cloudflare Workers. 39 | 40 | ```ts 41 | import { webcrypto } from "node:crypto"; 42 | 43 | globalThis.crypto = webcrypto as Crypto; 44 | ``` 45 | 46 | ## Semver 47 | 48 | Arctic does not strictly follow semantic versioning. While we aim to only introduce breaking changes in major versions, we may introduce them in a minor update if a provider updates their API in a non-backward compatible way. However, they will never be introduced in a patch update. 49 | -------------------------------------------------------------------------------- /docs/pages/providers/42.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "42 School" 3 | --- 4 | 5 | # 42 School 6 | 7 | OAuth 2.0 provider for 42 School. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | `FortyTwo` takes a client ID, client secret, and redirect URI. 14 | 15 | ```ts 16 | import * as arctic from "arctic"; 17 | 18 | const fortyTwo = new arctic.FortyTwo(clientId, clientSecret, redirectURI); 19 | ``` 20 | 21 | ## Create authorization URL 22 | 23 | ```ts 24 | import * as arctic from "arctic"; 25 | 26 | const state = arctic.generateState(); 27 | const scopes = ["public", "projects"]; 28 | const url = fortyTwo.createAuthorizationURL(state, scopes); 29 | ``` 30 | 31 | ## Validate authorization code 32 | 33 | `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). 42 School will return an access token with an expiration. 34 | 35 | ```ts 36 | import * as arctic from "arctic"; 37 | 38 | try { 39 | const tokens = await fortyTwo.validateAuthorizationCode(code); 40 | const accessToken = tokens.accessToken(); 41 | const accessToken = tokens.accessTokenExpiresAt(); 42 | } catch (e) { 43 | if (e instanceof arctic.OAuth2RequestError) { 44 | // Invalid authorization code, credentials, or redirect URI 45 | const code = e.code; 46 | // ... 47 | } 48 | if (e instanceof arctic.ArcticFetchError) { 49 | // Failed to call `fetch()` 50 | const cause = e.cause; 51 | // ... 52 | } 53 | // Parse error 54 | } 55 | ``` 56 | 57 | ## Get user profile 58 | 59 | You can retrieve user information via the [`/v2/me` endpoint](https://api.intra.42.fr/apidoc/2.0/users/me.html). 60 | 61 | ```ts 62 | const response = await fetch("https://api.intra.42.fr/v2/me", { 63 | headers: { 64 | Authorization: `Bearer ${accessToken}` 65 | } 66 | }); 67 | const user = await response.json(); 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/pages/providers/anilist.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "AniList" 3 | --- 4 | 5 | # AniList 6 | 7 | OAuth 2.0 provider for AniList. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const aniList = new arctic.AniList(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const url = aniList.createAuthorizationURL(state); 26 | ``` 27 | 28 | ## Validate authorization code 29 | 30 | `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). AniList will only return an access token (no expiration). 31 | 32 | ```ts 33 | import * as arctic from "arctic"; 34 | 35 | try { 36 | const tokens = await aniList.validateAuthorizationCode(code); 37 | const accessToken = tokens.accessToken(); 38 | } catch (e) { 39 | if (e instanceof arctic.OAuth2RequestError) { 40 | // Invalid authorization code, credentials, or redirect URI 41 | const code = e.code; 42 | // ... 43 | } 44 | if (e instanceof arctic.ArcticFetchError) { 45 | // Failed to call `fetch()` 46 | const cause = e.cause; 47 | // ... 48 | } 49 | // Parse error 50 | } 51 | ``` 52 | 53 | ## Get user profile 54 | 55 | Use the `Viewer` query to get the [user object](https://docs.anilist.co/reference/object/user). 56 | 57 | ```ts 58 | const query = `query { 59 | Viewer { 60 | id 61 | name 62 | } 63 | }`; 64 | const response = await fetch("https://graphql.anilist.co", { 65 | method: "POST", 66 | headers: { 67 | Authorization: `Bearer ${tokens.accessToken}`, 68 | "Content-Type": "application/json", 69 | Accept: "application/json" 70 | }, 71 | body: JSON.stringify({ 72 | query 73 | }) 74 | }); 75 | const user = await response.json(); 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/pages/providers/atlassian.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Atlassian" 3 | --- 4 | 5 | # Atlassian 6 | 7 | OAuth 2.0 provider for Atlassian. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const atlassian = new arctic.Atlassian(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["write:jira-work", "read:jira-user"]; 26 | const url = atlassian.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Atlassian returns an access token, the access token expiration, and a refresh token. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await atlassian.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 40 | const refreshToken = tokens.refreshToken(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Refresh access tokens 57 | 58 | Use `refreshAccessToken()` to get a new access token using a refresh token. This method's behavior is identical to `validateAuthorizationCode()`. 59 | 60 | ```ts 61 | import * as arctic from "arctic"; 62 | 63 | try { 64 | const tokens = await atlassian.refreshAccessToken(refreshToken); 65 | const accessToken = tokens.accessToken(); 66 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 67 | const refreshToken = tokens.refreshToken(); 68 | } catch (e) { 69 | if (e instanceof arctic.OAuth2RequestError) { 70 | // Invalid authorization code, credentials, or redirect URI 71 | } 72 | if (e instanceof arctic.ArcticFetchError) { 73 | // Failed to call `fetch()` 74 | } 75 | // Parse error 76 | } 77 | ``` 78 | 79 | ## Get user profile 80 | 81 | Add the `read:me` scope and use the [`/me` endpoint](https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/#how-do-i-retrieve-the-public-profile-of-the-authenticated-user-). 82 | 83 | ```ts 84 | const scopes = ["read:me"]; 85 | const url = atlassian.createAuthorizationURL(state, scopes); 86 | ``` 87 | 88 | ```ts 89 | const response = await fetch("https://api.atlassian.com/me", { 90 | headers: { 91 | Authorization: `Bearer ${accessToken}` 92 | } 93 | }); 94 | const user = await response.json(); 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/pages/providers/battlenet.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Battle.net" 3 | --- 4 | 5 | # Battle.net 6 | 7 | OAuth 2.0 provider for Battle.net. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const battlenet = new arctic.BattleNet(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["openid", "wow.profile"]; 26 | const url = battlenet.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Battle.net returns an access token and the access token expiration. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await battlenet.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | } catch (e) { 40 | if (e instanceof arctic.OAuth2RequestError) { 41 | // Invalid authorization code, credentials, or redirect URI 42 | const code = e.code; 43 | // ... 44 | } 45 | if (e instanceof arctic.ArcticFetchError) { 46 | // Failed to call `fetch()` 47 | const cause = e.cause; 48 | // ... 49 | } 50 | // Parse error 51 | } 52 | ``` 53 | 54 | ## Get user profile 55 | 56 | Use the [`User Info` endpoint](https://develop.battle.net/documentation/battle-net/oauth-apis). 57 | 58 | ```ts 59 | const response = await fetch("https://oauth.battle.net/userinfo", { 60 | headers: { 61 | Authorization: `Bearer ${accessToken}` 62 | } 63 | }); 64 | const user = await response.json(); 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/pages/providers/bitbucket.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Bitbucket" 3 | --- 4 | 5 | # Bitbucket 6 | 7 | OAuth 2.0 provider for Bitbucket. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const bitBucket = new arctic.Bitbucket(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const url = bitBucket.createAuthorizationURL(state); 26 | ``` 27 | 28 | ## Validate authorization code 29 | 30 | `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). BitBucket returns an access token and a refresh token. 31 | 32 | ```ts 33 | import * as arctic from "arctic"; 34 | 35 | try { 36 | const tokens = await bitBucket.validateAuthorizationCode(code); 37 | // Accessing other fields will throw an error 38 | const accessToken = tokens.accessToken(); 39 | const refreshToken = tokens.refreshToken(); 40 | } catch (e) { 41 | if (e instanceof arctic.OAuth2RequestError) { 42 | // Invalid authorization code, credentials, or redirect URI 43 | const code = e.code; 44 | // ... 45 | } 46 | if (e instanceof arctic.ArcticFetchError) { 47 | // Failed to call `fetch()` 48 | const cause = e.cause; 49 | // ... 50 | } 51 | // Parse error 52 | } 53 | ``` 54 | 55 | ## Refresh access tokens 56 | 57 | Use `refreshAccessToken()` to get a new access token using a refresh token. This method's behavior is identical to `validateAuthorizationCode()`. 58 | 59 | ```ts 60 | import * as arctic from "arctic"; 61 | 62 | try { 63 | const tokens = await bitBucket.refreshAccessToken(refreshToken); 64 | // Accessing other fields will throw an error 65 | const accessToken = tokens.accessToken(); 66 | const refreshToken = tokens.refreshToken(); 67 | } catch (e) { 68 | if (e instanceof arctic.OAuth2RequestError) { 69 | // Invalid authorization code, credentials, or redirect URI 70 | } 71 | if (e instanceof arctic.ArcticFetchError) { 72 | // Failed to call `fetch()` 73 | } 74 | // Parse error 75 | } 76 | ``` 77 | 78 | ## Get user profile 79 | 80 | Enable the `account` scope on your account page and use the [`/user` endpoint](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-users/#api-user-get). 81 | 82 | ```ts 83 | const response = await fetch("https://api.bitbucket.org/2.0/user", { 84 | headers: { 85 | Authorization: `Bearer ${accessToken}` 86 | } 87 | }); 88 | const user = await response.json(); 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/pages/providers/box.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Box" 3 | --- 4 | 5 | # Box 6 | 7 | OAuth 2.0 provider for Box. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const box = new arctic.Box(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["root_readonly", "manage_managed_users"]; 26 | const url = box.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Box will only return an access token (no expiration). 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await box.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | } catch (e) { 40 | if (e instanceof arctic.OAuth2RequestError) { 41 | // Invalid authorization code, credentials, or redirect URI 42 | const code = e.code; 43 | // ... 44 | } 45 | if (e instanceof arctic.ArcticFetchError) { 46 | // Failed to call `fetch()` 47 | const cause = e.cause; 48 | // ... 49 | } 50 | // Parse error 51 | } 52 | ``` 53 | 54 | ## Get user profile 55 | 56 | Use the [`/users/me` endpoint](https://developer.box.com/reference/get-users-me). 57 | 58 | ```ts 59 | const response = await fetch("https://api.box.com/2.0/users/me", { 60 | headers: { 61 | Authorization: `Bearer ${accessToken}` 62 | } 63 | }); 64 | const user = await response.json(); 65 | ``` 66 | 67 | ## Refresh access tokens 68 | 69 | Use `refreshAccessToken()` to get a new access token using a refresh token. The behavior is identical to `validateAuthorizationCode()`. 70 | 71 | ```ts 72 | import * as arctic from "arctic"; 73 | 74 | try { 75 | const tokens = await box.refreshAccessToken(refreshToken); 76 | const accessToken = tokens.accessToken(); 77 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 78 | const refreshToken = tokens.refreshToken(); 79 | } catch (e) { 80 | if (e instanceof arctic.OAuth2RequestError) { 81 | // Invalid authorization code, credentials, or redirect URI 82 | } 83 | if (e instanceof arctic.ArcticFetchError) { 84 | // Failed to call `fetch()` 85 | } 86 | // Parse error 87 | } 88 | ``` 89 | 90 | ## Revoke tokens 91 | 92 | Revoke tokens with `revokeToken()`. Revoking a refresh token will also invalidate access tokens issued with it. It throws the same errors as `validateAuthorizationCode()`. 93 | 94 | ```ts 95 | try { 96 | await box.revokeToken(token); 97 | } catch (e) { 98 | // Handle errors 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/pages/providers/dribbble.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dribbble" 3 | --- 4 | 5 | # Dribbble 6 | 7 | OAuth 2.0 provider for Dribbble. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const dribble = new arctic.Dribble(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["public", "upload"]; 26 | const url = dribble.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Dribble will only return an access token (no expiration). 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await dribble.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | } catch (e) { 40 | if (e instanceof arctic.OAuth2RequestError) { 41 | // Invalid authorization code, credentials, or redirect URI 42 | const code = e.code; 43 | // ... 44 | } 45 | if (e instanceof arctic.ArcticFetchError) { 46 | // Failed to call `fetch()` 47 | const cause = e.cause; 48 | // ... 49 | } 50 | // Parse error 51 | } 52 | ``` 53 | 54 | ## Get user profile 55 | 56 | Use the [`/user` endpoint](https://developer.dribbble.com/v2/user). 57 | 58 | ```ts 59 | const response = await fetch("https://api.dribbble.com/v2/user", { 60 | headers: { 61 | Authorization: `Bearer ${accessToken}` 62 | } 63 | }); 64 | const user = await response.json(); 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/pages/providers/etsy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Etsy" 3 | --- 4 | 5 | # Etsy 6 | 7 | Implements OAuth 2.0 with PKCE. 8 | 9 | For usage, see [OAuth 2.0 provider with PKCE](/guides/oauth2-pkce). 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const etsy = new arctic.Etsy(clientId, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const codeVerifier = arctic.generateCodeVerifier(); 26 | const scopes = ["listings_r", "listings_w"]; 27 | 28 | const url: URL = await etsy.createAuthorizationURL(state, codeVerifier, scopes); 29 | ``` 30 | 31 | ## Validate authorization code 32 | 33 | `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). Etsy returns an access token, the access token expiration, and a refresh token. 34 | 35 | ```ts 36 | import * as arctic from "arctic"; 37 | 38 | try { 39 | const tokens: OAuth2Tokens = await etsy.validateAuthorizationCode(code, codeVerifier); 40 | const accessToken = tokens.accessToken(); 41 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 42 | const refreshToken = tokens.refreshToken(); 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 | ## Get user profile 59 | 60 | Add the `shops_r` and `email_r` scope. First use the [`getMe` endpoint](https://developer.etsy.com/documentation/reference#operation/getMe) to get the user's ID. 61 | 62 | ```ts 63 | const tokens = await etsy.validateAuthorizationCode(code, codeVerifier); 64 | const response = await fetch("https://openapi.etsy.com/v3/application/users/me", { 65 | headers: { 66 | "X-Api-Key": clientId, 67 | Authorization: `Bearer ${tokens.accessToken}` 68 | } 69 | }); 70 | const result = await response.json(); 71 | const userId = result.user_id; 72 | ``` 73 | 74 | Then use the [`getUser` endpoint](https://developer.etsy.com/documentation/reference#operation/getUser) with the user ID to get the user's profile. 75 | 76 | ```ts 77 | const response = await fetch(`https://openapi.etsy.com/v3/application/users/${userId}`, { 78 | headers: { 79 | "X-Api-Key": clientId, 80 | Authorization: `Bearer ${tokens.accessToken}` 81 | } 82 | }); 83 | const user = await response.json(); 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/pages/providers/facebook.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Facebook" 3 | --- 4 | 5 | # Facebook 6 | 7 | OAuth 2.0 provider for Facebook. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const facebook = new arctic.Facebook(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["email", "public_profile"]; 26 | const url = facebook.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `validateAuthorizationCode()` will either return an [`OAuth2Tokens`](/reference/main/OAuth2Tokens), or throw one of [`ArcticFetchError`](/reference/main/ArcticFetchError), [`UnexpectedResponseError`](/reference/main/UnexpectedResponseError), or [`UnexpectedErrorResponseBodyError`](/reference/main/UnexpectedErrorResponseBodyError). Facebook will return an access token with an expiration. 32 | 33 | Unlike other providers, this will not throw `OAuth2RequestError`. Facebook's error response is not compliant with the RFC and you must manually parse the response body to get the specific error message. 34 | 35 | ```ts 36 | import * as arctic from "arctic"; 37 | 38 | try { 39 | const tokens = await facebook.validateAuthorizationCode(code); 40 | const accessToken = tokens.accessToken(); 41 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 42 | } catch (e) { 43 | if (e instanceof arctic.UnexpectedErrorResponseBodyError) { 44 | // Invalid authorization code, credentials, or redirect URI 45 | const responseBody = e.data; 46 | // ... 47 | } 48 | if (e instanceof arctic.ArcticFetchError) { 49 | // Failed to call `fetch()` 50 | const cause = e.cause; 51 | // ... 52 | } 53 | // Parse error 54 | } 55 | ``` 56 | 57 | ## Get user profile 58 | 59 | Use the `/me` endpoint. See [user fields](https://developers.facebook.com/docs/graph-api/reference/user#Reading). 60 | 61 | ```ts 62 | const searchParams = new URLSearchParams(); 63 | searchParams.set("access_token", accessToken); 64 | searchParams.set("fields", ["id", "name", "picture", "email"].join(",")); 65 | const response = await fetch("https://graph.facebook.com/me" + "?" + searchParams.toString()); 66 | const user = await response.json(); 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/pages/providers/figma.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Figma" 3 | --- 4 | 5 | # Figma 6 | 7 | OAuth 2.0 provider for Figma. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const figma = new arctic.Figma(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["files:read", "file_variables:read"]; 26 | const url = figma.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Figma returns an access token, the access token expiration, and a refresh token. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await figma.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 40 | const refreshToken = tokens.refreshToken(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Refresh access tokens 57 | 58 | Use `refreshAccessToken()` to get a new access token using a refresh token. Figma only returns an access token and its expiration. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 59 | 60 | ```ts 61 | import * as arctic from "arctic"; 62 | 63 | try { 64 | const tokens = await figma.refreshAccessToken(refreshToken); 65 | const accessToken = tokens.accessToken(); 66 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 67 | } catch (e) { 68 | if (e instanceof arctic.OAuth2RequestError) { 69 | // Invalid authorization code, credentials, or redirect URI 70 | } 71 | if (e instanceof arctic.ArcticFetchError) { 72 | // Failed to call `fetch()` 73 | } 74 | // Parse error 75 | } 76 | ``` 77 | 78 | ## Get user profile 79 | 80 | Use the [`/me` endpoint](https://www.figma.com/developers/api#get-me-endpoint). 81 | 82 | ```ts 83 | const response = await fetch("https://api.figma.com/v1/me", { 84 | headers: { 85 | Authorization: `Bearer ${accessToken}` 86 | } 87 | }); 88 | const user = await response.json(); 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/pages/providers/lichess.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Lichess" 3 | --- 4 | 5 | # Lichess 6 | 7 | OAuth 2.0 provider for Lichess. 8 | 9 | Also see the [OAuth 2.0 with PKCE](/guides/oauth2-pkce) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const lichess = new arctic.Lichess(clientId, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const codeVerifier = arctic.generateCodeVerifier(); 26 | const scopes = ["challenge:read", "challenge:write"]; 27 | const url = lichess.createAuthorizationURL(state, codeVerifier, scopes); 28 | ``` 29 | 30 | ## Validate authorization code 31 | 32 | `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). Lichess returns an access token and its expiration. 33 | 34 | ```ts 35 | import * as arctic from "arctic"; 36 | 37 | try { 38 | const tokens = await lichess.validateAuthorizationCode(code, codeVerifier); 39 | const accessToken = tokens.accessToken(); 40 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Get user profile 57 | 58 | Use the [/api/account](https://lichess.org/api#tag/Account/operation/accountMe) endpoint 59 | 60 | ```ts 61 | const lichessUserResponse = await fetch("https://lichess.org/api/account", { 62 | headers: { 63 | Authorization: `Bearer ${accessToken}` 64 | } 65 | }); 66 | const user = await lichessUserResponse.json(); 67 | ``` 68 | 69 | ## Get user email 70 | 71 | Add the `email:read` scope and use the [/api/account/email](https://lichess.org/api#tag/Account/operation/accountEmail) endpoint 72 | 73 | ```ts 74 | const scopes = ["email:read"]; 75 | const url = lichess.createAuthorizationURL(state, codeVerifier, scopes); 76 | ``` 77 | 78 | ```ts 79 | const response = await fetch("https://lichess.org/api/account/email", { 80 | headers: { 81 | Authorization: `Bearer ${accessToken}` 82 | } 83 | }); 84 | const email = await response.json(); 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/pages/providers/linear.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Linear" 3 | --- 4 | 5 | # Linear 6 | 7 | OAuth 2.0 provider for Linear. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const linear = new arctic.Linear(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | **The `read` scope must always be included.** 22 | 23 | ```ts 24 | import * as arctic from "arctic"; 25 | 26 | const state = arctic.generateState(); 27 | const scopes = ["read", "write"]; 28 | const url = linear.createAuthorizationURL(state, scopes); 29 | ``` 30 | 31 | ## Validate authorization code 32 | 33 | `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). Linear will return an access token with an expiration. 34 | 35 | ```ts 36 | import * as arctic from "arctic"; 37 | 38 | try { 39 | const tokens = await linear.validateAuthorizationCode(code); 40 | const accessToken = tokens.accessToken(); 41 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 42 | } catch (e) { 43 | if (e instanceof arctic.OAuth2RequestError) { 44 | // Invalid authorization code, credentials, or redirect URI 45 | const code = e.code; 46 | // ... 47 | } 48 | if (e instanceof arctic.ArcticFetchError) { 49 | // Failed to call `fetch()` 50 | const cause = e.cause; 51 | // ... 52 | } 53 | // Parse error 54 | } 55 | ``` 56 | 57 | ## Get user profile 58 | 59 | Use Linear's [GraphQL API](https://developers.linear.app/docs/graphql/working-with-the-graphql-api). 60 | 61 | ```ts 62 | const response = await fetch("https://api.linear.app/graphql", { 63 | method: "POST", 64 | body: `{ "query": "{ viewer { id name } }" }`, 65 | headers: { 66 | "Content-Type": "application/json" 67 | Authorization: `Bearer ${accessToken}` 68 | } 69 | }); 70 | const user = await response.json(); 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/pages/providers/mastodon.md: -------------------------------------------------------------------------------- 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 | ``` 75 | -------------------------------------------------------------------------------- /docs/pages/providers/mercadopago.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Mercado Pago" 3 | --- 4 | 5 | # Mercado Pago 6 | 7 | OAuth 2.0 provider for Mercado Pago. This client requires PKCE to be enabled in your application settings. 8 | 9 | Also see the [OAuth 2.0 with PKCE](/guides/oauth2-pkce) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const mercadopago = new arctic.MercadoPago(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const url = mercadopago.createAuthorizationURL(state); 26 | ``` 27 | 28 | ## Validate authorization code 29 | 30 | `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). Mercado Pago returns an access token and its expiration by default. 31 | 32 | ```ts 33 | import * as arctic from "arctic"; 34 | 35 | try { 36 | const tokens = await mercadopago.validateAuthorizationCode(code); 37 | const accessToken = tokens.accessToken(); 38 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 39 | const refreshToken = tokens.refreshToken(); 40 | } catch (e) { 41 | if (e instanceof arctic.OAuth2RequestError) { 42 | // Invalid authorization code, credentials, or redirect URI 43 | const code = e.code; 44 | // ... 45 | } 46 | if (e instanceof arctic.ArcticFetchError) { 47 | // Failed to call `fetch()` 48 | const cause = e.cause; 49 | // ... 50 | } 51 | // Parse error 52 | } 53 | ``` 54 | 55 | ## Refresh tokens 56 | 57 | Add the `offline_access` scope to get a refresh token. 58 | 59 | ```ts 60 | const tokens = await mercadopago.validateAuthorizationCode(code, codeVerifier); 61 | const refreshToken = tokens.refreshToken(); 62 | ``` 63 | 64 | Use `refreshAccessToken()` to get a new access token using a refresh token. Mercado Pago 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 | const tokens = await mercadopago.refreshAccessToken(refreshToken); 71 | const accessToken = tokens.accessToken(); 72 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 73 | const refreshToken = tokens.refreshToken(); 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 | -------------------------------------------------------------------------------- /docs/pages/providers/myanimelist.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "MyAnimeList" 3 | --- 4 | 5 | # MyAnimeList 6 | 7 | OAuth 2.0 provider for MyAnimeList. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | The redirect URI is optional. 14 | 15 | ```ts 16 | import * as arctic from "arctic"; 17 | 18 | const mal = new arctic.MyAnimeList(clientId, clientSecret, null); 19 | const mal = new arctic.MyAnimeList(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 url = mal.createAuthorizationURL(state, codeVerifier); 30 | ``` 31 | 32 | ## Validate authorization code 33 | 34 | `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). MyAnimeList returns an access token, the access token expiration, and a refresh token. 35 | 36 | ```ts 37 | import * as arctic from "arctic"; 38 | 39 | try { 40 | const tokens = await mal.validateAuthorizationCode(code, codeVerifier); 41 | const accessToken = tokens.accessToken(); 42 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 43 | const refreshToken = tokens.refreshToken(); 44 | } catch (e) { 45 | if (e instanceof arctic.OAuth2RequestError) { 46 | // Invalid authorization code, credentials, or redirect URI 47 | const code = e.code; 48 | // ... 49 | } 50 | if (e instanceof arctic.ArcticFetchError) { 51 | // Failed to call `fetch()` 52 | const cause = e.cause; 53 | // ... 54 | } 55 | // Parse error 56 | } 57 | ``` 58 | 59 | ## Refresh access tokens 60 | 61 | Use `refreshAccessToken()` to get a new access token using a refresh token. MyAnimeList returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 62 | 63 | ```ts 64 | import * as arctic from "arctic"; 65 | 66 | try { 67 | const tokens = await mal.refreshAccessToken(refreshToken); 68 | const accessToken = tokens.accessToken(); 69 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 70 | const refreshToken = tokens.refreshToken(); 71 | } catch (e) { 72 | if (e instanceof arctic.OAuth2RequestError) { 73 | // Invalid authorization code, credentials, or redirect URI 74 | } 75 | if (e instanceof arctic.ArcticFetchError) { 76 | // Failed to call `fetch()` 77 | } 78 | // Parse error 79 | } 80 | ``` 81 | 82 | ## Get user profile 83 | 84 | Use the [`/users` endpoint](https://myanimelist.net/apiconfig/references/api/v2#operation/users_user_id_get). 85 | 86 | ```ts 87 | const response = await fetch("https://api.myanimelist.net/v2/users/@me", { 88 | headers: { 89 | Authorization: `Bearer ${accessToken}` 90 | } 91 | }); 92 | const user = await response.json(); 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/pages/providers/naver.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Naver" 3 | --- 4 | 5 | # Naver 6 | 7 | OAuth 2.0 provider for Naver. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const naver = new arctic.Naver(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | const url = naver.createAuthorizationURL(); 23 | ``` 24 | 25 | ## Validate authorization code 26 | 27 | `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). Naver returns an access token and a refresh token. 28 | 29 | ```ts 30 | import * as arctic from "arctic"; 31 | 32 | try { 33 | const tokens = await naver.validateAuthorizationCode(code); 34 | 35 | const accessToken = tokens.accessToken(); 36 | const refreshToken = tokens.refreshToken(); 37 | } catch (e) { 38 | if (e instanceof arctic.OAuth2RequestError) { 39 | // Invalid authorization code, credentials, or redirect URI 40 | const code = e.code; 41 | // ... 42 | } 43 | if (e instanceof arctic.ArcticFetchError) { 44 | // Failed to call `fetch()` 45 | const cause = e.cause; 46 | // ... 47 | } 48 | // Parse error 49 | } 50 | ``` 51 | 52 | It also returns the access token expiration, but does so in a non-RFC compliant manner. This is a known issue with Naver. 53 | 54 | ```ts 55 | const tokens = await bungie.validateAuthorizationCode(code); 56 | // Should be returned as a number per RFC 6749, but returns it as a string. 57 | if ("expires_in" in tokens.data && typeof tokens.data.expires_in === "string") { 58 | const accessTokenExpiresIn = Number(tokens.data.expires_in); 59 | } 60 | ``` 61 | 62 | ## Refresh access tokens 63 | 64 | Use `refreshAccessToken()` to get a new access token using a refresh token. Naver returns the same values as during the authorization code validation, including the access token expiration which needs to be manually parsed out. 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 | const tokens = await naver.refreshAccessToken(refreshToken); 71 | const accessToken = tokens.accessToken(); 72 | const refreshToken = tokens.refreshToken(); 73 | } catch (e) { 74 | if (e instanceof arctic.OAuth2RequestError) { 75 | // Invalid authorization code, credentials, or redirect URI 76 | } 77 | if (e instanceof arctic.ArcticFetchError) { 78 | // Failed to call `fetch()` 79 | } 80 | // Parse error 81 | } 82 | ``` 83 | 84 | ## Get user profile 85 | 86 | Use the [`/v1/nid/me` endpoint](https://developers.naver.com/docs/login/devguide/devguide.md#3-4-5-접근-토큰을-이용하여-프로필-api-호출하기). 87 | 88 | ```ts 89 | const response = await fetch("https://openapi.naver.com/v1/nid/me", { 90 | headers: { 91 | Authorization: `Bearer ${accessToken}` 92 | } 93 | }); 94 | const user = await response.json(); 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/pages/providers/notion.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Notion" 3 | --- 4 | 5 | # Notion 6 | 7 | OAuth 2.0 provider for Notion. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const notion = new arctic.Notion(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const url = notion.createAuthorizationURL(state); 26 | ``` 27 | 28 | ## Validate authorization code 29 | 30 | `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). Notion will only return an access token (no expiration). 31 | 32 | ```ts 33 | import * as arctic from "arctic"; 34 | 35 | try { 36 | const tokens = await notion.validateAuthorizationCode(code); 37 | const accessToken = tokens.accessToken(); 38 | } catch (e) { 39 | if (e instanceof arctic.OAuth2RequestError) { 40 | // Invalid authorization code, credentials, or redirect URI 41 | const code = e.code; 42 | // ... 43 | } 44 | if (e instanceof arctic.ArcticFetchError) { 45 | // Failed to call `fetch()` 46 | const cause = e.cause; 47 | // ... 48 | } 49 | // Parse error 50 | } 51 | ``` 52 | 53 | ## Get user profile 54 | 55 | Use the [`/users/me` endpoint](https://developers.notion.com/reference/get-self). 56 | 57 | ```ts 58 | const response = await fetch("https://api.notion.com/v1/users/me", { 59 | headers: { 60 | Authorization: `Bearer ${accessToken}` 61 | } 62 | }); 63 | const user = await response.json(); 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/pages/providers/osu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "osu!" 3 | --- 4 | 5 | # osu! 6 | 7 | OAuth 2.0 provider for osu! 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const osu = new arctic.Osu(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["public", "friends.read"]; 26 | const url = osu.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). osu! returns an access token, the access token expiration, and a refresh token. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await osu.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 40 | const refreshToken = tokens.refreshToken(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Refresh access tokens 57 | 58 | Use `refreshAccessToken()` to get a new access token using a refresh token. osu! returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 59 | 60 | ```ts 61 | import * as arctic from "arctic"; 62 | 63 | try { 64 | const tokens = await osu.refreshAccessToken(refreshToken); 65 | const accessToken = tokens.accessToken(); 66 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 67 | const refreshToken = tokens.refreshToken(); 68 | } catch (e) { 69 | if (e instanceof arctic.OAuth2RequestError) { 70 | // Invalid authorization code, credentials, or redirect URI 71 | } 72 | if (e instanceof arctic.ArcticFetchError) { 73 | // Failed to call `fetch()` 74 | } 75 | // Parse error 76 | } 77 | ``` 78 | 79 | ## Get user profile 80 | 81 | Use the [`/me` endpoint](https://osu.ppy.sh/docs/index.html#get-own-data). 82 | 83 | ```ts 84 | const response = await fetch("https://osu.ppy.sh/api/v2/me", { 85 | headers: { 86 | Authorization: `Bearer ${accessToken}` 87 | } 88 | }); 89 | const user = await response.json(); 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/pages/providers/patreon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Patreon" 3 | --- 4 | 5 | # Patreon 6 | 7 | OAuth 2.0 provider for Patreon. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const patreon = new arctic.Patreon(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["identity", "identity[email]"]; 26 | const url = patreon.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Patreon returns an access token, the access token expiration, and a refresh token. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await patreon.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 40 | const refreshToken = tokens.refreshToken(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Refresh access tokens 57 | 58 | Use `refreshAccessToken()` to get a new access token using a refresh token. Patreon returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 59 | 60 | ```ts 61 | import * as arctic from "arctic"; 62 | 63 | try { 64 | const tokens = await patreon.refreshAccessToken(refreshToken); 65 | const accessToken = tokens.accessToken(); 66 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 67 | const refreshToken = tokens.refreshToken(); 68 | } catch (e) { 69 | if (e instanceof arctic.OAuth2RequestError) { 70 | // Invalid authorization code, credentials, or redirect URI 71 | } 72 | if (e instanceof arctic.ArcticFetchError) { 73 | // Failed to call `fetch()` 74 | } 75 | // Parse error 76 | } 77 | ``` 78 | 79 | ## Get user profile 80 | 81 | Add the `identity` scope and use the [`/api/oauth2/v2/identity` endpoint](https://docs.patreon.com/#get-api-oauth2-v2-identity). Optionally add the `identity[email]` scope to get user email. 82 | 83 | ```ts 84 | const scopes = ["identity", "identity[email]"]; 85 | const url = patreon.createAuthorizationURL(state, scopes); 86 | ``` 87 | 88 | ```ts 89 | const response = await fetch("https://www.patreon.com/api/oauth2/v2/identity", { 90 | headers: { 91 | Authorization: `Bearer ${accessToken}` 92 | } 93 | }); 94 | const user = await response.json(); 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/pages/providers/shikimori.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Shikimori" 3 | --- 4 | 5 | # Shikimori 6 | 7 | OAuth 2.0 provider for Shikimori. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const shikimori = new arctic.Shikimori(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const url = shikimori.createAuthorizationURL(state); 26 | ``` 27 | 28 | ## Validate authorization code 29 | 30 | `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). Shikimori returns an access token, the access token expiration, and a refresh token. 31 | 32 | ```ts 33 | import * as arctic from "arctic"; 34 | 35 | try { 36 | const tokens = await shikimori.validateAuthorizationCode(code); 37 | const accessToken = tokens.accessToken(); 38 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 39 | const refreshToken = tokens.refreshToken(); 40 | } catch (e) { 41 | if (e instanceof arctic.OAuth2RequestError) { 42 | // Invalid authorization code, credentials, or redirect URI 43 | const code = e.code; 44 | // ... 45 | } 46 | if (e instanceof arctic.ArcticFetchError) { 47 | // Failed to call `fetch()` 48 | const cause = e.cause; 49 | // ... 50 | } 51 | // Parse error 52 | } 53 | ``` 54 | 55 | ## Refresh access tokens 56 | 57 | Use `refreshAccessToken()` to get a new access token using a refresh token. Shikimori returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 58 | 59 | ```ts 60 | import * as arctic from "arctic"; 61 | 62 | try { 63 | const tokens = await shikimori.refreshAccessToken(refreshToken); 64 | const accessToken = tokens.accessToken(); 65 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 66 | const refreshToken = tokens.refreshToken(); 67 | } catch (e) { 68 | if (e instanceof arctic.OAuth2RequestError) { 69 | // Invalid authorization code, credentials, or redirect URI 70 | } 71 | if (e instanceof arctic.ArcticFetchError) { 72 | // Failed to call `fetch()` 73 | } 74 | // Parse error 75 | } 76 | ``` 77 | 78 | ## Get user profile 79 | 80 | ```ts 81 | const response = await fetch("https://shikimori.one/api/users/whoami", { 82 | headers: { 83 | Authorization: `Bearer ${accessToken}` 84 | } 85 | }); 86 | const user = await response.json(); 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/pages/providers/slack.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Slack" 3 | --- 4 | 5 | # Slack (OpenID) 6 | 7 | OAuth 2.0 provider for Slack (OpenID Connect). 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | The redirect URI is optional. 14 | 15 | ```ts 16 | import * as arctic from "arctic"; 17 | 18 | const slack = new arctic.Slack(clientId, clientSecret, null); 19 | const slack = new arctic.Slack(clientId, clientSecret, redirectURI); 20 | ``` 21 | 22 | ## Create authorization URL 23 | 24 | **The `openid` scope is required.** 25 | 26 | ```ts 27 | import * as arctic from "arctic"; 28 | 29 | const state = arctic.generateState(); 30 | const scopes = ["openid", "profile"]; 31 | const url = slack.createAuthorizationURL(state, scopes); 32 | ``` 33 | 34 | ## Validate authorization code 35 | 36 | `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). Slack will return an access token (no expiration) and an ID token. 37 | 38 | ```ts 39 | import * as arctic from "arctic"; 40 | 41 | try { 42 | const tokens = await slack.validateAuthorizationCode(code); 43 | const accessToken = tokens.accessToken(); 44 | const idToken = tokens.idToken(); 45 | } catch (e) { 46 | if (e instanceof arctic.OAuth2RequestError) { 47 | // Invalid authorization code, credentials, or redirect URI 48 | const code = e.code; 49 | // ... 50 | } 51 | if (e instanceof arctic.ArcticFetchError) { 52 | // Failed to call `fetch()` 53 | const cause = e.cause; 54 | // ... 55 | } 56 | // Parse error 57 | } 58 | ``` 59 | 60 | ## Get user profile 61 | 62 | Decode the ID token or the `userinfo` endpoint to get the user profile. Arctic provides [`decodeIdToken()`](/reference/main/decodeIdToken) for decoding the token's payload. 63 | 64 | ```ts 65 | import * as arctic from "arctic"; 66 | 67 | const claims = arctic.decodeIdToken(idToken); 68 | ``` 69 | 70 | ```ts 71 | const response = await fetch("https://openidconnect.googleapis.com/v1/userinfo", { 72 | headers: { 73 | Authorization: `Bearer ${accessToken}` 74 | } 75 | }); 76 | const user = await response.json(); 77 | ``` 78 | 79 | Make sure to add the `profile` scope to get the user profile and the `email` scope to get the user email. 80 | 81 | ```ts 82 | const scopes = ["openid", "profile", "email"]; 83 | const url = slack.createAuthorizationURL(state, codeVerifier, scopes); 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/pages/providers/strava.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Strava" 3 | --- 4 | 5 | # Strava 6 | 7 | OAuth 2.0 provider for Strava. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const strava = new arctic.Strava(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["activity:write", "read"]; 26 | const url = strava.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Strava returns an access token, the access token expiration, and a refresh token. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await strava.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 40 | const refreshToken = tokens.refreshToken(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Refresh access tokens 57 | 58 | Use `refreshAccessToken()` to get a new access token using a refresh token. Strava returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 59 | 60 | ```ts 61 | import * as arctic from "arctic"; 62 | 63 | try { 64 | const tokens = await strava.refreshAccessToken(refreshToken); 65 | const accessToken = tokens.accessToken(); 66 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 67 | const refreshToken = tokens.refreshToken(); 68 | } catch (e) { 69 | if (e instanceof arctic.OAuth2RequestError) { 70 | // Invalid authorization code, credentials, or redirect URI 71 | } 72 | if (e instanceof arctic.ArcticFetchError) { 73 | // Failed to call `fetch()` 74 | } 75 | // Parse error 76 | } 77 | ``` 78 | 79 | ## Get user profile 80 | 81 | Add the `read` scope and use the [`/athlete` endpoint](https://developers.strava.com/docs/reference/#api-Athletes-getLoggedInAthlete). Alternatively, use the `read_all` scope to get all private data. 82 | 83 | ```ts 84 | const scopes = ["read"]; 85 | const url = strava.createAuthorizationURL(state, scopes); 86 | ``` 87 | 88 | ```ts 89 | const response = await fetch("https://www.strava.com/api/v3/athlete", { 90 | headers: { 91 | Authorization: `Bearer ${accessToken}` 92 | } 93 | }); 94 | const user = await response.json(); 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/pages/providers/synology.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Synology" 3 | --- 4 | 5 | # Synology 6 | 7 | OAuth 2.0 provider for Synology SSO and OAuth Apps. 8 | 9 | Also see the [OAuth 2.0 with PKCE](/guides/oauth2-pkce) guide. 10 | 11 | ## Prerequisites 12 | 13 | To use this provider, you have to install [SSO Server](https://www.synology.com/en-us/dsm/packages/SSOServer) package on your Synology NAS. 14 | 15 | There you have to: 16 | 17 | 1. Configure the base URL under which the SSO Server will be reachable 18 | 2. Enable the OIDC service 19 | 3. Create a new OAuth App 20 | 21 | > Note: Both the _base URL_ and the _redirect URI_ have to use `https`. 22 | 23 | ## Initialization 24 | 25 | The `baseURL` parameter is the full URL of your Synology NAS that you have configured in the SSO Server. 26 | 27 | ```ts 28 | import * as arctic from "arctic"; 29 | 30 | const baseURL = "https://my_synology_nas.local:5001"; 31 | const baseURL = "https://sso.nas.example.com"; 32 | const synology = new arctic.Synology(baseUrl, applicationId, applicationSecret, redirectURI); 33 | ``` 34 | 35 | ## Create authorization URL 36 | 37 | ```ts 38 | import * as arctic from "arctic"; 39 | 40 | const state = arctic.generateState(); 41 | const codeVerifier = arctic.generateCodeVerifier(); 42 | const scopes = ["email", "groups", "openid"]; 43 | const url = synology.createAuthorizationURL(state, codeVerifier, scopes); 44 | ``` 45 | 46 | > Note: You can find all available scopes in `/webman/sso/.well-known/openid-configuration` 47 | 48 | ## Validate authorization code 49 | 50 | `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). 51 | 52 | Synology returns an access token and the access token expiration. 53 | 54 | ```ts 55 | import * as arctic from "arctic"; 56 | 57 | try { 58 | const tokens = await synology.validateAuthorizationCode(code, codeVerifier); 59 | const accessToken = tokens.accessToken(); 60 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 61 | } catch (e) { 62 | if (e instanceof arctic.OAuth2RequestError) { 63 | // Invalid authorization code, credentials, or redirect URI 64 | const code = e.code; 65 | // ... 66 | } 67 | if (e instanceof arctic.ArcticFetchError) { 68 | // Failed to call `fetch()` 69 | const cause = e.cause; 70 | // ... 71 | } 72 | // Parse error 73 | } 74 | ``` 75 | 76 | ## Get user info 77 | 78 | Use the `/webman/sso/SSOUserInfo.cgi` endpoint. 79 | 80 | ```ts 81 | const user_info = await fetch("https://example.com/webman/sso/SSOUserInfo.cgi", { 82 | headers: { 83 | Authorization: `Bearer ${accessToken}` 84 | } 85 | }); 86 | const user = await response.json(); 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/pages/providers/tiltify.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tiltify" 3 | --- 4 | 5 | # Tiltify 6 | 7 | OAuth 2.0 provider for Tiltify. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const tiltify = new arctic.Tiltify(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["activity:write", "read"]; 26 | const url = tiltify.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Tiltify returns an access token, the access token expiration, and a refresh token. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await tiltify.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 40 | const refreshToken = tokens.refreshToken(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Refresh access tokens 57 | 58 | Use `refreshAccessToken()` to get a new access token using a refresh token. Tiltify returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 59 | 60 | ```ts 61 | import * as arctic from "arctic"; 62 | 63 | try { 64 | const tokens = await tiltify.refreshAccessToken(refreshToken); 65 | const accessToken = tokens.accessToken(); 66 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 67 | const refreshToken = tokens.refreshToken(); 68 | } catch (e) { 69 | if (e instanceof arctic.OAuth2RequestError) { 70 | // Invalid authorization code, credentials, or redirect URI 71 | } 72 | if (e instanceof arctic.ArcticFetchError) { 73 | // Failed to call `fetch()` 74 | } 75 | // Parse error 76 | } 77 | ``` 78 | 79 | ## Get user profile 80 | 81 | Use the [`/api/public/current-use` endpoint](https://developers.tiltify.com/api-reference/public#tag/user/operation/V5ApiWeb.Public.UserController.current_user) without passing any arguments. 82 | 83 | ```ts 84 | const response = await fetch("https://v5api.tiltify.com/api/public/current-user", { 85 | headers: { 86 | Authorization: `Bearer ${accessToken}`, 87 | "Client-Id": clientId 88 | } 89 | }); 90 | const user = await response.json(); 91 | ``` 92 | -------------------------------------------------------------------------------- /docs/pages/providers/vk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "VK" 3 | --- 4 | 5 | # VK 6 | 7 | OAuth 2.0 provider for VK. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const vk = new arctic.VK(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | Optionally use the `offline` scope to get access tokens with no expiration. 22 | 23 | ```ts 24 | import * as arctic from "arctic"; 25 | 26 | const state = arctic.generateState(); 27 | const scopes = ["email", "messages", "offline"]; 28 | const url = vk.createAuthorizationURL(state, scopes); 29 | ``` 30 | 31 | ## Validate authorization code 32 | 33 | `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). VK will return an access token. 34 | 35 | ```ts 36 | import * as arctic from "arctic"; 37 | 38 | try { 39 | const tokens = await vk.validateAuthorizationCode(code); 40 | const accessToken = tokens.accessToken(); 41 | // Only if `offline` scope is not used. 42 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 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 | ## Get user profile 59 | 60 | Use the [`users.get` endpoint](https://dev.vk.com/en/method/users.get). 61 | 62 | ```ts 63 | const response = await fetch("https://api.vk.com/method/users.get", { 64 | headers: { 65 | Authorization: `Bearer ${accessToken}` 66 | } 67 | }); 68 | const user = await response.json(); 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/pages/providers/withings.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Withings" 3 | --- 4 | 5 | # Withings 6 | 7 | OAuth 2.0 provider for Withings. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const withings = new arctic.Withings(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["user.info", "user.metrics", "user.activity"]; 26 | const url = withings.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Withings will return an access token with an expiration. 32 | 33 | Withings deviates from the RFC and the value of `OAuth2RequestError.code` will not be a registered OAuth 2.0 error code. 34 | 35 | ```ts 36 | import * as arctic from "arctic"; 37 | 38 | try { 39 | const tokens = await withings.validateAuthorizationCode(code); 40 | const accessToken = tokens.accessToken(); 41 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 42 | } catch (e) { 43 | if (e instanceof arctic.OAuth2RequestError) { 44 | // Invalid authorization code, credentials, or redirect URI 45 | const responseBody = e.code; 46 | // ... 47 | } 48 | if (e instanceof arctic.ArcticFetchError) { 49 | // Failed to call `fetch()` 50 | const cause = e.cause; 51 | // ... 52 | } 53 | // Parse error 54 | } 55 | ``` 56 | 57 | ## Get measures 58 | 59 | Use the `/measure` endpoint. See [the API docs](https://developer.withings.com/api-reference/#tag/measure). 60 | 61 | ```ts 62 | const response = await fetch("https://wbsapi.withings.net/measure", { 63 | method: "POST", 64 | headers: { 65 | Authorization: `Bearer ${tokens.accessToken()}`, 66 | "Content-Type": "application/json" 67 | }, 68 | body: JSON.stringify({ 69 | action: "getmeas", 70 | meastypes: "1,5,6,8,76", 71 | category: 1, 72 | lastupdate: 1746082800 73 | }) 74 | }); 75 | const measures = await response.json(); 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/pages/providers/workos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "WorkOS" 3 | --- 4 | 5 | # WorkOS 6 | 7 | OAuth 2.0 provider for WorkOS. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) 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 workos = new arctic.WorkOS(clientId, clientSecret, redirectURI); 19 | const workos = new arctic.WorkOS(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 url = workos.createAuthorizationURL(state); 29 | ``` 30 | 31 | ## Validate authorization code 32 | 33 | For confidential clients, pass the 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). WorkOS will only return an access token (no expiration). 36 | 37 | ```ts 38 | import * as arctic from "arctic"; 39 | 40 | try { 41 | const tokens = await workos.validateAuthorizationCode(code, null); 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 | For public clients, pass the authorization code and code verifier. 59 | 60 | ```ts 61 | const tokens = await workos.validateAuthorizationCode(code, codeVerifier); 62 | ``` 63 | 64 | ## Get user profile 65 | 66 | The [profile](https://workos.com/docs/reference/sso/profile) is included in the token response. 67 | 68 | ```ts 69 | const tokens = await workos.validateAuthorizationCode(code); 70 | if ( 71 | "profile" in tokens.data && 72 | typeof tokens.data.profile === "object" && 73 | tokens.data.profile !== null 74 | ) { 75 | const profile = tokens.data.profile; 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/pages/providers/yandex.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Yandex" 3 | --- 4 | 5 | # Yandex 6 | 7 | OAuth 2.0 provider for Yandex. 8 | 9 | Also see the [OAuth 2.0](/guides/oauth2) guide. 10 | 11 | ## Initialization 12 | 13 | ```ts 14 | import * as arctic from "arctic"; 15 | 16 | const yandex = new arctic.Yandex(clientId, clientSecret, redirectURI); 17 | ``` 18 | 19 | ## Create authorization URL 20 | 21 | ```ts 22 | import * as arctic from "arctic"; 23 | 24 | const state = arctic.generateState(); 25 | const scopes = ["activity:write", "read"]; 26 | const url = yandex.createAuthorizationURL(state, scopes); 27 | ``` 28 | 29 | ## Validate authorization code 30 | 31 | `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). Yandex returns an access token, the access token expiration, and a refresh token. 32 | 33 | ```ts 34 | import * as arctic from "arctic"; 35 | 36 | try { 37 | const tokens = await yandex.validateAuthorizationCode(code); 38 | const accessToken = tokens.accessToken(); 39 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 40 | const refreshToken = tokens.refreshToken(); 41 | } catch (e) { 42 | if (e instanceof arctic.OAuth2RequestError) { 43 | // Invalid authorization code, credentials, or redirect URI 44 | const code = e.code; 45 | // ... 46 | } 47 | if (e instanceof arctic.ArcticFetchError) { 48 | // Failed to call `fetch()` 49 | const cause = e.cause; 50 | // ... 51 | } 52 | // Parse error 53 | } 54 | ``` 55 | 56 | ## Refresh access tokens 57 | 58 | Use `refreshAccessToken()` to get a new access token using a refresh token. Yandex returns the same values as during the authorization code validation. This method also returns `OAuth2Tokens` and throws the same errors as `validateAuthorizationCode()` 59 | 60 | ```ts 61 | import * as arctic from "arctic"; 62 | 63 | try { 64 | const tokens = await yandex.refreshAccessToken(refreshToken); 65 | const accessToken = tokens.accessToken(); 66 | const accessTokenExpiresAt = tokens.accessTokenExpiresAt(); 67 | const refreshToken = tokens.refreshToken(); 68 | } catch (e) { 69 | if (e instanceof arctic.OAuth2RequestError) { 70 | // Invalid authorization code, credentials, or redirect URI 71 | } 72 | if (e instanceof arctic.ArcticFetchError) { 73 | // Failed to call `fetch()` 74 | } 75 | // Parse error 76 | } 77 | ``` 78 | 79 | ## Get user profile 80 | 81 | Use the [`/myself` endpoint](https://yandex.cloud/en/docs/tracker/get-user-info?utm_referrer=https%3A%2F%2Fwww.google.com%2F). 82 | 83 | ```ts 84 | const response = await fetch("https://api.tracker.yandex.net/v2/myself", { 85 | headers: { 86 | Authorization: `OAuth ${accessToken}`, 87 | "X-Org-ID": ORGANIZATION_ID 88 | } 89 | }); 90 | const user = await response.json(); 91 | ``` 92 | -------------------------------------------------------------------------------- /docs/pages/reference/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "API reference" 3 | --- 4 | 5 | # API reference 6 | 7 | ## Modules 8 | 9 | - [`arctic`](/reference/main) 10 | -------------------------------------------------------------------------------- /docs/pages/reference/main/ArcticFetchError.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "ArcticFetchError" 3 | --- 4 | 5 | # ArcticFetchError 6 | 7 | Extends `Error`. 8 | 9 | Error indicating that the `fetch()` call failed. See `ArcticFetchError.cause` for the error thrown by `fetch()`. 10 | -------------------------------------------------------------------------------- /docs/pages/reference/main/CodeChallengeMethod.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CodeChallengeMethod" 3 | --- 4 | 5 | # CodeChallengeMethod 6 | 7 | ## Definition 8 | 9 | ```ts 10 | enum CodeChallengeMethod { 11 | S256, 12 | Plain 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Client/createAuthorizationURL.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Client.createAuthorizationURL()" 3 | --- 4 | 5 | # OAuth2Client.createAuthorizationURL() 6 | 7 | Creates an authorization URL. The `scope` query parameter will not be set if `scopes` parameter is a empty array. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function createAuthorizationURL( 13 | authorizationEndpoint: string, 14 | state: string, 15 | scopes: string[] 16 | ): URL; 17 | ``` 18 | 19 | ### Parameters 20 | 21 | - `authorizationEndpoint` 22 | - `state` 23 | - `scopes` 24 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Client/createAuthorizationURLWithPKCE.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Client.createAuthorizationURLWithPKCE()" 3 | --- 4 | 5 | # OAuth2Client.createAuthorizationURLWithPKCE() 6 | 7 | Creates an authorization URL for PKCE flow. The `scope` query parameter will not be set if `scopes` parameter is a empty array. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | //$ CodeChallengeMethod=/reference/main/CodeChallengeMethod 13 | function createAuthorizationURLWithPKCE( 14 | authorizationEndpoint: string, 15 | state: string, 16 | codeChallengeMethod: $$CodeChallengeMethod, 17 | codeVerifier: string, 18 | scopes: string[] 19 | ); 20 | ``` 21 | 22 | ### Parameters 23 | 24 | - `authorizationEndpoint` 25 | - `state` 26 | - `codeChallengeMethod` 27 | - `codeVerifier` 28 | - `scopes` 29 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Client/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Client" 3 | --- 4 | 5 | # OAuth2Client 6 | 7 | A generic client for OAuth 2.0 authorization code flow based on [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749), [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009), and [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636). 8 | 9 | Only client password authentication is supported and is done with the HTTP basic authentication scheme. 10 | 11 | ## Constructor 12 | 13 | ```ts 14 | function constructor( 15 | clientId: string, 16 | clientPassword: string | null, 17 | redirectURI: string | null 18 | ): this; 19 | ``` 20 | 21 | ### Parameters 22 | 23 | - `clientId` 24 | - `clientPassword` 25 | - `redirectURI` 26 | 27 | ## Methods 28 | 29 | - [`createAuthorizationURL()`](/reference/main/OAuth2Client/createAuthorizationURL) 30 | - [`createAuthorizationURLWithPKCE()`](/reference/main/OAuth2Client/createAuthorizationURLWithPKCE) 31 | - [`refreshAccessToken()`](/reference/main/OAuth2Client/refreshAccessToken) 32 | - [`revokeToken()`](/reference/main/OAuth2Client/revokeToken) 33 | - [`validateAuthorizationCode()`](/reference/main/OAuth2Client/validateAuthorizationCode) 34 | 35 | ## Properties 36 | 37 | ```ts 38 | interface Properties { 39 | clientId: string; 40 | } 41 | ``` 42 | 43 | - `clientId` 44 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Client/refreshAccessToken.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Client.refreshAccessToken()" 3 | --- 4 | 5 | # OAuth2Client.refreshAccessToken() 6 | 7 | Refreshes an access token with a refresh token. The `scope` request parameter will not be set if `scopes` parameter is a empty array. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | //$ OAuth2Tokens=/reference/main/OAuth2Tokens 13 | async function refreshAccessToken( 14 | tokenEndpoint: string, 15 | refreshToken: string, 16 | scopes: string[] 17 | ): Promise<$$OAuth2Tokens>; 18 | ``` 19 | 20 | ### Parameters 21 | 22 | - `tokenEndpoint` 23 | - `refreshToken` 24 | - `scopes` 25 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Client/revokeToken.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Client.revokeToken()" 3 | --- 4 | 5 | # OAuth2Client.revokeToken() 6 | 7 | Revokes a token. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | async function revokeToken(tokenRevocationEndpoint: string, token: string): Promise; 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `tokenRevocationEndpoint` 18 | - `token` 19 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Client/validateAuthorizationCode.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Client.validateAuthorizationCode()" 3 | --- 4 | 5 | # OAuth2Client.validateAuthorizationCode() 6 | 7 | Validates an authorization code for an access token. Pass `codeVerifier` for PKCE. 8 | 9 | ## Definitions 10 | 11 | ```ts 12 | //$ OAuth2Tokens=/reference/main/OAuth2Tokens 13 | async function validateAuthorizationCode( 14 | tokenEndpoint: string, 15 | code: string, 16 | codeVerifier: string | null 17 | ): Promise<$$OAuth2Tokens>; 18 | ``` 19 | 20 | ### Parameters 21 | 22 | - `tokenEndpoint` 23 | - `code` 24 | - `codeVerifier` 25 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2RequestError.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2RequestError" 3 | --- 4 | 5 | # OAuth2RequestError 6 | 7 | Extends `Error`. 8 | 9 | Error indicating that the provider returned an [OAuth 2.0 error response](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1). 10 | 11 | ## Properties 12 | 13 | ```ts 14 | interface Properties { 15 | code: string; 16 | description: string | null; 17 | uri: string | null; 18 | state: string | null; 19 | } 20 | ``` 21 | 22 | - `code`: The `error` field 23 | - `description`: The `error_description` field 24 | - `uri`: The `error_uri` field 25 | - `state`: The `state` field 26 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Tokens/accessToken.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Tokens.accessToken()" 3 | --- 4 | 5 | # OAuth2Tokens.accessToken() 6 | 7 | Returns the `access_token` field value. Throws an `Error` if the field is missing or the value isn't a string. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function accessToken(): string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Tokens/accessTokenExpiresAt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Tokens.accessTokenExpiresAt()" 3 | --- 4 | 5 | # OAuth2Tokens.accessTokenExpiresAt() 6 | 7 | Gets the `expires_in` field value and returns the expiration `Date`. Throws an `Error` if the field is missing or the value isn't a number. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function accessTokenExpiresAt(): Date; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Tokens/accessTokenExpiresInSeconds.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Tokens.accessTokenExpiresInSeconds()" 3 | --- 4 | 5 | # OAuth2Tokens.accessTokenExpiresInSeconds() 6 | 7 | Returns the `expires_in` field value. Throws an `Error` if the field is missing or the value isn't a number. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function accessTokenExpiresInSeconds(): number; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Tokens/hasRefreshToken.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2RequestResult.hasRefreshToken()" 3 | --- 4 | 5 | # OAuth2RequestResult.hasRefreshToken() 6 | 7 | Returns `refresh_token` if the `error` field exists and the value is a string. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function hasRefreshToken(): boolean; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Tokens/idToken.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Tokens.idToken()" 3 | --- 4 | 5 | # OAuth2Tokens.idToken() 6 | 7 | Returns the `id_token` field value. Throws an `Error` if the field is missing or the value isn't a string. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function idToken(): string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Tokens/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Tokens" 3 | --- 4 | 5 | # OAuth2Tokens 6 | 7 | Represents a JSON-parsed successful token response body. 8 | 9 | ## Constructor 10 | 11 | ```ts 12 | function constructor(data: object): this; 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `data`: JSON-parsed successful response body. 18 | 19 | ## Methods 20 | 21 | - [`accessToken()`](/reference/main/OAuth2Tokens/accessToken) 22 | - [`accessTokenExpiresAt()`](/reference/main/OAuth2Tokens/accessTokenExpiresAt) 23 | - [`accessTokenExpiresInSeconds()`](/reference/main/OAuth2Tokens/accessTokenExpiresInSeconds) 24 | - [`hasRefreshToken()`](/reference/main/OAuth2Tokens/hasRefreshToken) 25 | - [`refreshToken()`](/reference/main/OAuth2Tokens/refreshToken) 26 | 27 | ## Properties 28 | 29 | ```ts 30 | interface Properties { 31 | data: object; 32 | } 33 | ``` 34 | 35 | - `data`: `JSON.parse()`-ed response body. 36 | -------------------------------------------------------------------------------- /docs/pages/reference/main/OAuth2Tokens/refreshToken.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "OAuth2Tokens.refreshToken()" 3 | --- 4 | 5 | # OAuth2Tokens.refreshToken() 6 | 7 | Returns the `refresh_token` field value. Throws an `Error` if the field is missing or the value isn't a string. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function refreshToken(): string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/UnexpectedErrorResponseBodyError.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "UnexpectedErrorResponseBodyError" 3 | --- 4 | 5 | # UnexpectedErrorResponseBodyError 6 | 7 | Extends `Error`. 8 | 9 | Indicates an unexpected error response JSON body. 10 | 11 | ## Properties 12 | 13 | ```ts 14 | interface Properties { 15 | status: number; 16 | data: unknown; 17 | } 18 | ``` 19 | 20 | - `status`: Response body status. 21 | - `data`: `JSON.parse()`-ed response body. 22 | -------------------------------------------------------------------------------- /docs/pages/reference/main/UnexpectedResponseError.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "UnexpectedResponseError" 3 | --- 4 | 5 | # UnexpectedResponseError 6 | 7 | Extends `Error`. 8 | 9 | Indicates an unexpected response status or unexpected response body content type. 10 | 11 | ## Properties 12 | 13 | ```ts 14 | interface Properties { 15 | status: number; 16 | } 17 | ``` 18 | 19 | - `status`: Response body status. 20 | -------------------------------------------------------------------------------- /docs/pages/reference/main/decodeIdToken.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "decodeIdToken()" 3 | --- 4 | 5 | # decodeIdToken 6 | 7 | Decodes the ID token payload. This does not validate the signature. Throws an `Error` if the token is malformed. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function decodeIdToken(idToken: string): object; 13 | ``` 14 | 15 | ### Parameters 16 | 17 | - `idToken` 18 | -------------------------------------------------------------------------------- /docs/pages/reference/main/generateCodeVerifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "generateCodeVerifier()" 3 | --- 4 | 5 | # generateCodeVerifier() 6 | 7 | Generates a cryptographically secure random code verifier with the Web Crypto API. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function generateCodeVerifier(): string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/generateState.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "generateState()" 3 | --- 4 | 5 | # generateState() 6 | 7 | Generates a cryptographically secure random state with the Web Crypto API. 8 | 9 | ## Definition 10 | 11 | ```ts 12 | function generateState(): string; 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/pages/reference/main/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "arctic" 3 | --- 4 | 5 | # arctic 6 | 7 | ## Classes 8 | 9 | - [`OAuth2Client`](/reference/main/OAuth2Client) 10 | - [`ArcticFetchError`](/reference/main/ArcticFetchError) 11 | - [`OAuth2RequestError`](/reference/main/OAuth2RequestError) 12 | - [`OAuth2Tokens`](/reference/main/OAuth2Tokens) 13 | 14 | ## Functions 15 | 16 | - [`generateCodeVerifier()`](/reference/main/generateCodeVerifier) 17 | - [`generateState()`](/reference/main/generateState) 18 | - [`decodeIdToken()`](/reference/main/decodeIdToken) 19 | 20 | ## Enums 21 | 22 | - [`CodeChallengeMethod`](/reference/main/CodeChallengeMethod) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arctic", 3 | "type": "module", 4 | "version": "3.7.0", 5 | "description": "OAuth 2.0 clients for popular providers", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "module": "dist/index.js", 9 | "scripts": { 10 | "build": "rm -rf dist/* && tsc", 11 | "format": "prettier -w .", 12 | "lint": "eslint src", 13 | "test": "vitest run --sequence.concurrent" 14 | }, 15 | "files": [ 16 | "/dist/" 17 | ], 18 | "author": "pilcrowOnPaper", 19 | "license": "MIT", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/pilcrowonpaper/arctic.git" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^20.8.6", 26 | "@typescript-eslint/eslint-plugin": "^6.7.5", 27 | "@typescript-eslint/parser": "^6.7.5", 28 | "auri": "^3.1.0", 29 | "eslint": "^8.51.0", 30 | "prettier": "^3.0.3", 31 | "typescript": "^5.2.2", 32 | "vitest": "1.6.0" 33 | }, 34 | "dependencies": { 35 | "@oslojs/crypto": "1.0.1", 36 | "@oslojs/encoding": "1.1.0", 37 | "@oslojs/jwt": "0.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/oauth2.ts: -------------------------------------------------------------------------------- 1 | import * as encoding from "@oslojs/encoding"; 2 | import * as sha2 from "@oslojs/crypto/sha2"; 3 | 4 | export class OAuth2Tokens { 5 | public data: object; 6 | 7 | constructor(data: object) { 8 | this.data = data; 9 | } 10 | 11 | public tokenType(): string { 12 | if ("token_type" in this.data && typeof this.data.token_type === "string") { 13 | return this.data.token_type; 14 | } 15 | throw new Error("Missing or invalid 'token_type' field"); 16 | } 17 | 18 | public accessToken(): string { 19 | if ("access_token" in this.data && typeof this.data.access_token === "string") { 20 | return this.data.access_token; 21 | } 22 | throw new Error("Missing or invalid 'access_token' field"); 23 | } 24 | 25 | public accessTokenExpiresInSeconds(): number { 26 | if ("expires_in" in this.data && typeof this.data.expires_in === "number") { 27 | return this.data.expires_in; 28 | } 29 | throw new Error("Missing or invalid 'expires_in' field"); 30 | } 31 | 32 | public accessTokenExpiresAt(): Date { 33 | return new Date(Date.now() + this.accessTokenExpiresInSeconds() * 1000); 34 | } 35 | 36 | public hasRefreshToken(): boolean { 37 | return "refresh_token" in this.data && typeof this.data.refresh_token === "string"; 38 | } 39 | 40 | public refreshToken(): string { 41 | if ("refresh_token" in this.data && typeof this.data.refresh_token === "string") { 42 | return this.data.refresh_token; 43 | } 44 | throw new Error("Missing or invalid 'refresh_token' field"); 45 | } 46 | 47 | public hasScopes(): boolean { 48 | return "scope" in this.data && typeof this.data.scope === "string"; 49 | } 50 | 51 | public scopes(): string[] { 52 | if ("scope" in this.data && typeof this.data.scope === "string") { 53 | return this.data.scope.split(" "); 54 | } 55 | throw new Error("Missing or invalid 'scope' field"); 56 | } 57 | 58 | public idToken(): string { 59 | if ("id_token" in this.data && typeof this.data.id_token === "string") { 60 | return this.data.id_token; 61 | } 62 | throw new Error("Missing or invalid field 'id_token'"); 63 | } 64 | } 65 | 66 | export function createS256CodeChallenge(codeVerifier: string): string { 67 | const codeChallengeBytes = sha2.sha256(new TextEncoder().encode(codeVerifier)); 68 | return encoding.encodeBase64urlNoPadding(codeChallengeBytes); 69 | } 70 | 71 | export function generateCodeVerifier(): string { 72 | const randomValues = new Uint8Array(32); 73 | crypto.getRandomValues(randomValues); 74 | return encoding.encodeBase64urlNoPadding(randomValues); 75 | } 76 | 77 | export function generateState(): string { 78 | const randomValues = new Uint8Array(32); 79 | crypto.getRandomValues(randomValues); 80 | return encoding.encodeBase64urlNoPadding(randomValues); 81 | } 82 | -------------------------------------------------------------------------------- /src/oidc.test.ts: -------------------------------------------------------------------------------- 1 | import * as vitest from "vitest"; 2 | 3 | import { decodeIdToken } from "./oidc.js"; 4 | 5 | vitest.test("decodeIdToken()", () => { 6 | const jwt = 7 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; 8 | vitest.expect(decodeIdToken(jwt)).toStrictEqual({ 9 | sub: "1234567890", 10 | name: "John Doe", 11 | iat: 1516239022 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/oidc.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from "@oslojs/jwt"; 2 | 3 | export function decodeIdToken(idToken: string): object { 4 | try { 5 | return jwt.decodeJWT(idToken); 6 | } catch (e) { 7 | throw new Error("Invalid ID token", { 8 | cause: e 9 | }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/providers/42.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://api.intra.42.fr/oauth/authorize"; 6 | const tokenEndpoint = "https://api.intra.42.fr/oauth/token"; 7 | 8 | export class FortyTwo { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/providers/amazon-cognito.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | export class AmazonCognito { 6 | private authorizationEndpoint: string; 7 | private tokenEndpoint: string; 8 | private tokenRevocationEndpoint: string; 9 | 10 | private client: OAuth2Client; 11 | 12 | constructor(domain: string, clientId: string, clientSecret: string | null, redirectURI: string) { 13 | this.authorizationEndpoint = `https://${domain}/oauth2/authorize`; 14 | this.tokenEndpoint = `https://${domain}/oauth2/token`; 15 | this.tokenRevocationEndpoint = `https://${domain}/oauth2/revoke`; 16 | 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 { 35 | const tokens = await this.client.validateAuthorizationCode( 36 | this.tokenEndpoint, 37 | code, 38 | codeVerifier 39 | ); 40 | return tokens; 41 | } 42 | 43 | public async refreshAccessToken(refreshToken: string, scopes: string[]): Promise { 44 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, scopes); 45 | return tokens; 46 | } 47 | 48 | public async revokeToken(token: string): Promise { 49 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/providers/anilist.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://anilist.co/api/v2/oauth/authorize"; 6 | const tokenEndpoint = "https://anilist.co/api/v2/oauth/token"; 7 | 8 | export class AniList { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, []); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/providers/apple.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from "@oslojs/jwt"; 2 | 3 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 4 | 5 | import type { OAuth2Tokens } from "../oauth2.js"; 6 | 7 | const authorizationEndpoint = "https://appleid.apple.com/auth/authorize"; 8 | const tokenEndpoint = "https://appleid.apple.com/auth/token"; 9 | 10 | export class Apple { 11 | private clientId: string; 12 | private teamId: string; 13 | private keyId: string; 14 | private pkcs8PrivateKey: Uint8Array; 15 | private redirectURI: string; 16 | 17 | constructor( 18 | clientId: string, 19 | teamId: string, 20 | keyId: string, 21 | pkcs8PrivateKey: Uint8Array, 22 | redirectURI: string 23 | ) { 24 | this.clientId = clientId; 25 | this.teamId = teamId; 26 | this.keyId = keyId; 27 | this.pkcs8PrivateKey = pkcs8PrivateKey; 28 | this.redirectURI = redirectURI; 29 | } 30 | 31 | public createAuthorizationURL(state: string, scopes: string[]): URL { 32 | const url = new URL(authorizationEndpoint); 33 | url.searchParams.set("response_type", "code"); 34 | url.searchParams.set("client_id", this.clientId); 35 | url.searchParams.set("state", state); 36 | if (scopes.length > 0) { 37 | url.searchParams.set("scope", scopes.join(" ")); 38 | } 39 | url.searchParams.set("redirect_uri", this.redirectURI); 40 | return url; 41 | } 42 | 43 | public async validateAuthorizationCode(code: string): Promise { 44 | const body = new URLSearchParams(); 45 | body.set("grant_type", "authorization_code"); 46 | body.set("code", code); 47 | body.set("redirect_uri", this.redirectURI); 48 | body.set("client_id", this.clientId); 49 | const clientSecret = await this.createClientSecret(); 50 | body.set("client_secret", clientSecret); 51 | const request = createOAuth2Request(tokenEndpoint, body); 52 | const tokens = await sendTokenRequest(request); 53 | return tokens; 54 | } 55 | 56 | private async createClientSecret(): Promise { 57 | const privateKey = await crypto.subtle.importKey( 58 | "pkcs8", 59 | this.pkcs8PrivateKey, 60 | { 61 | name: "ECDSA", 62 | namedCurve: "P-256" 63 | }, 64 | false, 65 | ["sign"] 66 | ); 67 | const now = Math.floor(Date.now() / 1000); 68 | const headerJSON = JSON.stringify({ 69 | typ: "JWT", 70 | alg: "ES256", 71 | kid: this.keyId 72 | }); 73 | const payloadJSON = JSON.stringify({ 74 | iss: this.teamId, 75 | exp: now + 5 * 60, 76 | aud: ["https://appleid.apple.com"], 77 | sub: this.clientId, 78 | iat: now 79 | }); 80 | const signature = new Uint8Array( 81 | await crypto.subtle.sign( 82 | { 83 | name: "ECDSA", 84 | hash: "SHA-256" 85 | }, 86 | privateKey, 87 | jwt.createJWTSignatureMessage(headerJSON, payloadJSON) 88 | ) 89 | ); 90 | const token = jwt.encodeJWT(headerJSON, payloadJSON, signature); 91 | return token; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/providers/atlassian.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://auth.atlassian.com/authorize"; 6 | const tokenEndpoint = "https://auth.atlassian.com/oauth/token"; 7 | 8 | export class Atlassian { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | url.searchParams.set("audience", "api.atlassian.com"); 29 | url.searchParams.set("prompt", "consent"); 30 | return url; 31 | } 32 | 33 | public async validateAuthorizationCode(code: string): Promise { 34 | const body = new URLSearchParams(); 35 | body.set("grant_type", "authorization_code"); 36 | body.set("code", code); 37 | body.set("redirect_uri", this.redirectURI); 38 | body.set("client_id", this.clientId); 39 | body.set("client_secret", this.clientSecret); 40 | const request = createOAuth2Request(tokenEndpoint, body); 41 | const tokens = await sendTokenRequest(request); 42 | return tokens; 43 | } 44 | 45 | public async refreshAccessToken(refreshToken: string): Promise { 46 | const body = new URLSearchParams(); 47 | body.set("grant_type", "refresh_token"); 48 | body.set("refresh_token", refreshToken); 49 | body.set("client_id", this.clientId); 50 | body.set("client_secret", this.clientSecret); 51 | const request = createOAuth2Request(tokenEndpoint, body); 52 | const tokens = await sendTokenRequest(request); 53 | return tokens; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/providers/auth0.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | export class Auth0 { 6 | private authorizationEndpoint: string; 7 | private tokenEndpoint: string; 8 | private tokenRevocationEndpoint: string; 9 | 10 | private client: OAuth2Client; 11 | 12 | constructor(domain: string, clientId: string, clientSecret: string | null, redirectURI: string) { 13 | this.authorizationEndpoint = `https://${domain}/authorize`; 14 | this.tokenEndpoint = `https://${domain}/oauth/token`; 15 | this.tokenRevocationEndpoint = `https://${domain}/oauth/revoke`; 16 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 17 | } 18 | public createAuthorizationURL(state: string, codeVerifier: string | null, scopes: string[]): URL { 19 | let url: URL; 20 | if (codeVerifier !== null) { 21 | url = this.client.createAuthorizationURLWithPKCE( 22 | this.authorizationEndpoint, 23 | state, 24 | CodeChallengeMethod.S256, 25 | codeVerifier, 26 | scopes 27 | ); 28 | } else { 29 | url = this.client.createAuthorizationURL(this.authorizationEndpoint, state, scopes); 30 | } 31 | return url; 32 | } 33 | 34 | public async validateAuthorizationCode( 35 | code: string, 36 | codeVerifier: string | null 37 | ): Promise { 38 | const tokens = await this.client.validateAuthorizationCode( 39 | this.tokenEndpoint, 40 | code, 41 | codeVerifier 42 | ); 43 | return tokens; 44 | } 45 | 46 | public async refreshAccessToken(refreshToken: string): Promise { 47 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []); 48 | return tokens; 49 | } 50 | 51 | public async revokeToken(token: string): Promise { 52 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/providers/authentik.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | import { joinURIAndPath } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | export class Authentik { 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 | null, redirectURI: string) { 14 | this.authorizationEndpoint = joinURIAndPath(baseURL, "/application/o/authorize/"); 15 | this.tokenEndpoint = joinURIAndPath(baseURL, "/application/o/token/"); 16 | this.tokenRevocationEndpoint = joinURIAndPath(baseURL, "/application/o/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 { 35 | const tokens = await this.client.validateAuthorizationCode( 36 | this.tokenEndpoint, 37 | code, 38 | codeVerifier 39 | ); 40 | return tokens; 41 | } 42 | 43 | public async refreshAccessToken(refreshToken: string): Promise { 44 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []); 45 | return tokens; 46 | } 47 | 48 | public async revokeToken(token: string): Promise { 49 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/providers/autodesk.ts: -------------------------------------------------------------------------------- 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 { 31 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 32 | return tokens; 33 | } 34 | 35 | public async refreshAccessToken(refreshToken: string): Promise { 36 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 37 | return tokens; 38 | } 39 | 40 | public async revokeToken(token: string): Promise { 41 | await this.client.revokeToken(tokenRevocationEndpoint, token); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/battlenet.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://oauth.battle.net/authorize"; 6 | const tokenEndpoint = "https://oauth.battle.net/token"; 7 | 8 | export class BattleNet { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | url.searchParams.set("scope", scopes.join(" ")); 25 | url.searchParams.set("redirect_uri", this.redirectURI); 26 | return url; 27 | } 28 | 29 | public async validateAuthorizationCode(code: string): Promise { 30 | const body = new URLSearchParams(); 31 | body.set("grant_type", "authorization_code"); 32 | body.set("code", code); 33 | body.set("redirect_uri", this.redirectURI); 34 | body.set("client_id", this.clientId); 35 | body.set("client_secret", this.clientSecret); 36 | const request = createOAuth2Request(tokenEndpoint, body); 37 | const tokens = await sendTokenRequest(request); 38 | return tokens; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/providers/bitbucket.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://bitbucket.org/site/oauth2/authorize"; 6 | const tokenEndpoint = "https://bitbucket.org/site/oauth2/access_token"; 7 | 8 | export class Bitbucket { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, []); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | 25 | public async refreshAccessToken(refreshToken: string): Promise { 26 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 27 | return tokens; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/box.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://account.box.com/api/oauth2/authorize"; 6 | const tokenEndpoint = "https://api.box.com/oauth2/token"; 7 | const tokenRevocationEndpoint = "https://api.box.com/oauth2/revoke"; 8 | 9 | export class Box { 10 | private clientId: string; 11 | private clientSecret: string; 12 | private redirectURI: string; 13 | 14 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 15 | this.clientId = clientId; 16 | this.clientSecret = clientSecret; 17 | this.redirectURI = redirectURI; 18 | } 19 | 20 | public createAuthorizationURL(state: string, scopes: string[]): URL { 21 | const url = new URL(authorizationEndpoint); 22 | url.searchParams.set("response_type", "code"); 23 | url.searchParams.set("client_id", this.clientId); 24 | url.searchParams.set("state", state); 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(" ")); 27 | } 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | return url; 30 | } 31 | 32 | public async validateAuthorizationCode(code: string): Promise { 33 | const body = new URLSearchParams(); 34 | body.set("grant_type", "authorization_code"); 35 | body.set("code", code); 36 | body.set("redirect_uri", this.redirectURI); 37 | body.set("client_id", this.clientId); 38 | body.set("client_secret", this.clientSecret); 39 | const request = createOAuth2Request(tokenEndpoint, body); 40 | const tokens = await sendTokenRequest(request); 41 | return tokens; 42 | } 43 | 44 | public async refreshAccessToken(refreshToken: string): Promise { 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 | } 54 | 55 | public async revokeToken(token: string): Promise { 56 | const body = new URLSearchParams(); 57 | body.set("token", token); 58 | body.set("client_id", this.clientId); 59 | body.set("client_secret", this.clientSecret); 60 | const request = createOAuth2Request(tokenRevocationEndpoint, body); 61 | await sendTokenRevocationRequest(request); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/providers/bungie.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.bungie.net/en/oauth/authorize"; 6 | const tokenEndpoint = "https://www.bungie.net/platform/app/oauth/token"; 7 | 8 | export class Bungie { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string | null, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, scopes: string[]): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | 25 | public async refreshAccessToken(refreshToken: string): Promise { 26 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 27 | return tokens; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/coinbase.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.coinbase.com/oauth/authorize"; 6 | const tokenEndpoint = "https://www.coinbase.com/oauth/token"; 7 | const tokenRevocationEndpoint = "https://api.coinbase.com/oauth/revoke"; 8 | 9 | export class Coinbase { 10 | private clientId: string; 11 | private clientSecret: string; 12 | private redirectURI: string; 13 | 14 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 15 | this.clientId = clientId; 16 | this.clientSecret = clientSecret; 17 | this.redirectURI = redirectURI; 18 | } 19 | 20 | public createAuthorizationURL(state: string, scopes: string[]): URL { 21 | const url = new URL(authorizationEndpoint); 22 | url.searchParams.set("response_type", "code"); 23 | url.searchParams.set("client_id", this.clientId); 24 | url.searchParams.set("state", state); 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(" ")); 27 | } 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | return url; 30 | } 31 | 32 | public async validateAuthorizationCode(code: string): Promise { 33 | const body = new URLSearchParams(); 34 | body.set("grant_type", "authorization_code"); 35 | body.set("code", code); 36 | body.set("redirect_uri", this.redirectURI); 37 | body.set("client_id", this.clientId); 38 | body.set("client_secret", this.clientSecret); 39 | const request = createOAuth2Request(tokenEndpoint, body); 40 | const tokens = await sendTokenRequest(request); 41 | return tokens; 42 | } 43 | 44 | public async refreshAccessToken(refreshToken: string): Promise { 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 | } 54 | 55 | public async revokeToken(token: string): Promise { 56 | const body = new URLSearchParams(); 57 | body.set("token", token); 58 | body.set("client_id", this.clientId); 59 | body.set("client_secret", this.clientSecret); 60 | const request = createOAuth2Request(tokenRevocationEndpoint, body); 61 | await sendTokenRevocationRequest(request); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/providers/discord.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://discord.com/oauth2/authorize"; 6 | const tokenEndpoint = "https://discord.com/api/oauth2/token"; 7 | const tokenRevocationEndpoint = "https://discord.com/api/oauth2/token/revoke"; 8 | 9 | export class Discord { 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 | null, scopes: string[]): URL { 17 | let url: URL; 18 | if (codeVerifier !== null) { 19 | url = this.client.createAuthorizationURLWithPKCE( 20 | authorizationEndpoint, 21 | state, 22 | CodeChallengeMethod.S256, 23 | codeVerifier, 24 | scopes 25 | ); 26 | } else { 27 | url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 28 | } 29 | return url; 30 | } 31 | 32 | public async validateAuthorizationCode( 33 | code: string, 34 | codeVerifier: string | null 35 | ): Promise { 36 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 37 | return tokens; 38 | } 39 | 40 | public async refreshAccessToken(refreshToken: string): Promise { 41 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 42 | return tokens; 43 | } 44 | 45 | public async revokeToken(token: string): Promise { 46 | await this.client.revokeToken(tokenRevocationEndpoint, token); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/providers/donation-alerts.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.donationalerts.com/oauth/authorize"; 6 | const tokenEndpoint = "https://www.donationalerts.com/oauth/token"; 7 | 8 | export class DonationAlerts { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("scope", scopes.join(" ")); 24 | url.searchParams.set("redirect_uri", this.redirectURI); 25 | return url; 26 | } 27 | 28 | public async validateAuthorizationCode(code: string): Promise { 29 | const body = new URLSearchParams(); 30 | body.set("grant_type", "authorization_code"); 31 | body.set("code", code); 32 | body.set("redirect_uri", this.redirectURI); 33 | body.set("client_id", this.clientId); 34 | body.set("client_secret", this.clientSecret); 35 | const request = createOAuth2Request(tokenEndpoint, body); 36 | const tokens = await sendTokenRequest(request); 37 | return tokens; 38 | } 39 | 40 | public async refreshAccessToken(refreshToken: string, scopes: string[]): Promise { 41 | const body = new URLSearchParams(); 42 | body.set("grant_type", "refresh_token"); 43 | body.set("refresh_token", refreshToken); 44 | body.set("client_id", this.clientId); 45 | body.set("client_secret", this.clientSecret); 46 | body.set("scope", scopes.join(" ")); 47 | const request = createOAuth2Request(tokenEndpoint, body); 48 | const tokens = await sendTokenRequest(request); 49 | return tokens; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/providers/dribbble.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://dribbble.com/oauth/authorize"; 6 | const tokenEndpoint = "https://dribbble.com/oauth/token"; 7 | 8 | export class Dribbble { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/providers/dropbox.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.dropbox.com/oauth2/authorize"; 6 | const tokenEndpoint = "https://api.dropboxapi.com/oauth2/token"; 7 | const tokenRevocationEndpoint = "https://api.dropboxapi.com/2/auth/token/revoke"; 8 | 9 | export class Dropbox { 10 | private client: OAuth2Client; 11 | 12 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 13 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 14 | } 15 | 16 | public createAuthorizationURL(state: string, scopes: string[]): URL { 17 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 18 | return url; 19 | } 20 | 21 | public async validateAuthorizationCode(code: string): Promise { 22 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 23 | return tokens; 24 | } 25 | 26 | public async refreshAccessToken(refreshToken: string): Promise { 27 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 28 | return tokens; 29 | } 30 | 31 | public async revokeToken(token: string): Promise { 32 | await this.client.revokeToken(tokenRevocationEndpoint, token); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/providers/epicgames.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.epicgames.com/id/authorize"; 6 | const tokenEndpoint = "https://api.epicgames.dev/epic/oauth/v2/token"; 7 | const tokenRevocationEndpoint = "https://api.epicgames.dev/epic/oauth/v2/revoke"; 8 | 9 | export class EpicGames { 10 | private client: OAuth2Client; 11 | 12 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 13 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 14 | } 15 | 16 | public createAuthorizationURL(state: string, scopes: string[]): URL { 17 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 18 | return url; 19 | } 20 | 21 | public async validateAuthorizationCode(code: string): Promise { 22 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 23 | return tokens; 24 | } 25 | 26 | public async refreshAccessToken(refreshToken: string): Promise { 27 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 28 | return tokens; 29 | } 30 | 31 | public async revokeToken(token: string): Promise { 32 | await this.client.revokeToken(tokenRevocationEndpoint, token); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/providers/etsy.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.etsy.com/oauth/connect"; 6 | const tokenEndpoint = "https://api.etsy.com/v3/public/oauth/token"; 7 | 8 | export class Etsy { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, null, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 16 | const url = this.client.createAuthorizationURLWithPKCE( 17 | authorizationEndpoint, 18 | state, 19 | CodeChallengeMethod.S256, 20 | codeVerifier, 21 | scopes 22 | ); 23 | return url; 24 | } 25 | 26 | public async validateAuthorizationCode( 27 | code: string, 28 | codeVerifier: string 29 | ): Promise { 30 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 31 | return tokens; 32 | } 33 | 34 | public async refreshAccessToken(refreshToken: string): Promise { 35 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 36 | return tokens; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/providers/facebook.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.facebook.com/v16.0/dialog/oauth"; 6 | const tokenEndpoint = "https://graph.facebook.com/v16.0/oauth/access_token"; 7 | 8 | export class Facebook { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/providers/figma.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.figma.com/oauth"; 6 | const tokenEndpoint = "https://api.figma.com/v1/oauth/token"; 7 | const refreshEndpoint = "https://api.figma.com/v1/oauth/refresh"; 8 | 9 | export class Figma { 10 | private client: OAuth2Client; 11 | 12 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 13 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 14 | } 15 | 16 | public createAuthorizationURL(state: string, scopes: string[]): URL { 17 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 18 | return url; 19 | } 20 | 21 | public async validateAuthorizationCode(code: string): Promise { 22 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 23 | return tokens; 24 | } 25 | 26 | public async refreshAccessToken(refreshToken: string): Promise { 27 | const tokens = await this.client.refreshAccessToken(refreshEndpoint, refreshToken, []); 28 | return tokens; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/providers/gitea.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | import { joinURIAndPath } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | export class Gitea { 7 | private authorizationEndpoint: string; 8 | private tokenEndpoint: string; 9 | 10 | private client: OAuth2Client; 11 | 12 | constructor(baseURL: string, clientId: string, clientSecret: string | null, redirectURI: string) { 13 | this.authorizationEndpoint = joinURIAndPath(baseURL, "/login/oauth/authorize"); 14 | this.tokenEndpoint = joinURIAndPath(baseURL, "/login/oauth/access_token"); 15 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 16 | } 17 | 18 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 19 | const url = this.client.createAuthorizationURLWithPKCE( 20 | this.authorizationEndpoint, 21 | state, 22 | CodeChallengeMethod.S256, 23 | codeVerifier, 24 | scopes 25 | ); 26 | return url; 27 | } 28 | 29 | public async validateAuthorizationCode( 30 | code: string, 31 | codeVerifier: string 32 | ): Promise { 33 | const tokens = await this.client.validateAuthorizationCode( 34 | this.tokenEndpoint, 35 | code, 36 | codeVerifier 37 | ); 38 | return tokens; 39 | } 40 | 41 | public async refreshAccessToken(refreshToken: string): Promise { 42 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []); 43 | return tokens; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/providers/gitlab.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | import { joinURIAndPath } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | export class GitLab { 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 | null, redirectURI: string) { 14 | this.authorizationEndpoint = joinURIAndPath(baseURL, "/oauth/authorize"); 15 | this.tokenEndpoint = joinURIAndPath(baseURL, "/oauth/token"); 16 | this.tokenRevocationEndpoint = joinURIAndPath(baseURL, "/oauth/revoke"); 17 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 18 | } 19 | 20 | public createAuthorizationURL(state: string, scopes: string[]): URL { 21 | const url = this.client.createAuthorizationURL(this.authorizationEndpoint, state, scopes); 22 | return url; 23 | } 24 | 25 | public async validateAuthorizationCode(code: string): Promise { 26 | const tokens = await this.client.validateAuthorizationCode(this.tokenEndpoint, code, null); 27 | return tokens; 28 | } 29 | 30 | public async refreshAccessToken(refreshToken: string): Promise { 31 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []); 32 | return tokens; 33 | } 34 | 35 | public async revokeToken(token: string): Promise { 36 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/providers/google.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client, CodeChallengeMethod } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"; 6 | const tokenEndpoint = "https://oauth2.googleapis.com/token"; 7 | const tokenRevocationEndpoint = "https://oauth2.googleapis.com/revoke"; 8 | 9 | export class Google { 10 | private client: OAuth2Client; 11 | 12 | constructor(clientId: string, clientSecret: string, 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 { 31 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 32 | return tokens; 33 | } 34 | 35 | public async refreshAccessToken(refreshToken: string): Promise { 36 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 37 | return tokens; 38 | } 39 | 40 | public async revokeToken(token: string): Promise { 41 | await this.client.revokeToken(tokenRevocationEndpoint, token); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/intuit.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://appcenter.intuit.com/connect/oauth2"; 6 | const tokenEndpoint = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"; 7 | const tokenRevocationEndpoint = "https://developer.API.intuit.com/v2/oauth2/tokens/revoke"; 8 | 9 | export class Intuit { 10 | private client: OAuth2Client; 11 | 12 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 13 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 14 | } 15 | 16 | public createAuthorizationURL(state: string, scopes: string[]): URL { 17 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 18 | return url; 19 | } 20 | 21 | public async validateAuthorizationCode(code: string): Promise { 22 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 23 | return tokens; 24 | } 25 | 26 | public async refreshAccessToken(refreshToken: string): Promise { 27 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 28 | return tokens; 29 | } 30 | 31 | public async revokeToken(token: string): Promise { 32 | await this.client.revokeToken(tokenRevocationEndpoint, token); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/providers/kakao.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://kauth.kakao.com/oauth/authorize"; 6 | const tokenEndpoint = "https://kauth.kakao.com/oauth/token"; 7 | 8 | export class Kakao { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | 43 | public async refreshAccessToken(refreshToken: string): Promise { 44 | const body = new URLSearchParams(); 45 | body.set("grant_type", "refresh_token"); 46 | body.set("refresh_token", refreshToken); 47 | body.set("client_id", this.clientId); 48 | body.set("client_secret", this.clientSecret); 49 | const request = createOAuth2Request(tokenEndpoint, body); 50 | const tokens = await sendTokenRequest(request); 51 | return tokens; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/providers/keycloak.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | export class KeyCloak { 6 | private authorizationEndpoint: string; 7 | private tokenEndpoint: string; 8 | private tokenRevocationEndpoint: string; 9 | 10 | private client: OAuth2Client; 11 | 12 | constructor( 13 | realmURL: string, 14 | clientId: string, 15 | clientSecret: string | null, 16 | redirectURI: string 17 | ) { 18 | this.authorizationEndpoint = realmURL + "/protocol/openid-connect/auth"; 19 | this.tokenEndpoint = realmURL + "/protocol/openid-connect/token"; 20 | this.tokenRevocationEndpoint = realmURL + "/protocol/openid-connect/revoke"; 21 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 22 | } 23 | 24 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 25 | const url = this.client.createAuthorizationURLWithPKCE( 26 | this.authorizationEndpoint, 27 | state, 28 | CodeChallengeMethod.S256, 29 | codeVerifier, 30 | scopes 31 | ); 32 | return url; 33 | } 34 | 35 | public async validateAuthorizationCode( 36 | code: string, 37 | codeVerifier: string 38 | ): Promise { 39 | const tokens = await this.client.validateAuthorizationCode( 40 | this.tokenEndpoint, 41 | code, 42 | codeVerifier 43 | ); 44 | return tokens; 45 | } 46 | 47 | public async refreshAccessToken(refreshToken: string): Promise { 48 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []); 49 | return tokens; 50 | } 51 | 52 | public async revokeToken(token: string): Promise { 53 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/providers/kick.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; 2 | import { createS256CodeChallenge, type OAuth2Tokens } from "../oauth2.js"; 3 | 4 | const authorizationEndpoint = "https://id.kick.com/oauth/authorize"; 5 | const tokenEndpoint = "https://id.kick.com/oauth/token"; 6 | const tokenRevocationEndpoint = "https://id.kick.com/oauth/revoke"; 7 | 8 | export class Kick { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("client_id", this.clientId); 22 | url.searchParams.set("response_type", "code"); 23 | url.searchParams.set("redirect_uri", this.redirectURI); 24 | url.searchParams.set("state", state); 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(" ")); 27 | } 28 | const codeChallenge = createS256CodeChallenge(codeVerifier); 29 | url.searchParams.set("code_challenge", codeChallenge); 30 | url.searchParams.set("code_challenge_method", "S256"); 31 | return url; 32 | } 33 | 34 | public async validateAuthorizationCode( 35 | code: string, 36 | codeVerifier: string 37 | ): Promise { 38 | const body = new URLSearchParams(); 39 | body.set("code", code); 40 | body.set("client_id", this.clientId); 41 | body.set("client_secret", this.clientSecret); 42 | body.set("redirect_uri", this.redirectURI); 43 | body.set("grant_type", "authorization_code"); 44 | body.set("code_verifier", codeVerifier); 45 | const request = createOAuth2Request(tokenEndpoint, body); 46 | const tokens = await sendTokenRequest(request); 47 | return tokens; 48 | } 49 | 50 | public async refreshAccessToken(refreshToken: string): Promise { 51 | const body = new URLSearchParams(); 52 | body.set("refresh_token", refreshToken); 53 | body.set("client_id", this.clientId); 54 | body.set("client_secret", this.clientSecret); 55 | body.set("grant_type", "refresh_token"); 56 | const request = createOAuth2Request(tokenEndpoint, body); 57 | const tokens = await sendTokenRequest(request); 58 | return tokens; 59 | } 60 | 61 | public async revokeToken(token: string): Promise { 62 | const body = new URLSearchParams(); 63 | body.set("token", token); 64 | const request = createOAuth2Request(tokenRevocationEndpoint, body); 65 | await sendTokenRevocationRequest(request); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/providers/lichess.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client, CodeChallengeMethod } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://lichess.org/oauth"; 6 | const tokenEndpoint = "https://lichess.org/api/token"; 7 | 8 | export class Lichess { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, null, redirectURI); 13 | } 14 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 15 | const url = this.client.createAuthorizationURLWithPKCE( 16 | authorizationEndpoint, 17 | state, 18 | CodeChallengeMethod.S256, 19 | codeVerifier, 20 | scopes 21 | ); 22 | return url; 23 | } 24 | 25 | public async validateAuthorizationCode( 26 | code: string, 27 | codeVerifier: string 28 | ): Promise { 29 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 30 | return tokens; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/providers/line.ts: -------------------------------------------------------------------------------- 1 | import { createS256CodeChallenge } from "../oauth2.js"; 2 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | const authorizationEndpoint = "https://access.line.me/oauth2/v2.1/authorize"; 7 | const tokenEndpoint = "https://api.line.me/oauth2/v2.1/token"; 8 | 9 | export class Line { 10 | private clientId: string; 11 | private clientSecret: string; 12 | private redirectURI: string; 13 | 14 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 15 | this.clientId = clientId; 16 | this.clientSecret = clientSecret; 17 | this.redirectURI = redirectURI; 18 | } 19 | 20 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 21 | const url = new URL(authorizationEndpoint); 22 | url.searchParams.set("response_type", "code"); 23 | url.searchParams.set("client_id", this.clientId); 24 | url.searchParams.set("state", state); 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(" ")); 27 | } 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | const codeChallenge = createS256CodeChallenge(codeVerifier); 30 | url.searchParams.set("code_challenge_method", "S256"); 31 | url.searchParams.set("code_challenge", codeChallenge); 32 | return url; 33 | } 34 | 35 | public async validateAuthorizationCode( 36 | code: string, 37 | codeVerifier: string 38 | ): Promise { 39 | const body = new URLSearchParams(); 40 | body.set("grant_type", "authorization_code"); 41 | body.set("code", code); 42 | body.set("code_verifier", codeVerifier); 43 | body.set("redirect_uri", this.redirectURI); 44 | body.set("client_id", this.clientId); 45 | body.set("client_secret", this.clientSecret); 46 | const request = createOAuth2Request(tokenEndpoint, body); 47 | const tokens = await sendTokenRequest(request); 48 | return tokens; 49 | } 50 | 51 | public async refreshAccessToken(refreshToken: string): Promise { 52 | const body = new URLSearchParams(); 53 | body.set("grant_type", "refresh_token"); 54 | body.set("refresh_token", refreshToken); 55 | body.set("client_id", this.clientId); 56 | body.set("client_secret", this.clientSecret); 57 | const request = createOAuth2Request(tokenEndpoint, body); 58 | const tokens = await sendTokenRequest(request); 59 | return tokens; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/providers/linear.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://linear.app/oauth/authorize"; 6 | const tokenEndpoint = "https://api.linear.app/oauth/token"; 7 | 8 | export class Linear { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/providers/linkedin.ts: -------------------------------------------------------------------------------- 1 | // LinkedIn doesn't seem to support HTTP Basic Auth 2 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | const authorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization"; 7 | const tokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken"; 8 | 9 | export class LinkedIn { 10 | private clientId: string; 11 | private clientSecret: string; 12 | private redirectURI: string; 13 | 14 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 15 | this.clientId = clientId; 16 | this.clientSecret = clientSecret; 17 | this.redirectURI = redirectURI; 18 | } 19 | 20 | public createAuthorizationURL(state: string, scopes: string[]): URL { 21 | const url = new URL(authorizationEndpoint); 22 | url.searchParams.set("response_type", "code"); 23 | url.searchParams.set("client_id", this.clientId); 24 | url.searchParams.set("state", state); 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(" ")); 27 | } 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | return url; 30 | } 31 | 32 | public async validateAuthorizationCode(code: string): Promise { 33 | const body = new URLSearchParams(); 34 | body.set("grant_type", "authorization_code"); 35 | body.set("code", code); 36 | body.set("redirect_uri", this.redirectURI); 37 | body.set("client_id", this.clientId); 38 | body.set("client_secret", this.clientSecret); 39 | const request = createOAuth2Request(tokenEndpoint, body); 40 | const tokens = await sendTokenRequest(request); 41 | return tokens; 42 | } 43 | 44 | public async refreshAccessToken(refreshToken: string): Promise { 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 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/providers/mastodon.ts: -------------------------------------------------------------------------------- 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 { 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 { 44 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/providers/mercadolibre.ts: -------------------------------------------------------------------------------- 1 | import { createS256CodeChallenge } from "../oauth2.js"; 2 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | const authorizationEndpoint = "https://auth.mercadolibre.com/authorization"; 7 | const tokenEndpoint = "https://api.mercadolibre.com/oauth/token"; 8 | 9 | export class MercadoLibre { 10 | public clientId: string; 11 | 12 | private clientSecret: string; 13 | private redirectURI: string; 14 | 15 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 16 | this.clientId = clientId; 17 | this.clientSecret = clientSecret; 18 | this.redirectURI = redirectURI; 19 | } 20 | 21 | // `scopes` not required since they are defined in the application settings 22 | public createAuthorizationURL(state: string, codeVerifier: string): URL { 23 | const url = new URL(authorizationEndpoint); 24 | url.searchParams.set("response_type", "code"); 25 | url.searchParams.set("client_id", this.clientId); 26 | url.searchParams.set("redirect_uri", this.redirectURI); 27 | url.searchParams.set("state", state); 28 | const codeChallenge = createS256CodeChallenge(codeVerifier); 29 | url.searchParams.set("code_challenge_method", "S256"); 30 | url.searchParams.set("code_challenge", codeChallenge); 31 | return url; 32 | } 33 | 34 | public async validateAuthorizationCode( 35 | code: string, 36 | codeVerifier: string 37 | ): Promise { 38 | const body = new URLSearchParams(); 39 | body.set("grant_type", "authorization_code"); 40 | body.set("code", code); 41 | body.set("redirect_uri", this.redirectURI); 42 | body.set("code_verifier", codeVerifier); 43 | body.set("client_id", this.clientId); 44 | body.set("client_secret", this.clientSecret); 45 | const request = createOAuth2Request(tokenEndpoint, body); 46 | const tokens = await sendTokenRequest(request); 47 | return tokens; 48 | } 49 | 50 | public async refreshAccessToken(refreshToken: string): Promise { 51 | const body = new URLSearchParams(); 52 | body.set("grant_type", "refresh_token"); 53 | body.set("refresh_token", refreshToken); 54 | body.set("client_id", this.clientId); 55 | body.set("client_secret", this.clientSecret); 56 | const request = createOAuth2Request(tokenEndpoint, body); 57 | const tokens = await sendTokenRequest(request); 58 | return tokens; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/providers/mercadopago.ts: -------------------------------------------------------------------------------- 1 | import { createS256CodeChallenge } from "../oauth2.js"; 2 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | const authorizationEndpoint = "https://auth.mercadopago.com/authorization"; 7 | const tokenEndpoint = "https://api.mercadopago.com/oauth/token"; 8 | 9 | export class MercadoPago { 10 | public clientId: string; 11 | 12 | private clientSecret: string; 13 | private redirectURI: string; 14 | 15 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 16 | this.clientId = clientId; 17 | this.clientSecret = clientSecret; 18 | this.redirectURI = redirectURI; 19 | } 20 | 21 | // `scopes` not required since they are defined in the application settings 22 | public createAuthorizationURL(state: string, codeVerifier: string): URL { 23 | const url = new URL(authorizationEndpoint); 24 | url.searchParams.set("response_type", "code"); 25 | url.searchParams.set("client_id", this.clientId); 26 | url.searchParams.set("redirect_uri", this.redirectURI); 27 | url.searchParams.set("state", state); 28 | const codeChallenge = createS256CodeChallenge(codeVerifier); 29 | url.searchParams.set("code_challenge_method", "S256"); 30 | url.searchParams.set("code_challenge", codeChallenge); 31 | return url; 32 | } 33 | 34 | public async validateAuthorizationCode( 35 | code: string, 36 | codeVerifier: string 37 | ): Promise { 38 | const body = new URLSearchParams(); 39 | body.set("grant_type", "authorization_code"); 40 | body.set("code", code); 41 | body.set("redirect_uri", this.redirectURI); 42 | body.set("code_verifier", codeVerifier); 43 | body.set("client_id", this.clientId); 44 | body.set("client_secret", this.clientSecret); 45 | const request = createOAuth2Request(tokenEndpoint, body); 46 | const tokens = await sendTokenRequest(request); 47 | return tokens; 48 | } 49 | 50 | public async refreshAccessToken(refreshToken: string): Promise { 51 | const body = new URLSearchParams(); 52 | body.set("grant_type", "refresh_token"); 53 | body.set("refresh_token", refreshToken); 54 | body.set("client_id", this.clientId); 55 | body.set("client_secret", this.clientSecret); 56 | const request = createOAuth2Request(tokenEndpoint, body); 57 | const tokens = await sendTokenRequest(request); 58 | return tokens; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/providers/myanimelist.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client, CodeChallengeMethod } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://myanimelist.net/v1/oauth2/authorize"; 6 | const tokenEndpoint = "https://myanimelist.net/v1/oauth2/token"; 7 | 8 | export class MyAnimeList { 9 | private client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string | null) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, codeVerifier: string): URL { 16 | const url = this.client.createAuthorizationURLWithPKCE( 17 | authorizationEndpoint, 18 | state, 19 | CodeChallengeMethod.Plain, 20 | codeVerifier, 21 | [] 22 | ); 23 | return url; 24 | } 25 | 26 | public async validateAuthorizationCode( 27 | code: string, 28 | codeVerifier: string 29 | ): Promise { 30 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 31 | return tokens; 32 | } 33 | 34 | public async refreshAccessToken(refreshToken: string): Promise { 35 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 36 | return tokens; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/providers/naver.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://nid.naver.com/oauth2.0/authorize"; 6 | const tokenEndpoint = "https://nid.naver.com/oauth2.0/token"; 7 | 8 | export class Naver { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("redirect_uri", this.redirectURI); 24 | return url; 25 | } 26 | 27 | public async validateAuthorizationCode(code: string): Promise { 28 | const body = new URLSearchParams(); 29 | body.set("grant_type", "authorization_code"); 30 | body.set("client_id", this.clientId); 31 | body.set("client_secret", this.clientSecret); 32 | body.set("code", code); 33 | body.set("redirect_uri", this.redirectURI); 34 | const request = createOAuth2Request(tokenEndpoint, body); 35 | const tokens = await sendTokenRequest(request); 36 | return tokens; 37 | } 38 | 39 | public async refreshAccessToken(refreshToken: string): Promise { 40 | const body = new URLSearchParams(); 41 | body.set("grant_type", "refresh_token"); 42 | body.set("client_id", this.clientId); 43 | body.set("client_secret", this.clientSecret); 44 | body.set("refresh_token", refreshToken); 45 | const request = createOAuth2Request(tokenEndpoint, body); 46 | const tokens = await sendTokenRequest(request); 47 | return tokens; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/providers/notion.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | import type { OAuth2Tokens } from "../oauth2.js"; 3 | 4 | const authorizationEndpoint = "https://api.notion.com/v1/oauth/authorize"; 5 | const tokenEndpoint = "https://api.notion.com/v1/oauth/token"; 6 | 7 | export class Notion { 8 | private client: OAuth2Client; 9 | 10 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 11 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 12 | } 13 | 14 | public createAuthorizationURL(state: string): URL { 15 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, []); 16 | url.searchParams.set("owner", "user"); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/providers/okta.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | import { joinURIAndPath } from "../request.js"; 5 | 6 | export class Okta { 7 | private authorizationEndpoint: string; 8 | private tokenEndpoint: string; 9 | private tokenRevocationEndpoint: string; 10 | 11 | private client: OAuth2Client; 12 | 13 | constructor( 14 | domain: string, 15 | authorizationServerId: string | null, 16 | clientId: string, 17 | clientSecret: string, 18 | redirectURI: string 19 | ) { 20 | let baseURL = `https://${domain}/oauth2`; 21 | if (authorizationServerId !== null) { 22 | baseURL = joinURIAndPath(baseURL, authorizationServerId); 23 | } 24 | this.authorizationEndpoint = joinURIAndPath(baseURL, "/v1/authorize"); 25 | this.tokenEndpoint = joinURIAndPath(baseURL, "/v1/token"); 26 | this.tokenRevocationEndpoint = joinURIAndPath(baseURL, "/v1/revoke"); 27 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 28 | } 29 | 30 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 31 | const url = this.client.createAuthorizationURLWithPKCE( 32 | this.authorizationEndpoint, 33 | state, 34 | CodeChallengeMethod.S256, 35 | codeVerifier, 36 | scopes 37 | ); 38 | return url; 39 | } 40 | 41 | public async validateAuthorizationCode( 42 | code: string, 43 | codeVerifier: string 44 | ): Promise { 45 | const tokens = await this.client.validateAuthorizationCode( 46 | this.tokenEndpoint, 47 | code, 48 | codeVerifier 49 | ); 50 | return tokens; 51 | } 52 | 53 | public async refreshAccessToken(refreshToken: string, scopes: string[]): Promise { 54 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, scopes); 55 | return tokens; 56 | } 57 | 58 | public async revokeToken(token: string): Promise { 59 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/providers/osu.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://osu.ppy.sh/oauth/authorize"; 6 | const tokenEndpoint = "https://osu.ppy.sh/oauth/token"; 7 | 8 | export class Osu { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string | null; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string | null) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | if (this.redirectURI !== null) { 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | } 30 | return url; 31 | } 32 | 33 | public async validateAuthorizationCode(code: string): Promise { 34 | const body = new URLSearchParams(); 35 | body.set("grant_type", "authorization_code"); 36 | body.set("code", code); 37 | if (this.redirectURI !== null) { 38 | body.set("redirect_uri", this.redirectURI); 39 | } 40 | body.set("client_id", this.clientId); 41 | body.set("client_secret", this.clientSecret); 42 | const request = createOAuth2Request(tokenEndpoint, body); 43 | const tokens = await sendTokenRequest(request); 44 | return tokens; 45 | } 46 | 47 | public async refreshAccessToken(refreshToken: string): Promise { 48 | const body = new URLSearchParams(); 49 | body.set("grant_type", "refresh_token"); 50 | body.set("refresh_token", refreshToken); 51 | body.set("client_id", this.clientId); 52 | body.set("client_secret", this.clientSecret); 53 | const request = createOAuth2Request(tokenEndpoint, body); 54 | const tokens = await sendTokenRequest(request); 55 | return tokens; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/providers/patreon.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.patreon.com/oauth2/authorize"; 6 | const tokenEndpoint = "https://www.patreon.com/api/oauth2/token"; 7 | 8 | export class Patreon { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | 43 | public async refreshAccessToken(refreshToken: string): Promise { 44 | const body = new URLSearchParams(); 45 | body.set("grant_type", "refresh_token"); 46 | body.set("refresh_token", refreshToken); 47 | body.set("client_id", this.clientId); 48 | body.set("client_secret", this.clientSecret); 49 | const request = createOAuth2Request(tokenEndpoint, body); 50 | const tokens = await sendTokenRequest(request); 51 | return tokens; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/providers/polar.ts: -------------------------------------------------------------------------------- 1 | import { createS256CodeChallenge } from "../oauth2.js"; 2 | import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | const authorizationEndpoint = "https://polar.sh/oauth2/authorize"; 7 | const tokenEndpoint = "https://api.polar.sh/v1/oauth2/token"; 8 | const tokenRevocationEndpoint = "https://api.polar.sh/v1/oauth2/revoke"; 9 | 10 | // Polar.sh supports HTTP Basic Auth but `client_secret` is set as the default authentication method. 11 | export class Polar { 12 | private clientId: string; 13 | private clientSecret: string | null; 14 | private redirectURI: string; 15 | 16 | constructor(clientId: string, clientSecret: string | null, redirectURI: string) { 17 | this.clientId = clientId; 18 | this.clientSecret = clientSecret; 19 | this.redirectURI = redirectURI; 20 | } 21 | 22 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 23 | const url = new URL(authorizationEndpoint); 24 | url.searchParams.set("client_id", this.clientId); 25 | url.searchParams.set("response_type", "code"); 26 | url.searchParams.set("redirect_uri", this.redirectURI); 27 | url.searchParams.set("state", state); 28 | if (scopes.length > 0) { 29 | url.searchParams.set("scope", scopes.join(" ")); 30 | } 31 | const codeChallenge = createS256CodeChallenge(codeVerifier); 32 | url.searchParams.set("code_challenge", codeChallenge); 33 | url.searchParams.set("code_challenge_method", "S256"); 34 | return url; 35 | } 36 | 37 | public async validateAuthorizationCode( 38 | code: string, 39 | codeVerifier: string 40 | ): Promise { 41 | const body = new URLSearchParams(); 42 | body.set("code", code); 43 | body.set("client_id", this.clientId); 44 | if (this.clientSecret !== null) { 45 | body.set("client_secret", this.clientSecret); 46 | } 47 | body.set("redirect_uri", this.redirectURI); 48 | body.set("grant_type", "authorization_code"); 49 | body.set("code_verifier", codeVerifier); 50 | const request = createOAuth2Request(tokenEndpoint, body); 51 | const tokens = await sendTokenRequest(request); 52 | return tokens; 53 | } 54 | 55 | public async refreshAccessToken(refreshToken: string): Promise { 56 | const body = new URLSearchParams(); 57 | body.set("refresh_token", refreshToken); 58 | body.set("client_id", this.clientId); 59 | if (this.clientSecret !== null) { 60 | body.set("client_secret", this.clientSecret); 61 | } 62 | body.set("grant_type", "refresh_token"); 63 | const request = createOAuth2Request(tokenEndpoint, body); 64 | const tokens = await sendTokenRequest(request); 65 | return tokens; 66 | } 67 | 68 | public async revokeToken(token: string): Promise { 69 | const body = new URLSearchParams(); 70 | body.set("token", token); 71 | const request = createOAuth2Request(tokenRevocationEndpoint, body); 72 | await sendTokenRevocationRequest(request); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/providers/reddit.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.reddit.com/api/v1/authorize"; 6 | const tokenEndpoint = "https://www.reddit.com/api/v1/access_token"; 7 | 8 | export class Reddit { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, scopes: string[]): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | 25 | public async refreshAccessToken(refreshToken: string): Promise { 26 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 27 | return tokens; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/roblox.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client, CodeChallengeMethod } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://apis.roblox.com/oauth/v1/authorize"; 6 | const tokenEndpoint = "https://apis.roblox.com/oauth/v1/token"; 7 | const tokenRevocationEndpoint = "https://apis.roblox.com/oauth/v1/token/revoke"; 8 | 9 | export class Roblox { 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 { 31 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 32 | return tokens; 33 | } 34 | 35 | public async refreshAccessToken(refreshToken: string): Promise { 36 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 37 | return tokens; 38 | } 39 | 40 | public async revokeToken(token: string): Promise { 41 | await this.client.revokeToken(tokenRevocationEndpoint, token); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/salesforce.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client, CodeChallengeMethod } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | export class Salesforce { 6 | private authorizationEndpoint: string; 7 | private tokenEndpoint: string; 8 | private tokenRevocationEndpoint: string; 9 | 10 | private client: OAuth2Client; 11 | 12 | constructor(domain: string, clientId: string, clientSecret: string | null, redirectURI: string) { 13 | this.authorizationEndpoint = `https://${domain}/services/oauth2/authorize`; 14 | this.tokenEndpoint = `https://${domain}/services/oauth2/token`; 15 | this.tokenRevocationEndpoint = `https://${domain}/services/oauth2/revoke`; 16 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 17 | } 18 | 19 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 20 | const url = this.client.createAuthorizationURLWithPKCE( 21 | this.authorizationEndpoint, 22 | state, 23 | CodeChallengeMethod.S256, 24 | codeVerifier, 25 | scopes 26 | ); 27 | return url; 28 | } 29 | 30 | public async validateAuthorizationCode( 31 | code: string, 32 | codeVerifier: string 33 | ): Promise { 34 | const tokens = await this.client.validateAuthorizationCode( 35 | this.tokenEndpoint, 36 | code, 37 | codeVerifier 38 | ); 39 | return tokens; 40 | } 41 | 42 | public async refreshAccessToken(refreshToken: string): Promise { 43 | const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []); 44 | return tokens; 45 | } 46 | 47 | public async revokeToken(token: string): Promise { 48 | await this.client.revokeToken(this.tokenRevocationEndpoint, token); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/providers/shikimori.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://shikimori.one/oauth/authorize"; 6 | const tokenEndpoint = "https://shikimori.one/oauth/token"; 7 | 8 | export class Shikimori { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | url.searchParams.set("redirect_uri", this.redirectURI); 25 | return url; 26 | } 27 | 28 | public async validateAuthorizationCode(code: string): Promise { 29 | const body = new URLSearchParams(); 30 | body.set("grant_type", "authorization_code"); 31 | body.set("code", code); 32 | body.set("redirect_uri", this.redirectURI); 33 | body.set("client_id", this.clientId); 34 | body.set("client_secret", this.clientSecret); 35 | const request = createOAuth2Request(tokenEndpoint, body); 36 | const tokens = await sendTokenRequest(request); 37 | return tokens; 38 | } 39 | 40 | public async refreshAccessToken(refreshToken: string): Promise { 41 | const body = new URLSearchParams(); 42 | body.set("grant_type", "refresh_token"); 43 | body.set("refresh_token", refreshToken); 44 | body.set("client_id", this.clientId); 45 | body.set("client_secret", this.clientSecret); 46 | const request = createOAuth2Request(tokenEndpoint, body); 47 | const tokens = await sendTokenRequest(request); 48 | return tokens; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/providers/slack.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://slack.com/openid/connect/authorize"; 6 | const tokenEndpoint = "https://slack.com/api/openid.connect.token"; 7 | 8 | export class Slack { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string | null) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, scopes: string[]): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/providers/spotify.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://accounts.spotify.com/authorize"; 6 | const tokenEndpoint = "https://accounts.spotify.com/api/token"; 7 | 8 | export class Spotify { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string | null, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, codeVerifier: string | null, scopes: string[]): URL { 16 | let url: URL; 17 | if (codeVerifier !== null) { 18 | url = this.client.createAuthorizationURLWithPKCE( 19 | authorizationEndpoint, 20 | state, 21 | CodeChallengeMethod.S256, 22 | codeVerifier, 23 | scopes 24 | ); 25 | } else { 26 | url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 27 | } 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode( 32 | code: string, 33 | codeVerifier: string | null 34 | ): Promise { 35 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 36 | return tokens; 37 | } 38 | 39 | public async refreshAccessToken(refreshToken: string): Promise { 40 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 41 | return tokens; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/startgg.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://start.gg/oauth/authorize"; 6 | const tokenEndpoint = "https://api.start.gg/oauth/access_token"; 7 | const refreshEndpoint = "https://api.start.gg/oauth/refresh"; 8 | 9 | export class StartGG { 10 | private clientId: string; 11 | private clientSecret: string; 12 | private redirectURI: string; 13 | 14 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 15 | this.clientId = clientId; 16 | this.clientSecret = clientSecret; 17 | this.redirectURI = redirectURI; 18 | } 19 | 20 | public createAuthorizationURL(state: string, scopes: string[]): URL { 21 | const url = new URL(authorizationEndpoint); 22 | url.searchParams.set("response_type", "code"); 23 | url.searchParams.set("client_id", this.clientId); 24 | url.searchParams.set("state", state); 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(" ")); 27 | } 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | return url; 30 | } 31 | 32 | public async validateAuthorizationCode(code: string, scopes: string[]): Promise { 33 | const body = new URLSearchParams(); 34 | body.set("grant_type", "authorization_code"); 35 | body.set("code", code); 36 | body.set("redirect_uri", this.redirectURI); 37 | body.set("client_id", this.clientId); 38 | body.set("client_secret", this.clientSecret); 39 | if (scopes.length > 0) { 40 | body.set("scope", scopes.join(" ")); 41 | } 42 | const request = createOAuth2Request(tokenEndpoint, body); 43 | const tokens = await sendTokenRequest(request); 44 | return tokens; 45 | } 46 | 47 | public async refreshAccessToken(refreshToken: string, scopes: string[]): Promise { 48 | const body = new URLSearchParams(); 49 | body.set("grant_type", "refresh_token"); 50 | body.set("refresh_token", refreshToken); 51 | body.set("redirect_uri", this.redirectURI); 52 | body.set("client_id", this.clientId); 53 | body.set("client_secret", this.clientSecret); 54 | if (scopes.length > 0) { 55 | body.set("scope", scopes.join(" ")); 56 | } 57 | const request = createOAuth2Request(refreshEndpoint, body); 58 | const tokens = await sendTokenRequest(request); 59 | return tokens; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/providers/strava.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.strava.com/oauth/authorize"; 6 | const tokenEndpoint = "https://www.strava.com/api/v3/oauth/token"; 7 | 8 | export class Strava { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | // Strava deviates from the RFC and uses a comma-delimitated string instead of space. 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(",")); 27 | } 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | return url; 30 | } 31 | 32 | public async validateAuthorizationCode(code: string): Promise { 33 | const body = new URLSearchParams(); 34 | body.set("grant_type", "authorization_code"); 35 | body.set("code", code); 36 | body.set("redirect_uri", this.redirectURI); 37 | body.set("client_id", this.clientId); 38 | body.set("client_secret", this.clientSecret); 39 | const request = createOAuth2Request(tokenEndpoint, body); 40 | const tokens = await sendTokenRequest(request); 41 | return tokens; 42 | } 43 | 44 | public async refreshAccessToken(refreshToken: string): Promise { 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 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/providers/synology.ts: -------------------------------------------------------------------------------- 1 | import { CodeChallengeMethod, OAuth2Client } from "../client.js"; 2 | import { joinURIAndPath } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | export class Synology { 7 | private authorizationEndpoint: string; 8 | private tokenEndpoint: string; 9 | 10 | private client: OAuth2Client; 11 | 12 | constructor( 13 | baseURL: string, 14 | applicationId: string, 15 | applicationSecret: string, 16 | redirectURI: string 17 | ) { 18 | this.authorizationEndpoint = joinURIAndPath(baseURL, "/webman/sso/SSOOauth.cgi"); 19 | this.tokenEndpoint = joinURIAndPath(baseURL, "/webman/sso/SSOAccessToken.cgi"); 20 | 21 | this.client = new OAuth2Client(applicationId, applicationSecret, redirectURI); 22 | } 23 | 24 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 25 | const url = this.client.createAuthorizationURLWithPKCE( 26 | this.authorizationEndpoint, 27 | state, 28 | CodeChallengeMethod.S256, 29 | codeVerifier, 30 | scopes 31 | ); 32 | return url; 33 | } 34 | 35 | public async validateAuthorizationCode( 36 | code: string, 37 | codeVerifier: string 38 | ): Promise { 39 | const tokens = await this.client.validateAuthorizationCode( 40 | this.tokenEndpoint, 41 | code, 42 | codeVerifier 43 | ); 44 | return tokens; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/providers/tiktok.ts: -------------------------------------------------------------------------------- 1 | import { createS256CodeChallenge } from "../oauth2.js"; 2 | import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | const authorizationEndpoint = "https://www.tiktok.com/v2/auth/authorize"; 7 | const tokenEndpoint = "https://open.tiktokapis.com/v2/oauth/token/"; 8 | const tokenRevocationEndpoint = "https://open.tiktokapis.com/v2/oauth/revoke/"; 9 | 10 | export class TikTok { 11 | private clientKey: string; 12 | private clientSecret: string; 13 | private redirectURI: string; 14 | 15 | constructor(clientKey: string, clientSecret: string, redirectURI: string) { 16 | this.clientKey = clientKey; 17 | this.clientSecret = clientSecret; 18 | this.redirectURI = redirectURI; 19 | } 20 | 21 | public createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL { 22 | const url = new URL(authorizationEndpoint); 23 | url.searchParams.set("response_type", "code"); 24 | url.searchParams.set("client_key", this.clientKey); 25 | url.searchParams.set("state", state); 26 | const codeChallenge = createS256CodeChallenge(codeVerifier); 27 | url.searchParams.set("code_challenge_method", "S256"); 28 | url.searchParams.set("code_challenge", codeChallenge); 29 | if (scopes.length > 0) { 30 | url.searchParams.set("scope", scopes.join(",")); 31 | } 32 | url.searchParams.set("redirect_uri", this.redirectURI); 33 | return url; 34 | } 35 | 36 | public async validateAuthorizationCode( 37 | code: string, 38 | codeVerifier: string 39 | ): Promise { 40 | const body = new URLSearchParams(); 41 | body.set("grant_type", "authorization_code"); 42 | body.set("code", code); 43 | body.set("redirect_uri", this.redirectURI); 44 | body.set("code_verifier", codeVerifier); 45 | body.set("client_key", this.clientKey); 46 | body.set("client_secret", this.clientSecret); 47 | const request = createOAuth2Request(tokenEndpoint, body); 48 | const tokens = await sendTokenRequest(request); 49 | return tokens; 50 | } 51 | 52 | public async refreshAccessToken(refreshToken: string): Promise { 53 | const body = new URLSearchParams(); 54 | body.set("grant_type", "refresh_token"); 55 | body.set("refresh_token", refreshToken); 56 | body.set("client_key", this.clientKey); 57 | body.set("client_secret", this.clientSecret); 58 | const request = createOAuth2Request(tokenEndpoint, body); 59 | const tokens = await sendTokenRequest(request); 60 | return tokens; 61 | } 62 | 63 | public async revokeToken(token: string): Promise { 64 | const body = new URLSearchParams(); 65 | body.set("token", token); 66 | body.set("client_key", this.clientKey); 67 | body.set("client_secret", this.clientSecret); 68 | const request = createOAuth2Request(tokenRevocationEndpoint, body); 69 | await sendTokenRevocationRequest(request); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/providers/tiltify.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://v5api.tiltify.com/oauth/authorize"; 6 | const tokenEndpoint = "https://v5api.tiltify.com/oauth/token"; 7 | 8 | export class Tiltify { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | 43 | public async refreshAccessToken(refreshToken: string): Promise { 44 | const body = new URLSearchParams(); 45 | body.set("grant_type", "refresh_token"); 46 | body.set("refresh_token", refreshToken); 47 | body.set("client_id", this.clientId); 48 | body.set("client_secret", this.clientSecret); 49 | const request = createOAuth2Request(tokenEndpoint, body); 50 | const tokens = await sendTokenRequest(request); 51 | return tokens; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/providers/tumblr.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://www.tumblr.com/oauth2/authorize"; 6 | const tokenEndpoint = "https://api.tumblr.com/v2/oauth2/token"; 7 | 8 | export class Tumblr { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, scopes: string[]): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | 25 | public async refreshAccessToken(refreshToken: string): Promise { 26 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 27 | return tokens; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/twitch.ts: -------------------------------------------------------------------------------- 1 | // Does not support HTTP Basic Auth scheme. 2 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 3 | 4 | import type { OAuth2Tokens } from "../oauth2.js"; 5 | 6 | const authorizationEndpoint = "https://id.twitch.tv/oauth2/authorize"; 7 | const tokenEndpoint = "https://id.twitch.tv/oauth2/token"; 8 | 9 | export class Twitch { 10 | private clientId: string; 11 | private clientSecret: string; 12 | private redirectURI: string; 13 | 14 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 15 | this.clientId = clientId; 16 | this.clientSecret = clientSecret; 17 | this.redirectURI = redirectURI; 18 | } 19 | 20 | public createAuthorizationURL(state: string, scopes: string[]): URL { 21 | const url = new URL(authorizationEndpoint); 22 | url.searchParams.set("response_type", "code"); 23 | url.searchParams.set("client_id", this.clientId); 24 | url.searchParams.set("state", state); 25 | if (scopes.length > 0) { 26 | url.searchParams.set("scope", scopes.join(" ")); 27 | } 28 | url.searchParams.set("redirect_uri", this.redirectURI); 29 | return url; 30 | } 31 | 32 | public async validateAuthorizationCode(code: string): Promise { 33 | const body = new URLSearchParams(); 34 | body.set("grant_type", "authorization_code"); 35 | body.set("code", code); 36 | body.set("redirect_uri", this.redirectURI); 37 | body.set("client_id", this.clientId); 38 | body.set("client_secret", this.clientSecret); 39 | const request = createOAuth2Request(tokenEndpoint, body); 40 | const tokens = await sendTokenRequest(request); 41 | return tokens; 42 | } 43 | 44 | public async refreshAccessToken(refreshToken: string): Promise { 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 | } 54 | 55 | // No token revocation since the error responses are not spec-compliant 56 | } 57 | -------------------------------------------------------------------------------- /src/providers/twitter.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client, CodeChallengeMethod } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://twitter.com/i/oauth2/authorize"; 6 | const tokenEndpoint = "https://api.twitter.com/2/oauth2/token"; 7 | const tokenRevocationEndpoint = "https://api.twitter.com/2/oauth2/revoke"; 8 | 9 | export class Twitter { 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 { 31 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 32 | return tokens; 33 | } 34 | 35 | public async refreshAccessToken(refreshToken: string): Promise { 36 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 37 | return tokens; 38 | } 39 | 40 | public async revokeToken(token: string): Promise { 41 | await this.client.revokeToken(tokenRevocationEndpoint, token); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/vk.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://oauth.vk.com/authorize"; 6 | const tokenEndpoint = "https://oauth.vk.com/access_token"; 7 | 8 | export class VK { 9 | private clientId: string; 10 | private clientSecret: string; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, scopes: string[]): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | if (scopes.length > 0) { 25 | url.searchParams.set("scope", scopes.join(" ")); 26 | } 27 | url.searchParams.set("redirect_uri", this.redirectURI); 28 | return url; 29 | } 30 | 31 | public async validateAuthorizationCode(code: string): Promise { 32 | const body = new URLSearchParams(); 33 | body.set("grant_type", "authorization_code"); 34 | body.set("code", code); 35 | body.set("redirect_uri", this.redirectURI); 36 | body.set("client_id", this.clientId); 37 | body.set("client_secret", this.clientSecret); 38 | const request = createOAuth2Request(tokenEndpoint, body); 39 | const tokens = await sendTokenRequest(request); 40 | return tokens; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/providers/workos.ts: -------------------------------------------------------------------------------- 1 | import { createOAuth2Request, sendTokenRequest } from "../request.js"; 2 | 3 | import { createS256CodeChallenge, type OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://api.workos.com/sso/authorize"; 6 | const tokenEndpoint = "https://api.workos.com/sso/token"; 7 | 8 | export class WorkOS { 9 | private clientId: string; 10 | private clientSecret: string | null; 11 | private redirectURI: string; 12 | 13 | constructor(clientId: string, clientSecret: string | null, redirectURI: string) { 14 | this.clientId = clientId; 15 | this.clientSecret = clientSecret; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | public createAuthorizationURL(state: string, codeVerifier: string | null): URL { 20 | const url = new URL(authorizationEndpoint); 21 | url.searchParams.set("response_type", "code"); 22 | url.searchParams.set("client_id", this.clientId); 23 | url.searchParams.set("state", state); 24 | url.searchParams.set("redirect_uri", this.redirectURI); 25 | if (codeVerifier !== null) { 26 | const codeChallenge = createS256CodeChallenge(codeVerifier); 27 | url.searchParams.set("code_challenge_method", "S256"); 28 | url.searchParams.set("code_challenge", codeChallenge); 29 | } 30 | return url; 31 | } 32 | 33 | public async validateAuthorizationCode( 34 | code: string, 35 | codeVerifier: string | null 36 | ): Promise { 37 | const body = new URLSearchParams(); 38 | body.set("grant_type", "authorization_code"); 39 | body.set("code", code); 40 | body.set("redirect_uri", this.redirectURI); 41 | body.set("client_id", this.clientId); 42 | if (this.clientSecret !== null) { 43 | body.set("client_secret", this.clientSecret); 44 | } 45 | if (codeVerifier !== null) { 46 | body.set("code_verifier", codeVerifier); 47 | } 48 | const request = createOAuth2Request(tokenEndpoint, body); 49 | const tokens = await sendTokenRequest(request); 50 | return tokens; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/providers/yahoo.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://api.login.yahoo.com/oauth2/request_auth"; 6 | const tokenEndpoint = "https://api.login.yahoo.com/oauth2/get_token"; 7 | 8 | export class Yahoo { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, scopes: string[]): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | 25 | public async refreshAccessToken(refreshToken: string): Promise { 26 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 27 | return tokens; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/yandex.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://oauth.yandex.com/authorize"; 6 | const tokenEndpoint = "https://oauth.yandex.com/token"; 7 | 8 | export class Yandex { 9 | private client: OAuth2Client; 10 | 11 | constructor(clientId: string, clientSecret: string, redirectURI: string) { 12 | this.client = new OAuth2Client(clientId, clientSecret, redirectURI); 13 | } 14 | 15 | public createAuthorizationURL(state: string, scopes: string[]): URL { 16 | const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); 17 | return url; 18 | } 19 | 20 | public async validateAuthorizationCode(code: string): Promise { 21 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); 22 | return tokens; 23 | } 24 | 25 | public async refreshAccessToken(refreshToken: string): Promise { 26 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 27 | return tokens; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/zoom.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Client, CodeChallengeMethod } from "../client.js"; 2 | 3 | import type { OAuth2Tokens } from "../oauth2.js"; 4 | 5 | const authorizationEndpoint = "https://zoom.us/oauth/authorize"; 6 | const tokenEndpoint = "https://zoom.us/oauth/token"; 7 | const tokenRevocationEndpoint = "https://zoom.us/oauth/revoke"; 8 | 9 | export class Zoom { 10 | private client: OAuth2Client; 11 | 12 | constructor(clientId: string, clientSecret: string, 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 { 31 | const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); 32 | return tokens; 33 | } 34 | 35 | public async refreshAccessToken(refreshToken: string): Promise { 36 | const tokens = await this.client.refreshAccessToken(tokenEndpoint, refreshToken, []); 37 | return tokens; 38 | } 39 | 40 | public async revokeToken(token: string): Promise { 41 | await this.client.revokeToken(tokenRevocationEndpoint, token); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/request.test.ts: -------------------------------------------------------------------------------- 1 | import * as vitest from "vitest"; 2 | 3 | import { joinURIAndPath } from "./request.js"; 4 | 5 | vitest.test("joinBaseURIAndPath()", () => { 6 | vitest.expect(joinURIAndPath("https://example.com", "/hi")).toBe("https://example.com/hi"); 7 | vitest.expect(joinURIAndPath("https://example.com/", "/hi")).toBe("https://example.com/hi"); 8 | vitest.expect(joinURIAndPath("https://example.com/", "hi")).toBe("https://example.com/hi"); 9 | vitest.expect(joinURIAndPath("https://example.com", "hi")).toBe("https://example.com/hi"); 10 | vitest.expect(joinURIAndPath("https://example.com", "/hi/")).toBe("https://example.com/hi/"); 11 | 12 | vitest 13 | .expect(joinURIAndPath("https://example.com", "/hi", "/bye")) 14 | .toBe("https://example.com/hi/bye"); 15 | vitest 16 | .expect(joinURIAndPath("https://example.com", "hi", "bye")) 17 | .toBe("https://example.com/hi/bye"); 18 | vitest 19 | .expect(joinURIAndPath("https://example.com", "/hi/", "/bye/")) 20 | .toBe("https://example.com/hi/bye/"); 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils.test.ts: -------------------------------------------------------------------------------- 1 | import * as vitest from "vitest"; 2 | 3 | import { trimLeft, trimRight } from "./utils.js"; 4 | 5 | vitest.test("trimLeft()", () => { 6 | vitest.expect(trimLeft(" hello", " ")).toBe("hello"); 7 | vitest.expect(trimLeft(" hello", " ")).toBe("hello"); 8 | vitest.expect(trimLeft("!!!hello", "!")).toBe("hello"); 9 | vitest.expect(trimLeft("!!!hello!", "!")).toBe("hello!"); 10 | vitest.expect(trimLeft("!!", "!")).toBe(""); 11 | vitest.expect(trimLeft("", "!")).toBe(""); 12 | 13 | vitest.expect(() => trimLeft("hello", "!!")).toThrow(TypeError); 14 | }); 15 | 16 | vitest.test("trimRight()", () => { 17 | vitest.expect(trimRight("hello ", " ")).toBe("hello"); 18 | vitest.expect(trimRight("hello ", " ")).toBe("hello"); 19 | vitest.expect(trimRight("hello!!!", "!")).toBe("hello"); 20 | vitest.expect(trimRight("!hello!!!", "!")).toBe("!hello"); 21 | vitest.expect(trimRight("!!", "!")).toBe(""); 22 | vitest.expect(trimRight("", "!")).toBe(""); 23 | 24 | vitest.expect(() => trimLeft("hello", "!!")).toThrow(TypeError); 25 | }); 26 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function trimLeft(s: string, character: string): string { 2 | if (character.length !== 1) { 3 | throw new TypeError("Invalid character string"); 4 | } 5 | let start = 0; 6 | while (start < s.length && s[start] === character) { 7 | start++; 8 | } 9 | return s.slice(start); 10 | } 11 | 12 | export function trimRight(s: string, character: string): string { 13 | if (character.length !== 1) { 14 | throw new TypeError("Invalid character string"); 15 | } 16 | let end = s.length; 17 | while (end > 0 && s[end - 1] === character) { 18 | end--; 19 | } 20 | return s.slice(0, end); 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "target": "es2022", 9 | "verbatimModuleSyntax": true, 10 | "allowJs": true, 11 | "resolveJsonModule": true, 12 | "moduleDetection": "force", 13 | "strict": true, 14 | "moduleResolution": "NodeNext", 15 | "module": "NodeNext" 16 | }, 17 | "exclude": ["src/**/*.test.ts"] 18 | } 19 | --------------------------------------------------------------------------------