Comparing version 2.3.3 to 3.0.0
@@ -56,3 +56,3 @@ export { AmazonCognito } from "./providers/amazon-cognito.js"; | ||
export { OAuth2Tokens, generateCodeVerifier, generateState } from "./oauth2.js"; | ||
export { OAuth2RequestError, ArcticFetchError } from "./request.js"; | ||
export { OAuth2RequestError, ArcticFetchError, UnexpectedErrorResponseBodyError, UnexpectedResponseError } from "./request.js"; | ||
export { decodeIdToken } from "./oidc.js"; |
@@ -56,3 +56,3 @@ export { AmazonCognito } from "./providers/amazon-cognito.js"; | ||
export { OAuth2Tokens, generateCodeVerifier, generateState } from "./oauth2.js"; | ||
export { OAuth2RequestError, ArcticFetchError } from "./request.js"; | ||
export { OAuth2RequestError, ArcticFetchError, UnexpectedErrorResponseBodyError, UnexpectedResponseError } from "./request.js"; | ||
export { decodeIdToken } from "./oidc.js"; |
@@ -1,3 +0,3 @@ | ||
import { encodeBase64urlNoPadding } from "@oslojs/encoding"; | ||
import { sha256 } from "@oslojs/crypto/sha2"; | ||
import * as encoding from "@oslojs/encoding"; | ||
import * as sha2 from "@oslojs/crypto/sha2"; | ||
export class OAuth2Tokens { | ||
@@ -55,4 +55,4 @@ data; | ||
export function createS256CodeChallenge(codeVerifier) { | ||
const codeChallengeBytes = sha256(new TextEncoder().encode(codeVerifier)); | ||
return encodeBase64urlNoPadding(codeChallengeBytes); | ||
const codeChallengeBytes = sha2.sha256(new TextEncoder().encode(codeVerifier)); | ||
return encoding.encodeBase64urlNoPadding(codeChallengeBytes); | ||
} | ||
@@ -62,3 +62,3 @@ export function generateCodeVerifier() { | ||
crypto.getRandomValues(randomValues); | ||
return encodeBase64urlNoPadding(randomValues); | ||
return encoding.encodeBase64urlNoPadding(randomValues); | ||
} | ||
@@ -68,3 +68,3 @@ export function generateState() { | ||
crypto.getRandomValues(randomValues); | ||
return encodeBase64urlNoPadding(randomValues); | ||
return encoding.encodeBase64urlNoPadding(randomValues); | ||
} |
@@ -1,5 +0,5 @@ | ||
import { decodeJWT } from "@oslojs/jwt"; | ||
import * as jwt from "@oslojs/jwt"; | ||
export function decodeIdToken(idToken) { | ||
try { | ||
return decodeJWT(idToken); | ||
return jwt.decodeJWT(idToken); | ||
} | ||
@@ -6,0 +6,0 @@ catch (e) { |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
@@ -6,10 +6,8 @@ import type { OAuth2Tokens } from "../oauth2.js"; | ||
private tokenRevocationEndpoint; | ||
private clientId; | ||
private clientSecret; | ||
private redirectURI; | ||
constructor(userPool: string, clientId: string, clientSecret: string, redirectURI: string); | ||
private client; | ||
constructor(domain: string, clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; | ||
refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens>; | ||
refreshAccessToken(refreshToken: string, scopes: string[]): Promise<OAuth2Tokens>; | ||
revokeToken(token: string): Promise<void>; | ||
} |
@@ -1,7 +0,2 @@ | ||
/* | ||
While HTTP basic auth is supported when used without PKCE, | ||
only client secret is supported when PKCE is used. | ||
*/ | ||
import { createS256CodeChallenge } from "../oauth2.js"; | ||
import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; | ||
import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
export class AmazonCognito { | ||
@@ -11,58 +6,24 @@ authorizationEndpoint; | ||
tokenRevocationEndpoint; | ||
clientId; | ||
clientSecret; | ||
redirectURI; | ||
constructor(userPool, clientId, clientSecret, redirectURI) { | ||
this.authorizationEndpoint = userPool + "/oauth2/authorize"; | ||
this.tokenEndpoint = userPool + "/oauth2/token"; | ||
this.tokenRevocationEndpoint = userPool + "/oauth2/revoke"; | ||
this.clientId = clientId; | ||
this.clientSecret = clientSecret; | ||
this.redirectURI = redirectURI; | ||
client; | ||
constructor(domain, clientId, clientSecret, redirectURI) { | ||
this.authorizationEndpoint = `https://${domain}/oauth2/authorize`; | ||
this.tokenEndpoint = `https://${domain}/oauth2/token`; | ||
this.tokenRevocationEndpoint = `https://${domain}/oauth2/revoke`; | ||
this.client = new OAuth2Client(clientId, clientSecret, redirectURI); | ||
} | ||
createAuthorizationURL(state, codeVerifier, scopes) { | ||
const url = new URL(this.authorizationEndpoint); | ||
url.searchParams.set("response_type", "code"); | ||
url.searchParams.set("client_id", this.clientId); | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
url.searchParams.set("state", state); | ||
const codeChallenge = createS256CodeChallenge(codeVerifier); | ||
url.searchParams.set("code_challenge_method", "S256"); | ||
url.searchParams.set("code_challenge", codeChallenge); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
const url = this.client.createAuthorizationURLWithPKCE(this.authorizationEndpoint, state, CodeChallengeMethod.S256, codeVerifier, scopes); | ||
return url; | ||
} | ||
async validateAuthorizationCode(code, codeVerifier) { | ||
const body = new URLSearchParams(); | ||
body.set("grant_type", "authorization_code"); | ||
body.set("code", code); | ||
body.set("redirect_uri", this.redirectURI); | ||
body.set("code_verifier", codeVerifier); | ||
body.set("client_id", this.clientId); | ||
body.set("client_secret", this.clientSecret); | ||
const request = createOAuth2Request(this.tokenEndpoint, body); | ||
const tokens = await sendTokenRequest(request); | ||
const tokens = await this.client.validateAuthorizationCode(this.tokenEndpoint, code, codeVerifier); | ||
return tokens; | ||
} | ||
// TODO: Add `scopes` parameter | ||
async refreshAccessToken(refreshToken) { | ||
const body = new URLSearchParams(); | ||
body.set("grant_type", "refresh_token"); | ||
body.set("refresh_token", refreshToken); | ||
body.set("client_id", this.clientId); | ||
body.set("client_secret", this.clientSecret); | ||
const request = createOAuth2Request(this.tokenEndpoint, body); | ||
const tokens = await sendTokenRequest(request); | ||
async refreshAccessToken(refreshToken, scopes) { | ||
const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, scopes); | ||
return tokens; | ||
} | ||
async revokeToken(token) { | ||
const body = new URLSearchParams(); | ||
body.set("token", token); | ||
body.set("client_id", this.clientId); | ||
body.set("client_secret", this.clientSecret); | ||
const request = createOAuth2Request(this.tokenRevocationEndpoint, body); | ||
await sendTokenRevocationRequest(request); | ||
await this.client.revokeToken(this.tokenRevocationEndpoint, token); | ||
} | ||
} |
@@ -0,3 +1,3 @@ | ||
import * as jwt from "@oslojs/jwt"; | ||
import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
import { createJWTSignatureMessage, encodeJWT } from "@oslojs/jwt"; | ||
const authorizationEndpoint = "https://appleid.apple.com/auth/authorize"; | ||
@@ -23,3 +23,5 @@ const tokenEndpoint = "https://appleid.apple.com/auth/token"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -61,6 +63,6 @@ return url; | ||
hash: "SHA-256" | ||
}, privateKey, createJWTSignatureMessage(headerJSON, payloadJSON))); | ||
const jwt = encodeJWT(headerJSON, payloadJSON, signature); | ||
return jwt; | ||
}, privateKey, jwt.createJWTSignatureMessage(headerJSON, payloadJSON))); | ||
const token = jwt.encodeJWT(headerJSON, payloadJSON, signature); | ||
return token; | ||
} | ||
} |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ url.searchParams.set("audience", "api.atlassian.com"); |
@@ -7,7 +7,7 @@ import type { OAuth2Tokens } from "../oauth2.js"; | ||
private client; | ||
constructor(domain: string, clientId: string, clientSecret: string, redirectURI: string); | ||
createAuthorizationURL(state: string, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string): Promise<OAuth2Tokens>; | ||
constructor(domain: string, clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string | null, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string, codeVerifier: string | null): Promise<OAuth2Tokens>; | ||
refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens>; | ||
revokeToken(token: string): Promise<void>; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { OAuth2Client } from "../client.js"; | ||
import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
export class Auth0 { | ||
@@ -13,8 +13,14 @@ authorizationEndpoint; | ||
} | ||
createAuthorizationURL(state, scopes) { | ||
const url = this.client.createAuthorizationURL(this.authorizationEndpoint, state, scopes); | ||
createAuthorizationURL(state, codeVerifier, scopes) { | ||
let url; | ||
if (codeVerifier !== null) { | ||
url = this.client.createAuthorizationURLWithPKCE(this.authorizationEndpoint, state, CodeChallengeMethod.S256, codeVerifier, scopes); | ||
} | ||
else { | ||
url = this.client.createAuthorizationURL(this.authorizationEndpoint, state, scopes); | ||
} | ||
return url; | ||
} | ||
async validateAuthorizationCode(code) { | ||
const tokens = await this.client.validateAuthorizationCode(this.tokenEndpoint, code, null); | ||
async validateAuthorizationCode(code, codeVerifier) { | ||
const tokens = await this.client.validateAuthorizationCode(this.tokenEndpoint, code, codeVerifier); | ||
return tokens; | ||
@@ -21,0 +27,0 @@ } |
@@ -7,3 +7,3 @@ import type { OAuth2Tokens } from "../oauth2.js"; | ||
private client; | ||
constructor(domain: string, clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(baseURL: string, clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
@@ -10,0 +10,0 @@ validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; |
import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
import { joinURIAndPath } from "../request.js"; | ||
export class Authentik { | ||
@@ -7,6 +8,6 @@ authorizationEndpoint; | ||
client; | ||
constructor(domain, clientId, clientSecret, redirectURI) { | ||
this.authorizationEndpoint = `https://${domain}/application/o/authorize/`; | ||
this.tokenEndpoint = `https://${domain}/application/o/token/`; | ||
this.tokenRevocationEndpoint = `https://${domain}/application/o/revoke`; | ||
constructor(baseURL, clientId, clientSecret, redirectURI) { | ||
this.authorizationEndpoint = joinURIAndPath(baseURL, "/application/o/authorize"); | ||
this.tokenEndpoint = joinURIAndPath(baseURL, "/application/o/token"); | ||
this.tokenRevocationEndpoint = joinURIAndPath(baseURL, "/application/o/revoke"); | ||
this.client = new OAuth2Client(clientId, clientSecret, redirectURI); | ||
@@ -13,0 +14,0 @@ } |
@@ -19,3 +19,5 @@ import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -22,0 +24,0 @@ return url; |
import type { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class Bungie { | ||
private client; | ||
constructor(clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, scopes: string[]): URL; | ||
@@ -6,0 +6,0 @@ validateAuthorizationCode(code: string): Promise<OAuth2Tokens>; |
@@ -19,3 +19,5 @@ import { createOAuth2Request, sendTokenRequest, sendTokenRevocationRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -22,0 +24,0 @@ return url; |
import type { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class Discord { | ||
private client; | ||
constructor(clientId: string, clientSecret: string, redirectURI: string); | ||
createAuthorizationURL(state: string, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string): Promise<OAuth2Tokens>; | ||
constructor(clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string | null, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string, codeVerifier: string | null): Promise<OAuth2Tokens>; | ||
refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens>; | ||
revokeToken(token: string): Promise<void>; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { OAuth2Client } from "../client.js"; | ||
import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
const authorizationEndpoint = "https://discord.com/oauth2/authorize"; | ||
@@ -10,8 +10,14 @@ const tokenEndpoint = "https://discord.com/api/oauth2/token"; | ||
} | ||
createAuthorizationURL(state, scopes) { | ||
const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); | ||
createAuthorizationURL(state, codeVerifier, scopes) { | ||
let url; | ||
if (codeVerifier !== null) { | ||
url = this.client.createAuthorizationURLWithPKCE(authorizationEndpoint, state, CodeChallengeMethod.S256, codeVerifier, scopes); | ||
} | ||
else { | ||
url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); | ||
} | ||
return url; | ||
} | ||
async validateAuthorizationCode(code) { | ||
const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); | ||
async validateAuthorizationCode(code, codeVerifier) { | ||
const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); | ||
return tokens; | ||
@@ -18,0 +24,0 @@ } |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
import type { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class Etsy { | ||
private client; | ||
constructor(clientId: string, _ignore: any, redirectURI: string); | ||
constructor(clientId: string, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
@@ -6,0 +6,0 @@ validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; |
@@ -6,4 +6,3 @@ import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
client; | ||
// v3: Remove `_ignore` | ||
constructor(clientId, _ignore, redirectURI) { | ||
constructor(clientId, redirectURI) { | ||
this.client = new OAuth2Client(clientId, null, redirectURI); | ||
@@ -10,0 +9,0 @@ } |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
@@ -1,2 +0,2 @@ | ||
import type { OAuth2Tokens } from "../oauth2.js"; | ||
import { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class GitHub { | ||
@@ -3,0 +3,0 @@ private clientId; |
@@ -1,2 +0,3 @@ | ||
import { createOAuth2Request, encodeBasicCredentials, sendTokenRequest } from "../request.js"; | ||
import { ArcticFetchError, createOAuth2Request, createOAuth2RequestError, encodeBasicCredentials, UnexpectedErrorResponseBodyError, UnexpectedResponseError } from "../request.js"; | ||
import { OAuth2Tokens } from "../oauth2.js"; | ||
const authorizationEndpoint = "https://github.com/login/oauth/authorize"; | ||
@@ -18,3 +19,5 @@ const tokenEndpoint = "https://github.com/login/oauth/access_token"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
if (this.redirectURI !== null) { | ||
@@ -49,1 +52,38 @@ url.searchParams.set("redirect_uri", this.redirectURI); | ||
} | ||
async function sendTokenRequest(request) { | ||
let response; | ||
try { | ||
response = await fetch(request); | ||
} | ||
catch (e) { | ||
throw new ArcticFetchError(e); | ||
} | ||
if (response.status !== 200) { | ||
if (response.body !== null) { | ||
await response.body.cancel(); | ||
} | ||
throw new UnexpectedResponseError(response.status); | ||
} | ||
let data; | ||
try { | ||
data = await response.json(); | ||
} | ||
catch { | ||
throw new UnexpectedResponseError(response.status); | ||
} | ||
if (typeof data !== "object" || data === null) { | ||
throw new UnexpectedErrorResponseBodyError(response.status, data); | ||
} | ||
if ("error" in data && typeof data.error === "string") { | ||
let error; | ||
try { | ||
error = createOAuth2RequestError(data); | ||
} | ||
catch { | ||
throw new UnexpectedErrorResponseBodyError(response.status, data); | ||
} | ||
throw error; | ||
} | ||
const tokens = new OAuth2Tokens(data); | ||
return tokens; | ||
} |
@@ -7,3 +7,3 @@ import type { OAuth2Tokens } from "../oauth2.js"; | ||
private client; | ||
constructor(domain: string, clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(baseURL: string, clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, scopes: string[]): URL; | ||
@@ -10,0 +10,0 @@ validateAuthorizationCode(code: string): Promise<OAuth2Tokens>; |
import { OAuth2Client } from "../client.js"; | ||
import { joinURIAndPath } from "../request.js"; | ||
export class GitLab { | ||
@@ -7,6 +8,6 @@ authorizationEndpoint; | ||
client; | ||
constructor(domain, clientId, clientSecret, redirectURI) { | ||
this.authorizationEndpoint = `https://${domain}/oauth/authorize`; | ||
this.tokenEndpoint = `https://${domain}/oauth/token`; | ||
this.tokenRevocationEndpoint = `https://${domain}/oauth/revoke`; | ||
constructor(baseURL, clientId, clientSecret, redirectURI) { | ||
this.authorizationEndpoint = joinURIAndPath(baseURL, "/oauth/authorize"); | ||
this.tokenEndpoint = joinURIAndPath(baseURL, "/oauth/token"); | ||
this.tokenRevocationEndpoint = joinURIAndPath(baseURL, "/oauth/revoke"); | ||
this.client = new OAuth2Client(clientId, clientSecret, redirectURI); | ||
@@ -13,0 +14,0 @@ } |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
@@ -7,3 +7,3 @@ import type { OAuth2Tokens } from "../oauth2.js"; | ||
private client; | ||
constructor(realmURL: string, clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(realmURL: string, clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
@@ -10,0 +10,0 @@ validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; |
@@ -19,3 +19,5 @@ import { createS256CodeChallenge } from "../oauth2.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -22,0 +24,0 @@ const codeChallenge = createS256CodeChallenge(codeVerifier); |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
@@ -19,3 +19,5 @@ // LinkedIn doesn't seem to support HTTP Basic Auth | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -22,0 +24,0 @@ return url; |
@@ -6,6 +6,6 @@ import type { OAuth2Tokens } from "../oauth2.js"; | ||
private client; | ||
constructor(tenant: string, clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(tenant: string, clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; | ||
refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens>; | ||
refreshAccessToken(refreshToken: string, scopes: string[]): Promise<OAuth2Tokens>; | ||
} |
import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
import { joinURIAndPath } from "../request.js"; | ||
export class MicrosoftEntraId { | ||
@@ -7,4 +8,4 @@ authorizationEndpoint; | ||
constructor(tenant, clientId, clientSecret, redirectURI) { | ||
this.authorizationEndpoint = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`; | ||
this.tokenEndpoint = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`; | ||
this.authorizationEndpoint = joinURIAndPath("https://login.microsoftonline.com", tenant, "/oauth2/v2.0/authorize"); | ||
this.tokenEndpoint = joinURIAndPath("https://login.microsoftonline.com", tenant, "/oauth2/v2.0/token"); | ||
this.client = new OAuth2Client(clientId, clientSecret, redirectURI); | ||
@@ -20,7 +21,6 @@ } | ||
} | ||
// v3 TODO: Add `scopes` parameter | ||
async refreshAccessToken(refreshToken) { | ||
const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, []); | ||
async refreshAccessToken(refreshToken, scopes) { | ||
const tokens = await this.client.refreshAccessToken(this.tokenEndpoint, refreshToken, scopes); | ||
return tokens; | ||
} | ||
} |
import type { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class MyAnimeList { | ||
private client; | ||
constructor(clientId: string, clientSecret: string, options?: { | ||
redirectURI?: string; | ||
}); | ||
constructor(clientId: string, clientSecret: string, redirectURI: string | null); | ||
createAuthorizationURL(state: string, codeVerifier: string): URL; | ||
@@ -8,0 +6,0 @@ validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; |
@@ -6,5 +6,4 @@ import { OAuth2Client, CodeChallengeMethod } from "../client.js"; | ||
client; | ||
// v3 TODO: fix | ||
constructor(clientId, clientSecret, options) { | ||
this.client = new OAuth2Client(clientId, clientSecret, options?.redirectURI ?? null); | ||
constructor(clientId, clientSecret, redirectURI) { | ||
this.client = new OAuth2Client(clientId, clientSecret, redirectURI); | ||
} | ||
@@ -11,0 +10,0 @@ createAuthorizationURL(state, codeVerifier) { |
import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
import { joinURIAndPath } from "../request.js"; | ||
export class Okta { | ||
@@ -10,7 +11,7 @@ authorizationEndpoint; | ||
if (authorizationServerId !== null) { | ||
baseURL = baseURL + `/${authorizationServerId}`; | ||
baseURL = joinURIAndPath(baseURL, authorizationServerId); | ||
} | ||
this.authorizationEndpoint = baseURL + "/v1/authorize"; | ||
this.tokenEndpoint = baseURL + "/v1/token"; | ||
this.tokenRevocationEndpoint = baseURL + "/v1/revoke"; | ||
this.authorizationEndpoint = joinURIAndPath(baseURL, "/v1/authorize"); | ||
this.tokenEndpoint = joinURIAndPath(baseURL, "/v1/token"); | ||
this.tokenRevocationEndpoint = joinURIAndPath(baseURL, "/v1/revoke"); | ||
this.client = new OAuth2Client(clientId, clientSecret, redirectURI); | ||
@@ -17,0 +18,0 @@ } |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
if (this.redirectURI !== null) { | ||
@@ -21,0 +23,0 @@ url.searchParams.set("redirect_uri", this.redirectURI); |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
import type { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class Roblox { | ||
private client; | ||
constructor(clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
@@ -6,0 +6,0 @@ validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; |
@@ -7,3 +7,3 @@ import type { OAuth2Tokens } from "../oauth2.js"; | ||
private client; | ||
constructor(domain: string, clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(domain: string, clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
@@ -10,0 +10,0 @@ validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; |
import type { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class Spotify { | ||
private client; | ||
constructor(clientId: string, clientSecret: string, redirectURI: string); | ||
createAuthorizationURL(state: string, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string): Promise<OAuth2Tokens>; | ||
constructor(clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string | null, scopes: string[]): URL; | ||
validateAuthorizationCode(code: string, codeVerifier: string | null): Promise<OAuth2Tokens>; | ||
refreshAccessToken(refreshToken: string): Promise<OAuth2Tokens>; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { OAuth2Client } from "../client.js"; | ||
import { CodeChallengeMethod, OAuth2Client } from "../client.js"; | ||
const authorizationEndpoint = "https://accounts.spotify.com/authorize"; | ||
@@ -9,8 +9,14 @@ const tokenEndpoint = "https://accounts.spotify.com/api/token"; | ||
} | ||
createAuthorizationURL(state, scopes) { | ||
const url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); | ||
createAuthorizationURL(state, codeVerifier, scopes) { | ||
let url; | ||
if (codeVerifier !== null) { | ||
url = this.client.createAuthorizationURLWithPKCE(authorizationEndpoint, state, CodeChallengeMethod.S256, codeVerifier, scopes); | ||
} | ||
else { | ||
url = this.client.createAuthorizationURL(authorizationEndpoint, state, scopes); | ||
} | ||
return url; | ||
} | ||
async validateAuthorizationCode(code) { | ||
const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, null); | ||
async validateAuthorizationCode(code, codeVerifier) { | ||
const tokens = await this.client.validateAuthorizationCode(tokenEndpoint, code, codeVerifier); | ||
return tokens; | ||
@@ -17,0 +23,0 @@ } |
@@ -19,3 +19,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -31,3 +33,5 @@ return url; | ||
body.set("client_secret", this.clientSecret); | ||
body.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
body.set("scope", scopes.join(" ")); | ||
} | ||
const request = createOAuth2Request(tokenEndpoint, body); | ||
@@ -44,3 +48,5 @@ const tokens = await sendTokenRequest(request); | ||
body.set("client_secret", this.clientSecret); | ||
body.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
body.set("scope", scopes.join(" ")); | ||
} | ||
const request = createOAuth2Request(refreshEndpoint, body); | ||
@@ -47,0 +53,0 @@ const tokens = await sendTokenRequest(request); |
@@ -18,3 +18,6 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
// Strava deviates from the RFC and uses a comma-delimitated string instead of space. | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(",")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +24,0 @@ return url; |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
@@ -19,3 +19,5 @@ // Does not support HTTP Basic Auth scheme. | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -22,0 +24,0 @@ return url; |
import type { OAuth2Tokens } from "../oauth2.js"; | ||
export declare class Twitter { | ||
private client; | ||
constructor(clientId: string, clientSecret: string, redirectURI: string); | ||
constructor(clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string, scopes: string[]): URL; | ||
@@ -6,0 +6,0 @@ validateAuthorizationCode(code: string, codeVerifier: string): Promise<OAuth2Tokens>; |
@@ -18,3 +18,5 @@ import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
url.searchParams.set("state", state); | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
if (scopes.length > 0) { | ||
url.searchParams.set("scope", scopes.join(" ")); | ||
} | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
@@ -21,0 +23,0 @@ return url; |
@@ -1,2 +0,2 @@ | ||
import type { OAuth2Tokens } from "../oauth2.js"; | ||
import { type OAuth2Tokens } from "../oauth2.js"; | ||
export declare class WorkOS { | ||
@@ -6,5 +6,5 @@ private clientId; | ||
private redirectURI; | ||
constructor(clientId: string, clientSecret: string, redirectURI: string); | ||
createAuthorizationURL(state: string): URL; | ||
validateAuthorizationCode(code: string): Promise<OAuth2Tokens>; | ||
constructor(clientId: string, clientSecret: string | null, redirectURI: string); | ||
createAuthorizationURL(state: string, codeVerifier: string | null): URL; | ||
validateAuthorizationCode(code: string, codeVerifier: string | null): Promise<OAuth2Tokens>; | ||
} |
import { createOAuth2Request, sendTokenRequest } from "../request.js"; | ||
import { createS256CodeChallenge } from "../oauth2.js"; | ||
const authorizationEndpoint = "https://api.workos.com/sso/authorize"; | ||
@@ -13,3 +14,3 @@ const tokenEndpoint = "https://api.workos.com/sso/token"; | ||
} | ||
createAuthorizationURL(state) { | ||
createAuthorizationURL(state, codeVerifier) { | ||
const url = new URL(authorizationEndpoint); | ||
@@ -20,5 +21,10 @@ url.searchParams.set("response_type", "code"); | ||
url.searchParams.set("redirect_uri", this.redirectURI); | ||
if (codeVerifier !== null) { | ||
const codeChallenge = createS256CodeChallenge(codeVerifier); | ||
url.searchParams.set("code_challenge_method", "S256"); | ||
url.searchParams.set("code_challenge", codeChallenge); | ||
} | ||
return url; | ||
} | ||
async validateAuthorizationCode(code) { | ||
async validateAuthorizationCode(code, codeVerifier) { | ||
const body = new URLSearchParams(); | ||
@@ -29,3 +35,8 @@ body.set("grant_type", "authorization_code"); | ||
body.set("client_id", this.clientId); | ||
body.set("client_secret", this.clientSecret); | ||
if (this.clientSecret !== null) { | ||
body.set("client_secret", this.clientSecret); | ||
} | ||
if (codeVerifier !== null) { | ||
body.set("code_verifier", codeVerifier); | ||
} | ||
const request = createOAuth2Request(tokenEndpoint, body); | ||
@@ -32,0 +43,0 @@ const tokens = await sendTokenRequest(request); |
import { OAuth2Tokens } from "./oauth2.js"; | ||
export declare function joinURIAndPath(base: string, ...path: string[]): string; | ||
export declare function createOAuth2Request(endpoint: string, body: URLSearchParams): Request; | ||
@@ -6,2 +7,3 @@ export declare function encodeBasicCredentials(username: string, password: string): string; | ||
export declare function sendTokenRevocationRequest(request: Request): Promise<void>; | ||
export declare function createOAuth2RequestError(result: object): OAuth2RequestError; | ||
export declare class ArcticFetchError extends Error { | ||
@@ -17,1 +19,10 @@ constructor(cause: unknown); | ||
} | ||
export declare class UnexpectedResponseError extends Error { | ||
status: number; | ||
constructor(responseStatus: number); | ||
} | ||
export declare class UnexpectedErrorResponseBodyError extends Error { | ||
status: number; | ||
data: unknown; | ||
constructor(status: number, data: unknown); | ||
} |
@@ -1,3 +0,12 @@ | ||
import { encodeBase64 } from "@oslojs/encoding"; | ||
import * as encoding from "@oslojs/encoding"; | ||
import { OAuth2Tokens } from "./oauth2.js"; | ||
import { trimLeft, trimRight } from "./utils.js"; | ||
export function joinURIAndPath(base, ...path) { | ||
let joined = trimRight(base, "/"); | ||
for (const part of path) { | ||
joined += "/"; | ||
joined += trimRight(trimLeft(part, "/"), "/"); | ||
} | ||
return joined; | ||
} | ||
export function createOAuth2Request(endpoint, body) { | ||
@@ -19,3 +28,3 @@ const bodyBytes = new TextEncoder().encode(body.toString()); | ||
const bytes = new TextEncoder().encode(`${username}:${password}`); | ||
return encodeBase64(bytes); | ||
return encoding.encodeBase64(bytes); | ||
} | ||
@@ -30,17 +39,40 @@ export async function sendTokenRequest(request) { | ||
} | ||
let data; | ||
try { | ||
data = await response.json(); | ||
if (response.status === 400 || response.status === 401) { | ||
let data; | ||
try { | ||
data = await response.json(); | ||
} | ||
catch { | ||
throw new UnexpectedResponseError(response.status); | ||
} | ||
if (typeof data !== "object" || data === null) { | ||
throw new UnexpectedErrorResponseBodyError(response.status, data); | ||
} | ||
let error; | ||
try { | ||
error = createOAuth2RequestError(data); | ||
} | ||
catch { | ||
throw new UnexpectedErrorResponseBodyError(response.status, data); | ||
} | ||
throw error; | ||
} | ||
catch { | ||
throw new Error("Failed to parse response body"); | ||
if (response.status === 200) { | ||
let data; | ||
try { | ||
data = await response.json(); | ||
} | ||
catch { | ||
throw new UnexpectedResponseError(response.status); | ||
} | ||
if (typeof data !== "object" || data === null) { | ||
throw new UnexpectedErrorResponseBodyError(response.status, data); | ||
} | ||
const tokens = new OAuth2Tokens(data); | ||
return tokens; | ||
} | ||
if (typeof data !== "object" || data === null) { | ||
throw new Error("Unexpected response body data"); | ||
if (response.body !== null) { | ||
await response.body.cancel(); | ||
} | ||
if ("error" in data && typeof data.error === "string") { | ||
const error = createOAuth2RequestError(data); | ||
throw error; | ||
} | ||
return new OAuth2Tokens(data); | ||
throw new UnexpectedResponseError(response.status); | ||
} | ||
@@ -55,21 +87,34 @@ export async function sendTokenRevocationRequest(request) { | ||
} | ||
if (response.ok) { | ||
if (response.status === 400 || response.status === 401) { | ||
let data; | ||
try { | ||
data = await response.json(); | ||
} | ||
catch { | ||
throw new UnexpectedErrorResponseBodyError(response.status, null); | ||
} | ||
if (typeof data !== "object" || data === null) { | ||
throw new UnexpectedErrorResponseBodyError(response.status, data); | ||
} | ||
let error; | ||
try { | ||
error = createOAuth2RequestError(data); | ||
} | ||
catch { | ||
throw new UnexpectedErrorResponseBodyError(response.status, data); | ||
} | ||
throw error; | ||
} | ||
if (response.status === 200) { | ||
if (response.body !== null) { | ||
await response.body.cancel(); | ||
} | ||
return; | ||
} | ||
let data; | ||
try { | ||
data = await response.json(); | ||
if (response.body !== null) { | ||
await response.body.cancel(); | ||
} | ||
catch { | ||
throw new Error("Failed to parse response body"); | ||
} | ||
if (typeof data !== "object" || data === null) { | ||
throw new Error("Unexpected response body data"); | ||
} | ||
if ("error" in data && typeof data.error === "string") { | ||
const error = createOAuth2RequestError(data); | ||
throw error; | ||
} | ||
throw new UnexpectedResponseError(response.status); | ||
} | ||
function createOAuth2RequestError(result) { | ||
export function createOAuth2RequestError(result) { | ||
let code; | ||
@@ -85,12 +130,22 @@ if ("error" in result && typeof result.error === "string") { | ||
let state = null; | ||
if ("error_description" in result && typeof result.error_description === "string") { | ||
if ("error_description" in result) { | ||
if (typeof result.error_description !== "string") { | ||
throw new Error("Invalid data"); | ||
} | ||
description = result.error_description; | ||
} | ||
if ("error_uri" in result && typeof result.error_uri === "string") { | ||
if ("error_uri" in result) { | ||
if (typeof result.error_uri !== "string") { | ||
throw new Error("Invalid data"); | ||
} | ||
uri = result.error_uri; | ||
} | ||
if ("state" in result && typeof result.state === "string") { | ||
if ("state" in result) { | ||
if (typeof result.state !== "string") { | ||
throw new Error("Invalid data"); | ||
} | ||
state = result.state; | ||
} | ||
return new OAuth2RequestError(code, description, uri, state); | ||
const error = new OAuth2RequestError(code, description, uri, state); | ||
return error; | ||
} | ||
@@ -117,1 +172,17 @@ export class ArcticFetchError extends Error { | ||
} | ||
export class UnexpectedResponseError extends Error { | ||
status; | ||
constructor(responseStatus) { | ||
super("Unexpected error response"); | ||
this.status = responseStatus; | ||
} | ||
} | ||
export class UnexpectedErrorResponseBodyError extends Error { | ||
status; | ||
data; | ||
constructor(status, data) { | ||
super("Unexpected error response body"); | ||
this.status = status; | ||
this.data = data; | ||
} | ||
} |
{ | ||
"name": "arctic", | ||
"type": "module", | ||
"version": "2.3.3", | ||
"description": "OAuth 2.0 clients for popular providers", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"module": "dist/index.js", | ||
"files": [ | ||
"/dist/" | ||
], | ||
"author": "pilcrowOnPaper", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/pilcrowOnPaper/arctic" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.8.6", | ||
"@typescript-eslint/eslint-plugin": "^6.7.5", | ||
"@typescript-eslint/parser": "^6.7.5", | ||
"eslint": "^8.51.0", | ||
"prettier": "^3.0.3", | ||
"typescript": "^5.2.2", | ||
"vitest": "1.6.0" | ||
}, | ||
"dependencies": { | ||
"@oslojs/crypto": "1.0.1", | ||
"@oslojs/encoding": "1.1.0", | ||
"@oslojs/jwt": "0.2.0" | ||
}, | ||
"scripts": { | ||
"build": "rm -rf dist/* && tsc", | ||
"format": "prettier -w .", | ||
"lint": "eslint src", | ||
"test": "vitest run --sequence.concurrent" | ||
} | ||
} | ||
"name": "arctic", | ||
"type": "module", | ||
"version": "3.0.0", | ||
"description": "OAuth 2.0 clients for popular providers", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"module": "dist/index.js", | ||
"scripts": { | ||
"build": "rm -rf dist/* && tsc", | ||
"format": "prettier -w .", | ||
"lint": "eslint src", | ||
"test": "vitest run --sequence.concurrent" | ||
}, | ||
"files": [ | ||
"/dist/" | ||
], | ||
"author": "pilcrowOnPaper", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/pilcrowonpaper/arctic.git" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.8.6", | ||
"@typescript-eslint/eslint-plugin": "^6.7.5", | ||
"@typescript-eslint/parser": "^6.7.5", | ||
"auri": "^3.1.0", | ||
"eslint": "^8.51.0", | ||
"prettier": "^3.0.3", | ||
"typescript": "^5.2.2", | ||
"vitest": "1.6.0" | ||
}, | ||
"dependencies": { | ||
"@oslojs/crypto": "1.0.1", | ||
"@oslojs/encoding": "1.1.0", | ||
"@oslojs/jwt": "0.2.0" | ||
} | ||
} |
@@ -5,3 +5,3 @@ # Arctic | ||
Arctic is a collection of OAuth 2.0 clients for popular providers. It only supports the authorization code grant type and intended to be used server-side. Built on top of the Fetch API, it's light weight, fully-typed, and runtime-agnostic. | ||
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. | ||
@@ -13,7 +13,7 @@ ``` | ||
```ts | ||
import { GitHub, generateState } from "arctic"; | ||
import * as arctic from "arctic"; | ||
const github = new GitHub(clientId, clientSecret); | ||
const github = new arctic.GitHub(clientId, clientSecret); | ||
const state = generateState(); | ||
const state = arctic.generateState(); | ||
const scopes = ["user:email"]; | ||
@@ -79,2 +79,3 @@ const authorizationURL = github.createAuthorizationURL(state, scopes); | ||
- Strava | ||
- TikTok | ||
- Tiltify | ||
@@ -81,0 +82,0 @@ - Tumblr |
125617
123
2899
87
8
3