diff --git a/packages/core/src/lib/actions/callback/oauth/callback.ts b/packages/core/src/lib/actions/callback/oauth/callback.ts index e471299447..e680866181 100644 --- a/packages/core/src/lib/actions/callback/oauth/callback.ts +++ b/packages/core/src/lib/actions/callback/oauth/callback.ts @@ -51,6 +51,60 @@ export async function handleOAuth( ) { const { logger, provider } = options + /** + * If the provider supplies a custom `token.request` function, call it + * directly and skip the standard OAuth 2.0 authorization-code grant entirely. + * This is required for protocols that differ from OAuth 2.0, such as + * Steam's OpenID 2.0 flow, where there is no authorization code to exchange + * and no token/issuer endpoint to discover. + * + * This check must happen before the authorization-server setup block below, + * which would otherwise crash for providers that have no `token.url` or `issuer`. + */ + if (provider.token?.request) { + const resCookies: Cookie[] = [] + const state = await checks.state.use(cookies, resCookies, options) + const codeVerifier = await checks.pkce.use(cookies, resCookies, options) + const { userinfo } = provider + + const tokenResponse = await provider.token.request({ + params: params as Record, + checks: { pkce: codeVerifier, state }, + provider, + }) + + if (!tokenResponse) { + throw new OAuthCallbackError( + `Provider "${provider.id}" token.request returned no tokens` + ) + } + + const tokens: TokenSet & Pick = + tokenResponse.tokens as TokenSet & Pick + + let profile: Profile = {} + + if (userinfo?.request) { + const _profile = await userinfo.request({ tokens, provider }) + if (_profile instanceof Object) profile = _profile + } else if (userinfo?.url) { + const userinfoResponse = await fetch(userinfo.url, { + headers: { Authorization: `Bearer ${tokens.access_token}` }, + }) + profile = await userinfoResponse.json() + } else { + throw new TypeError("No userinfo endpoint configured") + } + + const profileResult = await getUserAndAccount( + profile, + provider, + tokens, + logger + ) + return { ...profileResult, profile, cookies: resCookies } + } + let as: o.AuthorizationServer const { token, userinfo } = provider @@ -156,6 +210,10 @@ export async function handleOAuth( redirect_uri = provider.redirectProxyUrl } + let profile: Profile = {} + + const requireIdToken = isOIDCProvider(provider) + let codeGrantResponse = await o.authorizationCodeGrantRequest( as, client, @@ -181,10 +239,6 @@ export async function handleOAuth( codeGrantResponse } - let profile: Profile = {} - - const requireIdToken = isOIDCProvider(provider) - if (provider[conformInternal]) { switch (provider.id) { case "microsoft-entra-id": diff --git a/packages/core/src/lib/pages/error.tsx b/packages/core/src/lib/pages/error.tsx index ac8f847a7f..7eec4f674d 100644 --- a/packages/core/src/lib/pages/error.tsx +++ b/packages/core/src/lib/pages/error.tsx @@ -1,3 +1,4 @@ +import type { JSX } from "preact" import type { ErrorPageParam, Theme } from "../../types.js" /** diff --git a/packages/core/src/lib/utils/assert.ts b/packages/core/src/lib/utils/assert.ts index 4f1a255044..57c902bdac 100644 --- a/packages/core/src/lib/utils/assert.ts +++ b/packages/core/src/lib/utils/assert.ts @@ -136,8 +136,8 @@ export function assertConfig( let key if (typeof a !== "string" && !a?.url) key = "authorization" - else if (typeof t !== "string" && !t?.url) key = "token" - else if (typeof u !== "string" && !u?.url) key = "userinfo" + else if (typeof t !== "string" && !t?.url && !t?.request) key = "token" + else if (typeof u !== "string" && !u?.url && !u?.request) key = "userinfo" if (key) { return new InvalidEndpoints( diff --git a/packages/core/src/providers/steam.ts b/packages/core/src/providers/steam.ts new file mode 100644 index 0000000000..6c4467df4f --- /dev/null +++ b/packages/core/src/providers/steam.ts @@ -0,0 +1,352 @@ +/** + *
+ * Built-in Steam integration. + * + * + * + *
+ * + * @module providers/steam + */ +import type { OAuthConfig, OAuthUserConfig } from "./index.js" + +const STEAM_PROVIDER_ID = "steam" +const STEAM_AUTHORIZATION_URL = "https://steamcommunity.com/openid/login" +const STEAM_API_URL = + "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002" +const STEAM_OPENID_NS = "http://specs.openid.net/auth/2.0" +const STEAM_CLAIMED_ID_PREFIX = "https://steamcommunity.com/openid/id/" +const STEAM_ID_PATTERN = /^https?:\/\/steamcommunity\.com\/openid\/id\/(\d+)$/ + +/** + * The profile returned by Steam's GetPlayerSummaries API. + * + * See the [Steam Web API documentation](https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0002.29). + */ +export interface SteamProfile extends Record { + /** 64-bit SteamID of the user. */ + steamid: string + /** This represents whether the profile is visible or not. */ + communityvisibilitystate: number + /** If set, indicates the user has a community profile configured. */ + profilestate: number + /** The player's persona name (display name). */ + personaname: string + /** The full URL of the player's Steam Community profile page. */ + profileurl: string + /** The full URL of the player's 32x32px avatar. */ + avatar: string + /** The full URL of the player's 64x64px avatar. */ + avatarmedium: string + /** The full URL of the player's 184x184px avatar. */ + avatarfull: string + /** SHA1 hash of the player's avatar. */ + avatarhash: string + /** Unix timestamp of the last time the user was online. */ + lastlogoff: number + /** The user's current status. */ + personastate: number + /** The primary clan for this user. */ + primaryclanid: string + /** Unix timestamp of the date the profile was created. */ + timecreated: number + /** Additional flags for the persona state. */ + personastateflags: number +} + +/** + * Configuration options for the Steam provider. + */ +export interface SteamProviderOptions extends Omit< + OAuthUserConfig, + "clientId" | "clientSecret" +> { + /** + * Your Steam Web API Key. Obtain one at: + * https://steamcommunity.com/dev/apikey + */ + clientSecret: string + /** + * Override the base callback URL. Defaults to `AUTH_URL` / `NEXTAUTH_URL`. + * Must **not** have a trailing slash. + * + * @example "https://example.com/api/auth/callback" + */ + callbackUrl?: string +} + +/** + * Verifies the Steam OpenID 2.0 assertion using the check_authentication + * mode — a direct HTTP POST to Steam's OpenID endpoint. + * + * This avoids any Node.js-only dependencies and works in any JS runtime + * (Edge, Node, Deno, Bun). + * + * @see https://openid.net/specs/openid-connect-core-1_0.html + * @see https://steamcommunity.com/dev + */ +async function verifySteamAssertion( + params: Record, + returnTo: string +): Promise { + if ( + params["openid.op_endpoint"] !== STEAM_AUTHORIZATION_URL || + params["openid.ns"] !== STEAM_OPENID_NS + ) { + throw new Error( + "Steam OpenID assertion failed: invalid endpoint or namespace" + ) + } + + const claimedId = params["openid.claimed_id"] ?? "" + if (!claimedId.startsWith(STEAM_CLAIMED_ID_PREFIX)) { + throw new Error( + "Steam OpenID assertion failed: claimed_id does not match Steam pattern" + ) + } + + const identity = params["openid.identity"] ?? "" + if (!identity.startsWith(STEAM_CLAIMED_ID_PREFIX)) { + throw new Error( + "Steam OpenID assertion failed: identity does not match Steam pattern" + ) + } + + // Rebuild the params for check_authentication verification + const verifyBody = new URLSearchParams() + for (const [key, value] of Object.entries(params)) { + if (value !== undefined) verifyBody.set(key, value) + } + verifyBody.set("openid.mode", "check_authentication") + // Ensure the return_to matches what we set in the authorization request + verifyBody.set("openid.return_to", returnTo) + + const verifyResponse = await fetch(STEAM_AUTHORIZATION_URL, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: verifyBody.toString(), + }) + + if (!verifyResponse.ok) { + throw new Error( + `Steam OpenID verification request failed: ${verifyResponse.status}` + ) + } + + const verifyText = await verifyResponse.text() + + if (!verifyText.includes("is_valid:true")) { + throw new Error( + "Steam OpenID assertion is invalid (Steam returned is_valid:false)" + ) + } + + const match = claimedId.match(STEAM_ID_PATTERN) + if (!match?.[1]) { + throw new Error( + "Steam OpenID assertion failed: could not extract Steam ID from claimed_id" + ) + } + + return match[1] +} + +/** + * Add Steam login to your page. + * + * :::note + * Steam uses **OpenID 2.0**, not OAuth 2.0 / OIDC. This means the provider + * requires the `token.request` hook in Auth.js core's `handleOAuth` callback, + * which bypasses the standard `oauth4webapi` code-grant flow. + * ::: + * + * ### Setup + * + * #### Callback URL + * ``` + * https://example.com/api/auth/callback/steam + * ``` + * + * #### Configuration + * ```ts + * import { Auth } from "@auth/core" + * import Steam from "@auth/core/providers/steam" + * + * const response = await Auth(request, { + * providers: [ + * Steam({ clientSecret: process.env.STEAM_API_KEY }), + * ], + * }) + * ``` + * + * ### Resources + * + * - [Steam OpenID documentation](https://steamcommunity.com/dev) + * - [Obtaining a Steam Web API Key](https://steamcommunity.com/dev/apikey) + * - [Steam Web API reference](https://developer.valvesoftware.com/wiki/Steam_Web_API) + * + * ### Notes + * + * Unlike all other built-in providers, Steam uses **OpenID 2.0** — a protocol + * older than and incompatible with OAuth 2.0. The differences are: + * + * - There is no `code` exchanged. Steam redirects back with OpenID assertion + * params (e.g. `openid.claimed_id`, `openid.sig`) in the callback URL. + * - Verification is done by re-posting those params to Steam with + * `openid.mode=check_authentication`. + * - A Steam Web API key (`clientSecret`) is required to fetch the user profile + * after authentication. + * + * :::tip + * + * The Steam provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/steam.ts). + * To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/configuring-oauth-providers). + * + * ::: + * + * :::info **Disclaimer** + * + * If you think you found a bug in the default configuration, you can [open an issue](https://authjs.dev/new/provider-issue). + * + * Auth.js strictly adheres to the specification and it cannot take responsibility for any deviation from + * the spec by the provider. You can open an issue, but if the problem is non-compliance with the spec, + * we might not pursue a resolution. You can ask for more help in [Discussions](https://authjs.dev/new/github-discussions). + * + * ::: + */ +export default function Steam( + options: SteamProviderOptions +): OAuthConfig { + if (!options.clientSecret) { + throw new Error( + "Steam provider requires `clientSecret` (your Steam Web API Key). " + + "Obtain one at: https://steamcommunity.com/dev/apikey" + ) + } + + const callbackBase = options.callbackUrl + ? new URL(options.callbackUrl) + : new URL( + "/api/auth/callback", + process.env.AUTH_URL ?? process.env.NEXTAUTH_URL + ) + + const realm = callbackBase.origin + const returnTo = `${callbackBase.href}/${STEAM_PROVIDER_ID}` + + return { + id: STEAM_PROVIDER_ID, + name: "Steam", + type: "oauth", + style: { + logo: "https://authjs.dev/img/providers/steam.svg", + bg: "#000000", + text: "#ffffff", + }, + /** + * Steam does not use PKCE or state — the OpenID 2.0 protocol + * has its own replay-protection via openid.nonce. + */ + checks: ["none"], + clientId: STEAM_PROVIDER_ID, + clientSecret: options.clientSecret, + authorization: { + url: STEAM_AUTHORIZATION_URL, + params: { + "openid.mode": "checkid_setup", + "openid.ns": STEAM_OPENID_NS, + "openid.identity": "http://specs.openid.net/auth/2.0/identifier_select", + "openid.claimed_id": + "http://specs.openid.net/auth/2.0/identifier_select", + "openid.return_to": returnTo, + "openid.realm": realm, + }, + }, + /** + * Steam's OpenID 2.0 does not issue an authorization code, so the standard + * OAuth 2.0 token exchange is bypassed entirely via `token.request`. + * + * The `params` object here contains the raw OpenID assertion query params + * that Steam appended to the callback URL (openid.mode, openid.sig, etc.). + * We re-post them to Steam with `openid.mode=check_authentication` to + * confirm the assertion server-side, then extract the Steam ID. + * + * The Steam ID is stored as `access_token` so that `userinfo.request` + * can use it to call the Steam Web API. + * + */ + token: { + async request({ params, provider }) { + const callbackUrl = + (provider as { callbackUrl?: string }).callbackUrl ?? returnTo + + const steamId = await verifySteamAssertion( + params as Record, + callbackUrl + ) + + return { + tokens: { + access_token: steamId, + token_type: "bearer", + }, + } + }, + }, + /** + * Uses the Steam ID (stored as `access_token` from the token step) to + * call the Steam Web API and retrieve the full player profile. + * + */ + userinfo: { + async request({ tokens, provider }) { + const steamId = tokens.access_token + if (!steamId) { + throw new Error( + "Steam provider: access_token (Steam ID) is missing from tokens" + ) + } + + const url = new URL(STEAM_API_URL) + url.searchParams.set("key", provider.clientSecret as string) + url.searchParams.set("steamids", steamId) + + const response = await fetch(url) + + if (!response.ok) { + throw new Error( + `Steam API request failed: ${response.status} ${response.statusText}` + ) + } + + const data = (await response.json()) as { + response: { players: SteamProfile[] } + } + + const player = data.response.players[0] + if (!player) { + throw new Error( + `Steam API returned no player for Steam ID: ${steamId}` + ) + } + + return player + }, + }, + profile(profile: SteamProfile) { + return { + id: profile.steamid, + name: profile.personaname, + /** + * Steam does not expose email addresses via its public API. + * We synthesize one from the Steam ID so that Auth.js does not + * reject the profile (email is required by the User model). + * Apps that need a real email should collect it separately after sign-in. + */ + email: `${profile.steamid}@steamcommunity.com`, + image: profile.avatarfull, + } + }, + options, + } +} diff --git a/packages/core/test/providers/steam.test.ts b/packages/core/test/providers/steam.test.ts new file mode 100644 index 0000000000..e26515423d --- /dev/null +++ b/packages/core/test/providers/steam.test.ts @@ -0,0 +1,303 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" +import Steam from "../../src/providers/steam" + +const STEAM_AUTHORIZATION_URL = "https://steamcommunity.com/openid/login" +const STEAM_OPENID_NS = "http://specs.openid.net/auth/2.0" +const FAKE_STEAM_ID = "76561198000000001" +const FAKE_CLAIMED_ID = `https://steamcommunity.com/openid/id/${FAKE_STEAM_ID}` + +describe("Steam provider", () => { + beforeEach(() => { + process.env.AUTH_URL = "https://example.com" + }) + + afterEach(() => { + delete process.env.AUTH_URL + vi.restoreAllMocks() + }) + + // --------------------------------------------------------------------------- + // Config shape + // --------------------------------------------------------------------------- + + it("returns the correct provider id, name and type", () => { + const provider = Steam({ clientSecret: "test-key" }) + expect(provider.id).toBe("steam") + expect(provider.name).toBe("Steam") + expect(provider.type).toBe("oauth") + }) + + it("sets checks to ['none'] because Steam uses OpenID 2.0 not OAuth 2.0", () => { + const provider = Steam({ clientSecret: "test-key" }) + expect(provider.checks).toEqual(["none"]) + }) + + it("throws if clientSecret is missing", () => { + expect(() => Steam({ clientSecret: "" })).toThrow(/clientSecret/) + }) + + // --------------------------------------------------------------------------- + // Authorization URL construction + // --------------------------------------------------------------------------- + + it("builds the correct OpenID 2.0 authorization params", () => { + const provider = Steam({ clientSecret: "test-key" }) + const auth = provider.authorization as { + url: string + params: Record + } + + expect(auth.url).toBe(STEAM_AUTHORIZATION_URL) + expect(auth.params["openid.ns"]).toBe(STEAM_OPENID_NS) + expect(auth.params["openid.mode"]).toBe("checkid_setup") + expect(auth.params["openid.identity"]).toBe( + "http://specs.openid.net/auth/2.0/identifier_select" + ) + expect(auth.params["openid.claimed_id"]).toBe( + "http://specs.openid.net/auth/2.0/identifier_select" + ) + expect(auth.params["openid.return_to"]).toContain("/steam") + expect(auth.params["openid.realm"]).toBe("https://example.com") + }) + + it("uses a custom callbackUrl when provided", () => { + const provider = Steam({ + clientSecret: "test-key", + callbackUrl: "https://custom.example.com/auth/callback", + }) + const auth = provider.authorization as { + url: string + params: Record + } + + expect(auth.params["openid.realm"]).toBe("https://custom.example.com") + expect(auth.params["openid.return_to"]).toBe( + "https://custom.example.com/auth/callback/steam" + ) + }) + + // --------------------------------------------------------------------------- + // Profile mapping + // --------------------------------------------------------------------------- + + it("maps the Steam profile to the Auth.js user shape", () => { + const provider = Steam({ clientSecret: "test-key" }) + + const steamProfile = { + steamid: FAKE_STEAM_ID, + personaname: "TestPlayer", + avatarfull: "https://cdn.cloudflare.steamstatic.com/avatar_full.jpg", + profileurl: `https://steamcommunity.com/profiles/${FAKE_STEAM_ID}/`, + avatar: "https://cdn.cloudflare.steamstatic.com/avatar.jpg", + avatarmedium: "https://cdn.cloudflare.steamstatic.com/avatar_medium.jpg", + avatarhash: "abc123", + lastlogoff: 1700000000, + personastate: 1, + communityvisibilitystate: 3, + profilestate: 1, + primaryclanid: "103582791429521408", + timecreated: 1200000000, + personastateflags: 0, + } + + const mapped = provider.profile!(steamProfile, {} as any) + + expect(mapped.id).toBe(FAKE_STEAM_ID) + expect(mapped.name).toBe("TestPlayer") + expect(mapped.image).toBe( + "https://cdn.cloudflare.steamstatic.com/avatar_full.jpg" + ) + expect(mapped.email).toContain(FAKE_STEAM_ID) + expect(mapped.email).toContain("steamcommunity.com") + }) + + // --------------------------------------------------------------------------- + // token.request — OpenID 2.0 assertion verification + // --------------------------------------------------------------------------- + + it("token.request verifies the OpenID assertion and returns the Steam ID as access_token", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: true, + text: async () => `ns:${STEAM_OPENID_NS}\nis_valid:true\n`, + }) + ) + + const provider = Steam({ clientSecret: "test-key" }) + const tokenHandler = provider.token as { + request: (ctx: any) => Promise<{ tokens: any }> + } + + const result = await tokenHandler.request({ + params: { + "openid.op_endpoint": STEAM_AUTHORIZATION_URL, + "openid.ns": STEAM_OPENID_NS, + "openid.claimed_id": FAKE_CLAIMED_ID, + "openid.identity": FAKE_CLAIMED_ID, + "openid.return_to": "https://example.com/api/auth/callback/steam", + "openid.mode": "id_res", + "openid.sig": "fakesig", + "openid.signed": "op_endpoint,claimed_id,identity,return_to,mode", + }, + checks: {}, + provider: { callbackUrl: "https://example.com/api/auth/callback/steam" }, + }) + + expect(result.tokens.access_token).toBe(FAKE_STEAM_ID) + expect(result.tokens.token_type).toBe("bearer") + }) + + it("token.request throws when Steam returns is_valid:false", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: true, + text: async () => `ns:${STEAM_OPENID_NS}\nis_valid:false\n`, + }) + ) + + const provider = Steam({ clientSecret: "test-key" }) + const tokenHandler = provider.token as { + request: (ctx: any) => Promise + } + + await expect( + tokenHandler.request({ + params: { + "openid.op_endpoint": STEAM_AUTHORIZATION_URL, + "openid.ns": STEAM_OPENID_NS, + "openid.claimed_id": FAKE_CLAIMED_ID, + "openid.identity": FAKE_CLAIMED_ID, + "openid.mode": "id_res", + }, + checks: {}, + provider: { + callbackUrl: "https://example.com/api/auth/callback/steam", + }, + }) + ).rejects.toThrow(/is_valid:false/) + }) + + it("token.request throws when the endpoint/namespace is wrong", async () => { + const provider = Steam({ clientSecret: "test-key" }) + const tokenHandler = provider.token as { + request: (ctx: any) => Promise + } + + await expect( + tokenHandler.request({ + params: { + "openid.op_endpoint": "https://evil.example.com/openid", + "openid.ns": STEAM_OPENID_NS, + "openid.claimed_id": FAKE_CLAIMED_ID, + "openid.identity": FAKE_CLAIMED_ID, + "openid.mode": "id_res", + }, + checks: {}, + provider: { + callbackUrl: "https://example.com/api/auth/callback/steam", + }, + }) + ).rejects.toThrow(/endpoint or namespace/) + }) + + it("token.request throws when claimed_id does not start with the Steam prefix", async () => { + const provider = Steam({ clientSecret: "test-key" }) + const tokenHandler = provider.token as { + request: (ctx: any) => Promise + } + + await expect( + tokenHandler.request({ + params: { + "openid.op_endpoint": STEAM_AUTHORIZATION_URL, + "openid.ns": STEAM_OPENID_NS, + "openid.claimed_id": "https://evil.example.com/openid/id/123", + "openid.identity": FAKE_CLAIMED_ID, + "openid.mode": "id_res", + }, + checks: {}, + provider: { + callbackUrl: "https://example.com/api/auth/callback/steam", + }, + }) + ).rejects.toThrow(/claimed_id/) + }) + + // --------------------------------------------------------------------------- + // userinfo.request — Steam Web API call + // --------------------------------------------------------------------------- + + it("userinfo.request fetches the Steam profile using the Steam ID from access_token", async () => { + const mockProfile = { + steamid: FAKE_STEAM_ID, + personaname: "TestPlayer", + avatarfull: "https://example.com/avatar_full.jpg", + avatar: "https://example.com/avatar.jpg", + avatarmedium: "https://example.com/avatar_medium.jpg", + } + + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ response: { players: [mockProfile] } }), + }) + ) + + const provider = Steam({ clientSecret: "test-key" }) + const userinfoHandler = provider.userinfo as { + request: (ctx: any) => Promise + } + + const profile = await userinfoHandler.request({ + tokens: { access_token: FAKE_STEAM_ID }, + provider: { clientSecret: "test-key" }, + }) + + expect(profile.steamid).toBe(FAKE_STEAM_ID) + expect(profile.personaname).toBe("TestPlayer") + + const fetchMock = vi.mocked(fetch) + const calledUrl = fetchMock.mock.calls[0][0] as URL + expect(calledUrl.searchParams.get("steamids")).toBe(FAKE_STEAM_ID) + expect(calledUrl.searchParams.get("key")).toBe("test-key") + }) + + it("userinfo.request throws if access_token (Steam ID) is missing", async () => { + const provider = Steam({ clientSecret: "test-key" }) + const userinfoHandler = provider.userinfo as { + request: (ctx: any) => Promise + } + + await expect( + userinfoHandler.request({ + tokens: { access_token: undefined }, + provider: { clientSecret: "test-key" }, + }) + ).rejects.toThrow(/access_token.*missing/) + }) + + it("userinfo.request throws if the Steam API returns no players", async () => { + vi.stubGlobal( + "fetch", + vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ response: { players: [] } }), + }) + ) + + const provider = Steam({ clientSecret: "test-key" }) + const userinfoHandler = provider.userinfo as { + request: (ctx: any) => Promise + } + + await expect( + userinfoHandler.request({ + tokens: { access_token: FAKE_STEAM_ID }, + provider: { clientSecret: "test-key" }, + }) + ).rejects.toThrow(/no player/) + }) +}) diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index af328abaad..c87ad0851b 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "allowJs": true, "baseUrl": ".", + "ignoreDeprecations": "6.0", "declaration": true, "declarationMap": true, "emitDecoratorMetadata": true,