Compare commits

...

13 Commits

Author SHA1 Message Date
Thang Vu
f48e0f1e2d Update pnpm-lock.yaml 2022-06-20 14:08:39 +07:00
Thang Vu
2eef2f85d5 Merge branch 'main' into feat/oauth4webapi 2022-06-20 14:04:22 +07:00
Thang Vu
bf31512071 Merge branch 'main' into feat/oauth4webapi 2022-06-20 12:00:44 +07:00
Thang Vu
acc9966285 Update pnpm-lock.yaml 2022-06-10 16:02:27 +07:00
Thang Vu
6dd44000d7 Merge branch 'main' into feat/oauth4webapi 2022-06-10 15:55:26 +07:00
Thang Vu
2267292f5f Improve pkce 2022-04-08 09:13:38 +07:00
Thang Vu
883b36e2b2 check if PKCE supported 2022-04-07 20:40:00 +07:00
Thang Vu
029edb93ee Always use PKCE 2022-04-07 18:46:49 +07:00
Thang Vu
ebcc3280df Some improvement
- handle pkce discovery
- update `validateAuthResponse` to use `expectState`
2022-04-07 15:04:41 +07:00
Thang Vu
70a16e82c3 Throw better error 2022-04-06 17:36:43 +07:00
Thang Vu
1365211a4e Check state only if pkce is not suported 2022-04-06 17:33:54 +07:00
Thang Vu
6df5773220 Revert most breaking changes.
test
2022-04-06 16:31:26 +07:00
Thang Vu
e3e3cf12d1 replace openid-client with oauth4webapi 2022-04-04 15:15:23 +07:00
13 changed files with 5633 additions and 7069 deletions

View File

@@ -68,10 +68,10 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.16.3", "@babel/runtime": "^7.16.3",
"@panva/hkdf": "^1.0.1", "@panva/hkdf": "^1.0.1",
"@panva/oauth4webapi": "^0.0.10",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"jose": "^4.3.7", "jose": "^4.3.7",
"oauth": "^0.9.15", "oauth": "^0.9.15",
"openid-client": "^5.1.0",
"preact": "^10.6.3", "preact": "^10.6.3",
"preact-render-to-string": "^5.1.19", "preact-render-to-string": "^5.1.19",
"uuid": "^8.3.2" "uuid": "^8.3.2"

View File

@@ -0,0 +1,32 @@
import { discoveryRequest, processDiscoveryResponse } from "@panva/oauth4webapi"
import type { AuthorizationServer } from "@panva/oauth4webapi"
import type { InternalProvider } from "src/lib/types"
export default async function getAuthorizationServer(
provider: InternalProvider<"oauth">
): Promise<AuthorizationServer> {
if (provider.idToken) {
const issuer = new URL(provider.issuer as string)
return await discoveryRequest(issuer).then(
async (response) => await processDiscoveryResponse(issuer, response)
)
} else {
return {
issuer: provider.issuer as string,
authorization_endpoint:
typeof provider.authorization === "string"
? provider.authorization
: provider.authorization?.url,
token_endpoint:
typeof provider.token === "string"
? provider.token
: provider.token?.url,
userinfo_endpoint:
typeof provider.userinfo === "string"
? provider.userinfo
: provider.userinfo?.url,
jwks_uri: provider.jwks_uri,
}
}
}

View File

@@ -1,7 +1,7 @@
import { openidClient } from "./client"
import { oAuth1Client } from "./client-legacy" import { oAuth1Client } from "./client-legacy"
import { createState } from "./state-handler" import { createState } from "./state-handler"
import { createPKCE } from "./pkce-handler" import { createPKCE } from "./pkce-handler"
import getAuthorizationServer from "./authorization-server"
import type { AuthorizationParameters } from "openid-client" import type { AuthorizationParameters } from "openid-client"
import type { InternalOptions } from "../../types" import type { InternalOptions } from "../../types"
@@ -50,28 +50,53 @@ export default async function getAuthorizationUrl(params: {
return { redirect: url } return { redirect: url }
} }
const client = await openidClient(options) const authorizationServer = await getAuthorizationServer(provider)
if (!authorizationServer.authorization_endpoint) throw new Error()
const authorizationUrl = new URL(authorizationServer.authorization_endpoint)
for (const [key, value] of Object.entries(params)) {
authorizationUrl.searchParams.set(key, value as string)
}
authorizationUrl.searchParams.set("client_id", provider.clientId as string)
authorizationUrl.searchParams.set("redirect_uri", provider.callbackUrl)
if (typeof provider.authorization !== "string" && provider.authorization) {
const { params: authorizationEndpointParams } = provider.authorization
if (typeof authorizationEndpointParams?.response_type === "string") {
authorizationUrl.searchParams.set(
"response_type",
authorizationEndpointParams.response_type
)
}
if (typeof authorizationEndpointParams?.scope === "string") {
authorizationUrl.searchParams.set(
"scope",
authorizationEndpointParams.scope
)
}
}
const authorizationParams: AuthorizationParameters = params
const cookies: Cookie[] = [] const cookies: Cookie[] = []
const pkce = await createPKCE(options, authorizationServer)
authorizationUrl.searchParams.set("code_challenge", pkce.code_challenge)
authorizationUrl.searchParams.set(
"code_challenge_method",
pkce.code_challenge_method
)
cookies.push(pkce.cookie)
const state = await createState(options) const state = await createState(options)
if (state) { if (!pkce.isSupported && state) {
authorizationParams.state = state.value authorizationUrl.searchParams.set("state", state.value)
cookies.push(state.cookie) cookies.push(state.cookie)
} }
const pkce = await createPKCE(options) logger.debug("GET_AUTHORIZATION_URL", { authorizationUrl, cookies })
if (pkce) { return { redirect: authorizationUrl.href, cookies }
authorizationParams.code_challenge = pkce.code_challenge
authorizationParams.code_challenge_method = pkce.code_challenge_method
cookies.push(pkce.cookie)
}
const url = client.authorizationUrl(authorizationParams)
logger.debug("GET_AUTHORIZATION_URL", { url, cookies })
return { redirect: url, cookies }
} catch (error) { } catch (error) {
logger.error("GET_AUTHORIZATION_URL_ERROR", error as Error) logger.error("GET_AUTHORIZATION_URL_ERROR", error as Error)
throw error throw error

View File

@@ -1,16 +1,31 @@
import { TokenSet } from "openid-client"
import { openidClient } from "./client" import { openidClient } from "./client"
import { oAuth1Client } from "./client-legacy" import { oAuth1Client } from "./client-legacy"
import { useState } from "./state-handler" import { useState } from "./state-handler"
import { usePKCECodeVerifier } from "./pkce-handler" import { usePKCECodeVerifier } from "./pkce-handler"
import { OAuthCallbackError } from "../../errors" import { OAuthCallbackError } from "../../errors"
import {
authorizationCodeGrantRequest,
expectNoState,
getValidatedIdTokenClaims,
isOAuth2Error,
processAuthorizationCodeOAuth2Response,
processAuthorizationCodeOpenIDResponse,
processUserInfoResponse,
userInfoRequest,
validateAuthResponse,
} from "@panva/oauth4webapi"
import getAuthorizationServer from "./authorization-server"
import type { CallbackParamsType } from "openid-client"
import type { Account, LoggerInstance, Profile } from "../../.." import type { Account, LoggerInstance, Profile } from "../../.."
import type { OAuthChecks, OAuthConfig } from "../../../providers" import type { OAuthChecks, OAuthConfig } from "../../../providers"
import type { InternalOptions } from "../../types" import type { InternalOptions } from "../../types"
import type { RequestInternal, OutgoingResponse } from "../.." import type { RequestInternal, OutgoingResponse } from "../.."
import type { Cookie } from "../cookie" import type { Cookie } from "../cookie"
import type {
OAuth2Error,
OAuth2TokenEndpointResponse,
OpenIDTokenEndpointResponse,
} from "@panva/oauth4webapi"
export default async function oAuthCallback(params: { export default async function oAuthCallback(params: {
options: InternalOptions<"oauth"> options: InternalOptions<"oauth">
@@ -19,7 +34,7 @@ export default async function oAuthCallback(params: {
method: Required<RequestInternal>["method"] method: Required<RequestInternal>["method"]
cookies: RequestInternal["cookies"] cookies: RequestInternal["cookies"]
}): Promise<GetProfileResult & { cookies?: OutgoingResponse["cookies"] }> { }): Promise<GetProfileResult & { cookies?: OutgoingResponse["cookies"] }> {
const { options, query, body, method, cookies } = params const { options, query, body, cookies } = params
const { logger, provider } = options const { logger, provider } = options
const errorMessage = body?.error ?? query?.error const errorMessage = body?.error ?? query?.error
@@ -65,81 +80,111 @@ export default async function oAuthCallback(params: {
} }
try { try {
const client = await openidClient(options) const client = openidClient(provider)
const authorizationServer = await getAuthorizationServer(provider)
let tokens: TokenSet let tokens:
| OpenIDTokenEndpointResponse
| OAuth2TokenEndpointResponse
| OAuth2Error
const checks: OAuthChecks = {} let expectedState: string | typeof expectNoState = expectNoState
const resCookies: Cookie[] = [] const resCookies: Cookie[] = []
const authParams = new URLSearchParams(query)
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
const pkce = await usePKCECodeVerifier(
codeVerifier,
options,
authorizationServer
)
resCookies.push(pkce.cookie)
const state = await useState(cookies?.[options.cookies.state.name], options) const state = await useState(cookies?.[options.cookies.state.name], options)
if (state) { if (!pkce.isSupported && state) {
checks.state = state.value
resCookies.push(state.cookie) resCookies.push(state.cookie)
expectedState = state.value
authParams.append("state", state.value)
} }
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name] const callbackParameters = validateAuthResponse(
const pkce = await usePKCECodeVerifier(codeVerifier, options) authorizationServer,
if (pkce) { client,
checks.code_verifier = pkce.codeVerifier authParams,
resCookies.push(pkce.cookie) expectedState
)
if (isOAuth2Error(callbackParameters)) {
throw new OAuthCallbackError(callbackParameters.error)
} }
const params: CallbackParamsType = { const response = await authorizationCodeGrantRequest(
...client.callbackParams({ authorizationServer,
url: `http://n?${new URLSearchParams(query)}`, client,
// TODO: Ask to allow object to be passed upstream: callbackParameters,
// https://github.com/panva/node-openid-client/blob/3ae206dfc78c02134aa87a07f693052c637cab84/types/index.d.ts#L439 provider.callbackUrl,
// @ts-expect-error pkce.codeVerifier
body, )
method,
}),
// @ts-expect-error
...provider.token?.params,
}
// @ts-expect-error if (typeof provider.token !== "string" && provider.token?.request) {
if (provider.token?.request) { const params = {
// @ts-expect-error ...callbackParameters,
...provider.token?.params,
}
const checks = new URLSearchParams()
if (state) checks.append("state", state.value)
checks.append("code_verifier", pkce.codeVerifier)
const response = await provider.token.request({ const response = await provider.token.request({
provider, provider,
params, params,
checks, checks,
client, client,
}) })
tokens = new TokenSet(response.tokens) tokens = response.tokens
} else if (provider.idToken) { } else if (provider.idToken) {
tokens = await client.callback(provider.callbackUrl, params, checks) tokens = await processAuthorizationCodeOpenIDResponse(
authorizationServer,
client,
response
)
} else { } else {
tokens = await client.oauthCallback(provider.callbackUrl, params, checks) tokens = await processAuthorizationCodeOAuth2Response(
authorizationServer,
client,
response
)
} }
// REVIEW: How can scope be returned as an array? if (isOAuth2Error(tokens)) {
if (Array.isArray(tokens.scope)) { throw new OAuthCallbackError(tokens.error)
tokens.scope = tokens.scope.join(" ")
} }
let profile: Profile let profile: Profile | Response
// @ts-expect-error if (typeof provider.userinfo !== "string" && provider.userinfo?.request) {
if (provider.userinfo?.request) {
// @ts-expect-error
profile = await provider.userinfo.request({ profile = await provider.userinfo.request({
provider, provider,
tokens, tokens,
client, client,
}) })
} else if (provider.idToken) { } else if (provider.idToken) {
profile = tokens.claims() const idToken = getValidatedIdTokenClaims(tokens)
profile = await processUserInfoResponse(
authorizationServer,
client,
idToken?.sub as string,
response
)
} else { } else {
profile = await client.userinfo(tokens, { profile = await userInfoRequest(
// @ts-expect-error authorizationServer,
params: provider.userinfo?.params, client,
}) tokens.access_token
)
} }
const profileResult = await getProfile({ const profileResult = await getProfile({
profile, profile: profile as Profile,
provider, provider,
tokens, tokens,
logger, logger,
@@ -156,7 +201,7 @@ export default async function oAuthCallback(params: {
export interface GetProfileParams { export interface GetProfileParams {
profile: Profile profile: Profile
tokens: TokenSet tokens: OpenIDTokenEndpointResponse | OAuth2TokenEndpointResponse
provider: OAuthConfig<any> provider: OAuthConfig<any>
logger: LoggerInstance logger: LoggerInstance
} }

View File

@@ -1,14 +1,20 @@
import { InternalProvider } from "src/lib/types"
import type { Client as WebApiClient } from "@panva/oauth4webapi"
import { Issuer, custom } from "openid-client" import { Issuer, custom } from "openid-client"
import type { Client } from "openid-client" import type { Client } from "openid-client"
import type { InternalOptions } from "../../types" import type { InternalOptions } from "../../types"
/** /**
* NOTE: We can add auto discovery of the provider's endpoint
* that requires only one endpoint to be specified by the user.
* Check out `Issuer.discover`
*
* Client supporting OAuth 2.x and OIDC * Client supporting OAuth 2.x and OIDC
*/ */
export function webApiClient(provider: InternalProvider<"oauth">): WebApiClient {
return {
client_id: provider.clientId as string,
client_secret: provider.clientSecret as string,
token_endpoint_auth_method: "client_secret_basic",
...provider.client,
}
}
export async function openidClient( export async function openidClient(
options: InternalOptions<"oauth"> options: InternalOptions<"oauth">
): Promise<Client> { ): Promise<Client> {
@@ -31,21 +37,4 @@ export async function openidClient(
userinfo_endpoint: provider.userinfo?.url ?? provider.userinfo, userinfo_endpoint: provider.userinfo?.url ?? provider.userinfo,
}) })
} }
const client = new issuer.Client(
{
client_id: provider.clientId as string,
client_secret: provider.clientSecret as string,
redirect_uris: [provider.callbackUrl],
...provider.client,
},
provider.jwks
)
// allow a 10 second skew
// See https://github.com/nextauthjs/next-auth/issues/3032
// and https://github.com/nextauthjs/next-auth/issues/3067
client[custom.clock_tolerance] = 10
return client
} }

View File

@@ -0,0 +1,9 @@
import { generateRandomCodeVerifier } from "@panva/oauth4webapi"
/**
* Generate random `state` value encoded as base64url. This method returns oauth4webapi's `generateRandomCodeVerifier` for convenience.
* @see {@link https://github.com/panva/oauth4webapi/blob/main/docs/functions/generateRandomCodeVerifier.md generateRandomCodeVerifier.}
*/
export function generateRandomState() {
return generateRandomCodeVerifier()
}

View File

@@ -1,30 +1,43 @@
import * as jwt from "../../../jwt" import * as jwt from "../../../jwt"
import {
generateRandomCodeVerifier,
calculatePKCECodeChallenge,
} from "@panva/oauth4webapi"
import { generators } from "openid-client" import { generators } from "openid-client"
import type { InternalOptions } from "../../types" import type { InternalOptions } from "../../types"
import type { Cookie } from "../cookie" import type { Cookie } from "../cookie"
import type { AuthorizationServer } from "@panva/oauth4webapi"
const PKCE_CODE_CHALLENGE_METHOD = "S256" const PKCE_CODE_CHALLENGE_METHOD = "S256"
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
/**
* Check if PKCE is supported by the authorization server.
* @see {@link https://datatracker.ietf.org/doc/html/rfc8414#section-2 code_challenge_methods_supported}
* @param as The authorization server. @see {@link https://github.com/panva/oauth4webapi/blob/main/docs/interfaces/AuthorizationServer.md AuthorizationServer}
*/
function isPKCESupported(as: AuthorizationServer) {
return !!as.code_challenge_methods_supported?.includes(
PKCE_CODE_CHALLENGE_METHOD
)
}
/** /**
* Returns `code_challenge` and `code_challenge_method` * Returns `code_challenge` and `code_challenge_method`
* and saves them in a cookie. * and saves them in a cookie.
*/ */
export async function createPKCE(options: InternalOptions<"oauth">): Promise< export async function createPKCE(
| undefined options: InternalOptions<"oauth">,
| { as: AuthorizationServer
code_challenge: string ): Promise<{
code_challenge_method: "S256" code_challenge: string
cookie: Cookie code_challenge_method: "S256"
} cookie: Cookie
> { isSupported: boolean
const { cookies, logger, provider } = options }> {
if (!provider.checks?.includes("pkce")) { const { cookies, logger } = options
// Provider does not support PKCE, return nothing. const code_verifier = generateRandomCodeVerifier()
return const code_challenge = await calculatePKCECodeChallenge(code_verifier)
}
const code_verifier = generators.codeVerifier()
const code_challenge = generators.codeChallenge(code_verifier)
const expires = new Date() const expires = new Date()
expires.setTime(expires.getTime() + PKCE_MAX_AGE * 1000) expires.setTime(expires.getTime() + PKCE_MAX_AGE * 1000)
@@ -51,34 +64,37 @@ export async function createPKCE(options: InternalOptions<"oauth">): Promise<
value: encryptedCodeVerifier, value: encryptedCodeVerifier,
options: { ...cookies.pkceCodeVerifier.options, expires }, options: { ...cookies.pkceCodeVerifier.options, expires },
}, },
isSupported: isPKCESupported(as),
} }
} }
/** /**
* Returns code_verifier if provider uses PKCE, * Returns `code_verifier`,
* and clears the container cookie afterwards. * and clears the container cookie afterwards.
*/ */
export async function usePKCECodeVerifier( export async function usePKCECodeVerifier(
codeVerifier: string | undefined, codeVerifier: string | undefined,
options: InternalOptions<"oauth"> options: InternalOptions<"oauth">,
): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> { as: AuthorizationServer
const { cookies, provider } = options ): Promise<{ codeVerifier: string; cookie: Cookie; isSupported: boolean }> {
if (codeVerifier === undefined) throw new Error("Invalid code verifier")
if (!provider?.checks?.includes("pkce") || !codeVerifier) { const { cookies } = options
return
}
const pkce = (await jwt.decode({ const pkce = await jwt.decode({
...options.jwt, ...options.jwt,
token: codeVerifier, token: codeVerifier,
})) as any })
if (pkce === null) throw new Error("Invalid code verifier")
return { return {
codeVerifier: pkce?.code_verifier ?? undefined, codeVerifier: pkce.code_verifier as string,
cookie: { cookie: {
name: cookies.pkceCodeVerifier.name, name: cookies.pkceCodeVerifier.name,
value: "", value: "",
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 }, options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
}, },
isSupported: isPKCESupported(as),
} }
} }

View File

@@ -2,6 +2,7 @@ import { generators } from "openid-client"
import type { InternalOptions } from "../../types" import type { InternalOptions } from "../../types"
import type { Cookie } from "../cookie" import type { Cookie } from "../cookie"
import { generateRandomState } from "./helper"
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
@@ -16,7 +17,7 @@ export async function createState(
return return
} }
const state = generators.state() const state = generateRandomState()
const encodedState = await jwt.encode({ const encodedState = await jwt.encode({
...jwt, ...jwt,

View File

@@ -11,6 +11,7 @@ import type { TokenSetParameters } from "openid-client"
import type { JWT, JWTOptions } from "../jwt" import type { JWT, JWTOptions } from "../jwt"
import type { LoggerInstance } from "../utils/logger" import type { LoggerInstance } from "../utils/logger"
import type { CookieSerializeOptions } from "cookie" import type { CookieSerializeOptions } from "cookie"
import type { TokenEndpointResponse } from "@panva/oauth4webapi"
import type { NextApiRequest, NextApiResponse } from "next" import type { NextApiRequest, NextApiResponse } from "next"
@@ -224,7 +225,7 @@ export interface Theme {
* Some of them are available with different casing, * Some of them are available with different casing,
* but they refer to the same value. * but they refer to the same value.
*/ */
export type TokenSet = TokenSetParameters export type TokenSet = TokenEndpointResponse
/** /**
* Usually contains information about the provider being used * Usually contains information about the provider being used

View File

@@ -1,3 +1,4 @@
import { AuthorizationServer, userInfoRequest } from "@panva/oauth4webapi"
import type { OAuthConfig, OAuthUserConfig } from "." import type { OAuthConfig, OAuthUserConfig } from "."
interface FacebookPictureData { interface FacebookPictureData {
@@ -26,11 +27,17 @@ export default function Facebook<P extends FacebookProfile>(
// https://developers.facebook.com/docs/graph-api/reference/user/#fields // https://developers.facebook.com/docs/graph-api/reference/user/#fields
params: { fields: "id,name,email,picture" }, params: { fields: "id,name,email,picture" },
async request({ tokens, client, provider }) { async request({ tokens, client, provider }) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // @ts-expect-error
return await client.userinfo(tokens.access_token!, { const userinfo_endpoint = new URL(provider.userinfo?.url)
// @ts-expect-error // @ts-expect-error
params: provider.userinfo?.params, Object.entries(provider.userinfo?.params).forEach(([key, value]) => {
userinfo_endpoint.searchParams.append(key, value as string)
}) })
const as: AuthorizationServer = {
issuer: options.issuer as string,
userinfo_endpoint: userinfo_endpoint.href,
}
return await userInfoRequest(as, client, tokens.access_token)
}, },
}, },
profile(profile: P) { profile(profile: P) {

View File

@@ -1,33 +1,20 @@
import type { CommonProviderOptions } from "../providers" import type { CommonProviderOptions } from "../providers"
import type { Profile, TokenSet, User, Awaitable } from ".." import type { Profile, TokenSet, User, Awaitable } from ".."
import type {
AuthorizationParameters,
CallbackParamsType,
Issuer,
ClientMetadata,
IssuerMetadata,
OAuthCallbackChecks,
OpenIDCallbackChecks,
HttpOptions,
} from "openid-client"
import type { JWK } from "jose" import type { JWK } from "jose"
import type { AuthorizationServer, Client } from "@panva/oauth4webapi"
type Client = InstanceType<Issuer["Client"]>
export type { OAuthProviderType } from "./oauth-types" export type { OAuthProviderType } from "./oauth-types"
type ChecksType = "pkce" | "state" | "none" type ChecksType = "pkce" | "state" | "none"
export type OAuthChecks = OpenIDCallbackChecks | OAuthCallbackChecks type PartialIssuer = Partial<Pick<AuthorizationServer, "jwks_uri" | "issuer">>
type PartialIssuer = Partial<Pick<IssuerMetadata, "jwks_endpoint" | "issuer">>
type UrlParams = Record<string, unknown> type UrlParams = Record<string, unknown>
type EndpointRequest<C, R, P> = ( type EndpointRequest<C, R, P> = (
context: C & { context: C & {
/** `openid-client` Client */ /** `oauth4webapi` Client */
client: Client client: Client
/** Provider is passed for convenience, ans also contains the `callbackUrl`. */ /** Provider is passed for convenience, ans also contains the `callbackUrl`. */
provider: OAuthConfig<P> & { provider: OAuthConfig<P> & {
@@ -61,8 +48,7 @@ export type EndpointHandler<
R = any R = any
> = AdvancedEndpointHandler<P, C, R> > = AdvancedEndpointHandler<P, C, R>
export type AuthorizationEndpointHandler = export type AuthorizationEndpointHandler = EndpointHandler<UrlParams>
EndpointHandler<AuthorizationParameters>
export type TokenEndpointHandler = EndpointHandler< export type TokenEndpointHandler = EndpointHandler<
UrlParams, UrlParams,
@@ -71,12 +57,12 @@ export type TokenEndpointHandler = EndpointHandler<
* Parameters extracted from the request to the `/api/auth/callback/:providerId` endpoint. * Parameters extracted from the request to the `/api/auth/callback/:providerId` endpoint.
* Contains params like `state`. * Contains params like `state`.
*/ */
params: CallbackParamsType params: URLSearchParams
/** /**
* When using this custom flow, make sure to do all the necessary security checks. * When using this custom flow, make sure to do all the necessary security checks.
* Thist object contains parameters you have to match against the request to make sure it is valid. * This object contains parameters you have to match against the request to make sure it is valid.
*/ */
checks: OAuthChecks checks: URLSearchParams
}, },
{ {
tokens: TokenSet tokens: TokenSet
@@ -86,7 +72,7 @@ export type TokenEndpointHandler = EndpointHandler<
export type UserinfoEndpointHandler = EndpointHandler< export type UserinfoEndpointHandler = EndpointHandler<
UrlParams, UrlParams,
{ tokens: TokenSet }, { tokens: TokenSet },
Profile Profile | Response
> >
export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer { export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
@@ -112,7 +98,7 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
version?: string version?: string
profile?: (profile: P, tokens: TokenSet) => Awaitable<User & { id: string }> profile?: (profile: P, tokens: TokenSet) => Awaitable<User & { id: string }>
checks?: ChecksType | ChecksType[] checks?: ChecksType | ChecksType[]
client?: Partial<ClientMetadata> client?: Partial<Client>
jwks?: { keys: JWK[] } jwks?: { keys: JWK[] }
clientId?: string clientId?: string
clientSecret?: string clientSecret?: string
@@ -128,11 +114,6 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
idToken?: boolean idToken?: boolean
// TODO: only allow for BattleNet // TODO: only allow for BattleNet
region?: string region?: string
// TODO: only allow for some
issuer?: string
/** Read more at: https://github.com/panva/node-openid-client/tree/main/docs#customizing-http-requests */
httpOptions?: HttpOptions
/** /**
* The options provided by the user. * The options provided by the user.
* We will perform a deep-merge of these values * We will perform a deep-merge of these values

View File

@@ -1,4 +1,10 @@
import type { OAuthConfig, OAuthUserConfig } from "." import type { OAuthConfig, OAuthUserConfig } from "."
import {
authorizationCodeGrantRequest,
AuthorizationServer,
isOAuth2Error,
processAuthorizationCodeOAuth2Response,
} from "@panva/oauth4webapi"
export interface TwitterLegacyProfile { export interface TwitterLegacyProfile {
id: number id: number
@@ -183,13 +189,33 @@ export default function Twitter<
url: "https://api.twitter.com/2/oauth2/token", url: "https://api.twitter.com/2/oauth2/token",
// TODO: Remove this // TODO: Remove this
async request({ client, params, checks, provider }) { async request({ client, params, checks, provider }) {
const response = await client.oauthCallback( const as: AuthorizationServer = {
provider.callbackUrl, issuer: options.issuer as string,
// @ts-expect-error
token_endpoint: provider.token?.url,
}
const additionalParameters = new URLSearchParams()
additionalParameters.append("client_id", options.clientId)
const response = await authorizationCodeGrantRequest(
as,
client,
params, params,
checks, provider.callbackUrl,
{ exchangeBody: { client_id: options.clientId } } checks.get("code_verifier") as string,
{
additionalParameters,
}
) )
return { tokens: response } const tokens = await processAuthorizationCodeOAuth2Response(
as,
client,
response
)
if (isOAuth2Error(tokens)) {
throw new Error()
}
return { tokens }
}, },
}, },
userinfo: { userinfo: {

12276
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff