Compare commits

...

6 Commits

Author SHA1 Message Date
Balázs Orbán
342202db87 configure client_secret_post explicitly in provider 2023-01-11 13:51:59 +01:00
Balázs Orbán
49cbf7431b Merge branch 'main' into fix/conform-twitch 2023-01-11 13:00:32 +01:00
Balázs Orbán
c7705eec35 fix(providers): conform Twitch provider to spec with escape hatch 2023-01-11 12:33:21 +01:00
Balázs Orbán
2d493d70e8 fix(core): stringify claims authorization url param 2023-01-11 12:32:43 +01:00
Balázs Orbán
ddbb4b0e51 fix(core): default to first supported auth method 2023-01-11 12:32:05 +01:00
Balázs Orbán
d785c5984f fix(core): add explicit non-conform escape hatch 2023-01-11 12:31:38 +01:00
4 changed files with 55 additions and 14 deletions

View File

@@ -104,7 +104,7 @@ export async function handleOAuth(
resCookies.push(nonce.cookie)
}
const codeGrantResponse = await o.authorizationCodeGrantRequest(
let codeGrantResponse = await o.authorizationCodeGrantRequest(
as,
client,
parameters,
@@ -112,6 +112,12 @@ export async function handleOAuth(
codeVerifier?.codeVerifier ?? "auth" // TODO: review fallback code verifier
)
if (provider.token?.conform) {
codeGrantResponse =
(await provider.token.conform(codeGrantResponse.clone())) ??
codeGrantResponse
}
let challenges: o.WWWAuthenticateChallenge[] | undefined
if ((challenges = o.parseWwwAuthenticateChallenges(codeGrantResponse))) {
for (const challenge of challenges) {

View File

@@ -96,6 +96,9 @@ function normalizeEndpoint(
// NOTE: This need to be checked when constructing the URL
// for the authorization, token and userinfo endpoints.
const url = new URL(e?.url ?? "https://authjs.dev")
for (const k in e?.params) url.searchParams.set(k, e?.params[k])
return { url, request: e?.request }
for (const k in e?.params) {
if (e?.params && k === "claims") e.params[k] = JSON.stringify(e.params[k])
url.searchParams.set(k, e?.params[k])
}
return { url, request: e?.request, conform: e?.conform }
}

View File

@@ -42,6 +42,8 @@ interface AdvancedEndpointHandler<P extends UrlParams, C, R> {
* You should **try to avoid using advanced options** unless you are very comfortable using them.
*/
request?: EndpointRequest<C, R, P>
/** @internal */
conform?: (response: Response) => Awaitable<Response | undefined>
}
/** Either an URL (containing all the parameters) or an object with more granular control. */
@@ -184,7 +186,11 @@ export type OAuthConfigInternal<Profile> = Omit<
OAuthEndpointType
> & {
authorization?: { url: URL }
token?: { url: URL; request?: TokenEndpointHandler["request"] }
token?: {
url: URL
request?: TokenEndpointHandler["request"]
conform?: TokenEndpointHandler["conform"]
}
userinfo?: { url: URL; request?: UserinfoEndpointHandler["request"] }
} & Pick<Required<OAuthConfig<Profile>>, "clientId" | "checks" | "profile">

View File

@@ -1,4 +1,4 @@
import type { OAuthConfig, OAuthUserConfig } from "./index.js"
import type { OIDCConfig, OIDCUserConfig } from "./index.js"
export interface TwitchProfile extends Record<string, any> {
sub: string
@@ -7,26 +7,52 @@ export interface TwitchProfile extends Record<string, any> {
picture: string
}
export default function Twitch<P extends TwitchProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
export default function Twitch(
config: OIDCUserConfig<TwitchProfile>
): OIDCConfig<TwitchProfile> {
return {
issuer: "https://id.twitch.tv/oauth2",
id: "twitch",
name: "Twitch",
type: "oidc",
client: { token_endpoint_auth_method: "client_secret_post" },
authorization: {
params: {
scope: "openid user:read:email",
claims: {
id_token: {
email: null,
picture: null,
preferred_username: null,
},
id_token: { email: null, picture: null, preferred_username: null },
},
},
},
token: {
async conform(response) {
const body = await response.json()
if (response.ok) {
if (typeof body.scope === "string") {
console.warn(
"'scope' is a string. Redundant workaround, please open an issue."
)
} else if (Array.isArray(body.scope)) {
body.scope = body.scope.join(" ")
return new Response(JSON.stringify(body), response)
} else if ("scope" in body) {
delete body.scope
return new Response(JSON.stringify(body), response)
}
} else {
const { message: error_description, error } = body
if (typeof error !== "string") {
return new Response(
JSON.stringify({ error: "invalid_request", error_description }),
response
)
}
console.warn(
"Response has 'error'. Redundant workaround, please open an issue."
)
}
},
},
style: {
logo: "/twitch.svg",
logoDark: "/twitch-dark.svg",
@@ -35,6 +61,6 @@ export default function Twitch<P extends TwitchProfile>(
bgDark: "#65459B",
textDark: "#fff",
},
options,
options: config,
}
}