-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix NextAuth redirecting to /undefined when signIn method throws an
error @auth/core `Auth` function can return an internalResponse or a Standard Response. Internal response has a `redirect` attribute. The problem is that the logic inside `Auth` method when there is an error is returning a Standard web Response which doesn't have `redirect` attribute but we can get redirecting url by using `headers.get('Origin')`. I think this fix this issue: #11008
- Loading branch information
1 parent
fc0e10a
commit f9b1a41
Showing
12 changed files
with
347 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<p align="center"> | ||
<br/> | ||
<a href="https://authjs.dev" target="_blank"> | ||
<img height="64px" src="https://authjs.dev/img/logo-sm.png" /> | ||
</a> | ||
<h3 align="center"><b>Test Adapter</b> - NextAuth.js / Auth.js</a></h3> | ||
<p align="center" style="align: center;"> | ||
<a href="https://npm.im/@auth/pg-adapter"> | ||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" /> | ||
</a> | ||
<a href="https://npm.im/@auth/test-adapter"> | ||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/pg-adapter?color=green&label=@auth/test-adapter&style=flat-square"> | ||
</a> | ||
<a href="https://www.npmtrends.com/@auth/pg-adapter"> | ||
<img src="https://img.shields.io/npm/dm/@auth/test-adapter?label=%20downloads&style=flat-square" alt="Downloads" /> | ||
</a> | ||
<a href="https://github.com/nextauthjs/next-auth/stargazers"> | ||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="GitHub Stars" /> | ||
</a> | ||
</p> | ||
</p> | ||
|
||
--- | ||
|
||
Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/test). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "@auth/test-adapter", | ||
"version": "0.1.0", | ||
"description": "Testing adapter for next-auth.", | ||
"homepage": "https://authjs.dev", | ||
"repository": "https://github.com/nextauthjs/next-auth", | ||
"bugs": { | ||
"url": "https://github.com/nextauthjs/next-auth/issues" | ||
}, | ||
"author": "Jake Coppinger", | ||
"contributors": [ | ||
"Thang Huu Vu <hi@thvu.dev>" | ||
], | ||
"license": "ISC", | ||
"keywords": [ | ||
"next-auth", | ||
"@auth", | ||
"Auth.js", | ||
"next.js", | ||
"oauth" | ||
], | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"types": "./index.d.ts", | ||
"import": "./index.js" | ||
} | ||
}, | ||
"files": [ | ||
"*.d.ts*", | ||
"*.js", | ||
"src" | ||
], | ||
"private": false, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"scripts": { | ||
"build": "tsc" | ||
}, | ||
"dependencies": { | ||
"@auth/core": "workspace:*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { | ||
Adapter, | ||
AdapterSession, | ||
AdapterUser, | ||
VerificationToken, | ||
AdapterAccount, | ||
} from "@auth/core/adapters" | ||
|
||
export function TestAdapter(): Adapter { | ||
let user: AdapterUser | null = null | ||
let session: AdapterSession | null = null | ||
let account: AdapterAccount | null = null | ||
let verificationToken: VerificationToken | null = null | ||
return { | ||
async createUser(data: AdapterUser) { | ||
user = data | ||
return user | ||
}, | ||
async getUser(userId: string) { | ||
return user && user.id === userId ? user : null | ||
}, | ||
async getUserByEmail(email: string) { | ||
return user && user.email === email ? user : null | ||
}, | ||
async createSession(data: { | ||
sessionToken: string | ||
userId: string | ||
expires: Date | ||
}) { | ||
session = { | ||
sessionToken: data.sessionToken, | ||
userId: data.userId, | ||
expires: data.expires, | ||
} | ||
return Promise.resolve(session) | ||
}, | ||
async getSessionAndUser(sessionToken: string) { | ||
const result = | ||
session && session.sessionToken === sessionToken | ||
? { session: session!, user: user! } | ||
: null | ||
return Promise.resolve(result) | ||
}, | ||
async updateUser(data: Partial<AdapterUser> & Pick<AdapterUser, "id">) { | ||
if (user?.id !== data.id) { | ||
throw new Error("No user id.") | ||
} | ||
|
||
user = { ...user, ...data } | ||
return user | ||
}, | ||
async updateSession( | ||
data: Partial<AdapterSession> & Pick<AdapterSession, "sessionToken"> | ||
) { | ||
if (!session || session.sessionToken !== data.sessionToken) return null | ||
|
||
session = { ...session, ...data } | ||
return session | ||
}, | ||
async linkAccount(data: AdapterAccount) { | ||
account = data | ||
return Promise.resolve(account) | ||
}, | ||
async getUserByAccount( | ||
input: Pick<AdapterAccount, "provider" | "providerAccountId"> | ||
) { | ||
return account && | ||
account.provider === input.provider && | ||
account.providerAccountId === input.providerAccountId | ||
? user | ||
: null | ||
}, | ||
async deleteSession(sessionToken: string) { | ||
if (!session || session.sessionToken !== sessionToken) return null | ||
session = null | ||
}, | ||
async createVerificationToken(data: VerificationToken) { | ||
verificationToken = data | ||
return verificationToken | ||
}, | ||
async useVerificationToken(params: { identifier: string; token: string }) { | ||
if (!verificationToken) return null | ||
if ( | ||
verificationToken.identifier === params.identifier && | ||
verificationToken.token === params.token | ||
) { | ||
verificationToken = null | ||
return verificationToken | ||
} | ||
|
||
return null | ||
}, | ||
async deleteUser(id: string) { | ||
if (user?.id !== id) return undefined | ||
user = null | ||
}, | ||
async unlinkAccount( | ||
params: Pick<AdapterAccount, "provider" | "providerAccountId"> | ||
) { | ||
if ( | ||
!account || | ||
account.provider !== params.provider || | ||
account.providerAccountId !== params.providerAccountId | ||
) { | ||
return undefined | ||
} | ||
account = null | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "utils/tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": ".", | ||
"rootDir": "src" | ||
}, | ||
"exclude": ["*.js", "*.d.ts"], | ||
"include": ["src/**/*"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// @ts-check | ||
|
||
/** | ||
* @type {import('typedoc').TypeDocOptions & import('typedoc-plugin-markdown').MarkdownTheme} | ||
*/ | ||
module.exports = { | ||
entryPoints: ["src/index.ts"], | ||
entryPointStrategy: "expand", | ||
tsconfig: "./tsconfig.json", | ||
entryModule: "@auth/test-adapter", | ||
entryFileName: "../test-adapter.mdx", | ||
includeVersion: true, | ||
readme: 'none', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" | ||
import { signIn } from "./actions" | ||
import { EmailProviderType } from "../providers" | ||
import { TestAdapter } from "@auth/test-adapter" | ||
import { NextAuthConfig } from "." | ||
|
||
let mockedHeaders = vi.hoisted(() => { | ||
return new globalThis.Headers() | ||
}) | ||
|
||
const mockRedirect = vi.hoisted(() => vi.fn()) | ||
vi.mock("next/navigation", async (importOriginal) => { | ||
const originalModule = await importOriginal() | ||
return { | ||
// @ts-expect-error - not typed | ||
...originalModule, | ||
redirect: mockRedirect, | ||
} | ||
}) | ||
|
||
vi.mock("next/headers", async (importOriginal) => { | ||
const originalModule = await importOriginal() | ||
return { | ||
// @ts-expect-error - not typed | ||
...originalModule, | ||
headers: () => mockedHeaders, | ||
cookies: () => { | ||
const cookies: { [key: string]: unknown } = {} | ||
return { | ||
get: (name: string) => { | ||
return cookies[name] | ||
}, | ||
set: (name: string, value: string) => { | ||
cookies[name] = value | ||
}, | ||
} | ||
}, | ||
} | ||
}) | ||
|
||
let options = { | ||
redirectTo: "http://localhost/dashboard", | ||
email: "jane@example.com", | ||
} | ||
let authorizationParams = {} | ||
let config: NextAuthConfig = { | ||
secret: ["supersecret"], | ||
trustHost: true, | ||
basePath: "/api/auth", | ||
adapter: TestAdapter(), | ||
providers: [ | ||
{ | ||
id: "nodemailer", | ||
type: "email" as EmailProviderType, | ||
name: "Email", | ||
from: "no-reply@authjs.dev", | ||
maxAge: 86400, | ||
sendVerificationRequest: async () => { }, | ||
options: {}, | ||
}, | ||
], | ||
} | ||
|
||
describe("signIn", () => { | ||
beforeEach(() => { | ||
process.env.NEXTAUTH_URL = "http://localhost" | ||
}) | ||
|
||
afterEach(() => { | ||
process.env.NEXTAUTH_URL = undefined | ||
}) | ||
|
||
it("redirects to verify URL", async () => { | ||
await signIn("nodemailer", options, authorizationParams, config) | ||
|
||
expect(mockRedirect).toHaveBeenCalledWith( | ||
"http://localhost/api/auth/verify-request?provider=nodemailer&type=email" | ||
) | ||
}) | ||
|
||
it("redirects to error URL", async () => { | ||
config = { | ||
...config, | ||
providers: [ | ||
{ | ||
id: "nodemailer", | ||
type: "email" as EmailProviderType, | ||
name: "Email", | ||
from: "no-reply@authjs.dev", | ||
maxAge: 86400, | ||
sendVerificationRequest: async () => { | ||
throw new Error("IGNORE_ERROR_IN_TEST_STACK") | ||
}, | ||
options: {}, | ||
}, | ||
], | ||
} | ||
let redirectTo: string | undefined | null | ||
redirectTo = await signIn( | ||
"nodemailer", | ||
{ ...options, redirect: false }, | ||
authorizationParams, | ||
config | ||
) | ||
expect(redirectTo).toEqual( | ||
"http://localhost/api/auth/error?error=Configuration" | ||
) | ||
}) | ||
}) |
Oops, something went wrong.