Auto-commit 2026-04-29 16:31

This commit is contained in:
2026-04-29 16:31:27 -04:00
parent e8687bb6b2
commit 0495ee5bd2
19691 changed files with 3272886 additions and 138 deletions

125
node_modules/next-auth/src/adapters.ts generated vendored Normal file
View File

@@ -0,0 +1,125 @@
import { Account, User, Awaitable } from "."
import type { Adapter as FutureAdapter } from "@auth/core/adapters"
export interface AdapterUser extends User {
id: string
email: string
emailVerified: Date | null
}
export interface AdapterAccount extends Account {
userId: string
}
export interface AdapterSession {
/** A randomly generated value that is used to get hold of the session. */
sessionToken: string
/** Used to connect the session to a particular user */
userId: string
expires: Date
}
export interface VerificationToken {
identifier: string
expires: Date
token: string
}
/**
* Using a custom adapter you can connect to any database backend or even several different databases.
* Custom adapters created and maintained by our community can be found in the adapters repository.
* Feel free to add a custom adapter from your project to the repository,
* or even become a maintainer of a certain adapter.
* Custom adapters can still be created and used in a project without being added to the repository.
*
* **Required methods**
*
* _(These methods are required for all sign in flows)_
* - `createUser`
* - `getUser`
* - `getUserByEmail`
* - `getUserByAccount`
* - `linkAccount`
* - `createSession`
* - `getSessionAndUser`
* - `updateSession`
* - `deleteSession`
* - `updateUser`
*
* _(Required to support email / passwordless sign in)_
*
* - `createVerificationToken`
* - `useVerificationToken`
*
* **Unimplemented methods**
*
* _(These methods will be required in a future release, but are not yet invoked)_
* - `deleteUser`
* - `unlinkAccount`
*
* [Adapters Overview](https://next-auth.js.org/adapters/overview) |
* [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
*/
export interface Adapter {
createUser?:
| FutureAdapter["createUser"]
| ((user: Omit<AdapterUser, "id">) => Awaitable<AdapterUser>)
getUser?: (id: string) => Awaitable<AdapterUser | null>
getUserByEmail?: (email: string) => Awaitable<AdapterUser | null>
/** Using the provider id and the id of the user for a specific account, get the user. */
getUserByAccount?: (
providerAccountId: Pick<AdapterAccount, "provider" | "providerAccountId">
) => Awaitable<AdapterUser | null>
updateUser?: (
user: Partial<AdapterUser> & Pick<AdapterUser, "id">
) => Awaitable<AdapterUser>
/** @todo Implement */
deleteUser?: (
userId: string
) => Promise<void> | Awaitable<AdapterUser | null | undefined>
linkAccount?:
| FutureAdapter["linkAccount"]
| ((
account: AdapterAccount,
) => Promise<void> | Awaitable<AdapterAccount | null | undefined>)
/** @todo Implement */
unlinkAccount?:
| FutureAdapter["unlinkAccount"]
| ((
providerAccountId: Pick<
AdapterAccount,
"provider" | "providerAccountId"
>,
) => Promise<void> | Awaitable<AdapterAccount | undefined>)
/** Creates a session for the user and returns it. */
createSession?: (session: {
sessionToken: string
userId: string
expires: Date
}) => Awaitable<AdapterSession>
getSessionAndUser?: (
sessionToken: string
) => Awaitable<{ session: AdapterSession; user: AdapterUser } | null>
updateSession?: (
session: Partial<AdapterSession> & Pick<AdapterSession, "sessionToken">
) => Awaitable<AdapterSession | null | undefined>
/**
* Deletes a session from the database.
* It is preferred that this method also returns the session
* that is being deleted for logging purposes.
*/
deleteSession?: (
sessionToken: string
) => Promise<void> | Awaitable<AdapterSession | null | undefined>
createVerificationToken?: (
verificationToken: VerificationToken
) => Awaitable<VerificationToken | null | undefined>
/**
* Return verification token from the database
* and delete it so it cannot be used again.
*/
useVerificationToken?: (params: {
identifier: string
token: string
}) => Awaitable<VerificationToken | null>
}

View File

@@ -0,0 +1,188 @@
import { rest } from "msw"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockSession } from "./helpers/mocks"
import { printFetchCalls } from "./helpers/utils"
import { SessionProvider, useSession, signOut, getSession } from "../../react"
const origDocumentVisibility = document.visibilityState
const fetchSpy = jest.spyOn(global, "fetch")
beforeAll(() => {
server.listen()
})
afterEach(() => {
server.resetHandlers()
changeTabVisibility(origDocumentVisibility)
fetchSpy.mockClear()
})
afterAll(() => {
server.close()
})
test("fetches the session once and re-uses it for different consumers", async () => {
render(<ProviderFlow />)
expect(screen.getByTestId("session-1")).toHaveTextContent("loading")
expect(screen.getByTestId("session-2")).toHaveTextContent("loading")
return waitFor(() => {
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(fetchSpy).toHaveBeenCalledWith(
"/api/auth/session",
expect.anything()
)
const session1 = screen.getByTestId("session-1").textContent
const session2 = screen.getByTestId("session-2").textContent
expect(session1).toEqual(session2)
})
})
test("when there's an existing session, it won't try to fetch a new one straightaway", async () => {
render(<ProviderFlow session={mockSession} />)
expect(fetchSpy).not.toHaveBeenCalled()
})
test("will refetch the session when the browser tab becomes active again", async () => {
render(<ProviderFlow session={mockSession} />)
expect(fetchSpy).not.toHaveBeenCalled()
// Hide the current tab
changeTabVisibility("hidden")
// Given the current tab got hidden, it should not attempt to re-fetch the session
expect(fetchSpy).not.toHaveBeenCalled()
// Make the tab again visible
changeTabVisibility("visible")
// Given the user made the tab visible again, now attempts to sync and re-fetch the session
return waitFor(() => {
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(fetchSpy).toHaveBeenCalledWith(
"/api/auth/session",
expect.anything()
)
})
})
test("will refetch the session if told to do so programmatically from another window", async () => {
render(<ProviderFlow session={mockSession} />)
expect(fetchSpy).not.toHaveBeenCalled()
// Hide the current tab
changeTabVisibility("hidden")
// Given the current tab got hidden, it should not attempt to re-fetch the session
expect(fetchSpy).not.toHaveBeenCalled()
// simulate sign-out triggered by another tab
signOut({ redirect: false })
// Given signed out in another tab, it attempts to sync and re-fetch the session
return waitFor(() => {
expect(fetchSpy).toHaveBeenCalledWith(
"/api/auth/session",
expect.anything()
)
// We should have a call to sign-out and a call to refetch the session accordingly
expect(printFetchCalls(fetchSpy.mock.calls)).toMatchInlineSnapshot(`
Array [
"GET /api/auth/csrf",
"POST /api/auth/signout",
"GET /api/auth/session",
]
`)
})
})
test("allows to customize how often the session will be re-fetched through polling", () => {
jest.useFakeTimers()
render(<ProviderFlow session={mockSession} refetchInterval={1} />)
// we provided a mock session so it shouldn't try to fetch a new one
expect(fetchSpy).not.toHaveBeenCalled()
jest.advanceTimersByTime(1000)
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(fetchSpy).toHaveBeenCalledWith("/api/auth/session", expect.anything())
jest.advanceTimersByTime(1000)
// it should have tried to refetch the session, hence counting 2 calls to the session endpoint
expect(fetchSpy).toHaveBeenCalledTimes(2)
expect(printFetchCalls(fetchSpy.mock.calls)).toMatchInlineSnapshot(`
Array [
"GET /api/auth/session",
"GET /api/auth/session",
]
`)
})
test("allows to customize the URL for session fetching", async () => {
const myPath = "/api/v1/auth"
server.use(
rest.get(`${myPath}/session`, (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockSession))
)
)
render(<ProviderFlow session={mockSession} basePath={myPath} />)
// there's an existing session so it should not try to fetch a new one
expect(fetchSpy).not.toHaveBeenCalled()
// force a session refetch across all clients...
getSession()
return waitFor(() => {
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(fetchSpy).toHaveBeenCalledWith(
`${myPath}/session`,
expect.anything()
)
})
})
function ProviderFlow(props) {
return (
<SessionProvider {...props}>
<SessionConsumer />
<SessionConsumer testId="2" />
</SessionProvider>
)
}
function SessionConsumer({ testId = 1, ...rest }) {
const { data: session, status } = useSession(rest)
return (
<div data-testid={`session-${testId}`}>
{status === "loading" ? "loading" : JSON.stringify(session)}
</div>
)
}
function changeTabVisibility(status) {
const visibleStates = ["visible", "hidden"]
if (!visibleStates.includes(status)) return
Object.defineProperty(document, "visibilityState", {
configurable: true,
value: status,
})
document.dispatchEvent(new Event("visibilitychange"))
}

View File

@@ -0,0 +1,104 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockCSRFToken } from "./helpers/mocks"
import logger from "../../utils/logger"
import { getCsrfToken } from "../../react"
import { rest } from "msw"
jest.mock("../../utils/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))
beforeAll(() => {
server.listen()
})
afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
})
afterAll(() => {
server.close()
})
test("returns the Cross Site Request Forgery Token (CSRF Token) required to make POST requests", async () => {
render(<CSRFFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(screen.getByTestId("csrf-result").textContent).toEqual(
mockCSRFToken.csrfToken
)
})
})
test("when there's no CSRF token returned, it'll reflect that", async () => {
server.use(
rest.get("*/api/auth/csrf", (req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
...mockCSRFToken,
csrfToken: null,
})
)
)
)
render(<CSRFFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(screen.getByTestId("csrf-result").textContent).toBe("null-response")
})
})
test("when the fetch fails it'll throw a client fetch error", async () => {
server.use(
rest.get("*/api/auth/csrf", (req, res, ctx) =>
res(ctx.status(500), ctx.text("some error happened"))
)
)
render(<CSRFFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
url: "/api/auth/csrf",
error: new SyntaxError("Unexpected token s in JSON at position 0"),
})
})
})
function CSRFFlow() {
const [response, setResponse] = useState()
async function handleCSRF() {
const result = await getCsrfToken()
setResponse(result)
}
return (
<>
<p data-testid="csrf-result">
{response === null ? "null-response" : response || "no response"}
</p>
<button onClick={handleCSRF}>Get CSRF</button>
</>
)
}

View File

@@ -0,0 +1,90 @@
import { setupServer } from "msw/node"
import { rest } from "msw"
import { randomBytes } from "crypto"
export const mockSession = {
ok: true,
user: {
image: null,
name: "John",
email: "john@email.com",
},
expires: 123213139,
}
export const mockProviders = {
ok: true,
github: {
id: "github",
name: "Github",
type: "oauth",
signinUrl: "path/to/signin",
callbackUrl: "path/to/callback",
},
credentials: {
id: "credentials",
name: "Credentials",
type: "credentials",
authorize: null,
credentials: null,
},
email: {
id: "email",
type: "email",
name: "Email",
},
}
export const mockCSRFToken = {
ok: true,
csrfToken: randomBytes(32).toString("hex"),
}
export const mockGithubResponse = {
ok: true,
status: 200,
url: "https://path/to/github/url",
}
export const mockCredentialsResponse = {
ok: true,
status: 200,
url: "https://path/to/credentials/url",
}
export const mockEmailResponse = {
ok: true,
status: 200,
url: "https://path/to/email/url",
}
export const mockSignOutResponse = {
ok: true,
status: 200,
url: "https://path/to/signout/url",
}
export const server = setupServer(
rest.post("*/api/auth/signout", (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockSignOutResponse))
),
rest.get("*/api/auth/session", (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockSession))
),
rest.get("*/api/auth/csrf", (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockCSRFToken))
),
rest.get("*/api/auth/providers", (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockProviders))
),
rest.post("*/api/auth/signin/github", (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockGithubResponse))
),
rest.post("*/api/auth/callback/credentials", (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockCredentialsResponse))
),
rest.post("*/api/auth/signin/email", (req, res, ctx) =>
res(ctx.status(200), ctx.json(mockEmailResponse))
),
rest.post("*/api/auth/_log", (req, res, ctx) => res(ctx.status(200)))
)

View File

@@ -0,0 +1,14 @@
export function getBroadcastEvents() {
return window.localStorage.setItem.mock.calls
.filter((call) => call[0] === "nextauth.message")
.map(([eventName, value]) => {
const { timestamp, ...rest } = JSON.parse(value)
return { eventName, value: rest }
})
}
export function printFetchCalls(mockCalls) {
return mockCalls.map(([path, { method = "GET" }]) => {
return `${method.toUpperCase()} ${path}`
})
}

View File

@@ -0,0 +1,84 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockProviders } from "./helpers/mocks"
import { getProviders } from "../../react"
import logger from "../../utils/logger"
import { rest } from "msw"
jest.mock("../../utils/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))
beforeAll(() => {
server.listen()
})
afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
})
afterAll(() => {
server.close()
})
test("when called it'll return the currently configured providers for sign in", async () => {
render(<ProvidersFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(screen.getByTestId("providers-result").textContent).toEqual(
JSON.stringify(mockProviders)
)
})
})
test("when failing to fetch the providers, it'll log the error", async () => {
server.use(
rest.get("*/api/auth/providers", (req, res, ctx) =>
res(ctx.status(500), ctx.text("some error happened"))
)
)
render(<ProvidersFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
url: "/api/auth/providers",
error: new SyntaxError("Unexpected token s in JSON at position 0"),
})
})
})
function ProvidersFlow() {
const [response, setResponse] = useState()
async function handleGerProviders() {
const result = await getProviders()
setResponse(result)
}
return (
<>
<p data-testid="providers-result">
{response === null
? "null-response"
: JSON.stringify(response) || "no response"}
</p>
<button onClick={handleGerProviders}>Get Providers</button>
</>
)
}

View File

@@ -0,0 +1,97 @@
import { render, screen, waitFor } from "@testing-library/react"
import { rest } from "msw"
import { server, mockSession } from "./helpers/mocks"
import logger from "../../utils/logger"
import { useState, useEffect } from "react"
import { getSession } from "../../react"
import { getBroadcastEvents } from "./helpers/utils"
jest.mock("../../utils/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))
beforeAll(() => server.listen())
beforeEach(() => {
// eslint-disable-next-line no-proto
jest.spyOn(window.localStorage.__proto__, "setItem")
})
afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
})
afterAll(() => {
server.close()
})
test("if it can fetch the session, it should store it in `localStorage`", async () => {
render(<SessionFlow />)
// In the start, there is no session
const noSession = await screen.findByText("No session")
expect(noSession).toBeInTheDocument()
// After we fetched the session, it should have been rendered by `<SessionFlow />`
const session = await screen.findByText(new RegExp(mockSession.user.name))
expect(session).toBeInTheDocument()
const broadcastCalls = getBroadcastEvents()
const [broadcastedEvent] = broadcastCalls
expect(broadcastCalls).toHaveLength(1)
expect(broadcastCalls).toHaveLength(1)
expect(broadcastedEvent.eventName).toBe("nextauth.message")
expect(broadcastedEvent.value).toStrictEqual({
data: {
trigger: "getSession",
},
event: "session",
})
})
test("if there's an error fetching the session, it should log it", async () => {
server.use(
rest.get("*/api/auth/session", (req, res, ctx) => {
return res(ctx.status(500), ctx.body("Server error"))
})
)
render(<SessionFlow />)
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
url: "/api/auth/session",
error: new SyntaxError("Unexpected token S in JSON at position 0"),
})
})
})
function SessionFlow() {
const [session, setSession] = useState(null)
useEffect(() => {
async function fetchUserSession() {
try {
const result = await getSession()
setSession(result)
} catch (e) {
console.error(e)
}
}
fetchUserSession()
}, [])
if (session) return <pre>{JSON.stringify(session, null, 2)}</pre>
return <p>No session</p>
}

View File

@@ -0,0 +1,290 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import logger from "../../utils/logger"
import {
server,
mockCredentialsResponse,
mockEmailResponse,
mockGithubResponse,
} from "./helpers/mocks"
import { signIn } from "../../react"
import { rest } from "msw"
const { location } = window
jest.mock("../../utils/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))
beforeAll(() => {
server.listen()
let _href = window.location.href
// Allows to mutate `window.location`...
delete window.location
window.location = {
reload: jest.fn(),
}
Object.defineProperty(window.location, "href", {
get: () => _href,
// whatwg-fetch or whatwg-url does not seem to work with relative URLs
set: (href) => {
_href = href.startsWith("/") ? `http://localhost${href}` : href
return _href
},
})
})
beforeEach(() => {
jest.clearAllMocks()
server.resetHandlers()
})
afterAll(() => {
window.location = location
server.close()
})
const callbackUrl = "https://redirects/to"
test.each`
provider | type
${""} | ${"no"}
${"foo"} | ${"unknown"}
`(
"if $type provider, it redirects to the default sign-in page",
async ({ provider }) => {
render(<SignInFlow providerId={provider} callbackUrl={callbackUrl} />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(
`http://localhost/api/auth/signin?${new URLSearchParams({
callbackUrl,
})}`
)
})
}
)
test.each`
provider | type
${""} | ${"no"}
${"foo"} | ${"unknown"}
`(
"if $type provider supplied and no callback URL, redirects using the current location",
async ({ provider }) => {
render(<SignInFlow providerId={provider} />)
const callbackUrl = window.location.href
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(
`http://localhost/api/auth/signin?${new URLSearchParams({
callbackUrl,
})}`
)
})
}
)
test.each`
provider | mockUrl
${`email`} | ${mockEmailResponse.url}
${`credentials`} | ${mockCredentialsResponse.url}
`(
"$provider provider redirects if `redirect` is `true`",
async ({ provider, mockUrl }) => {
render(<SignInFlow providerId={provider} redirect={true} />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(mockUrl)
})
}
)
test("redirection can't be stopped using an oauth provider", async () => {
render(
<SignInFlow
providerId="github"
callbackUrl={callbackUrl}
redirect={false}
/>
)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(mockGithubResponse.url)
})
})
test("redirection can be stopped using the 'credentials' provider", async () => {
render(
<SignInFlow
providerId="credentials"
callbackUrl={callbackUrl}
redirect={false}
/>
)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).not.toBe(mockCredentialsResponse.url)
expect(screen.getByTestId("signin-result").textContent).not.toBe(
"no response"
)
})
// snapshot the expected return shape from `signIn`
expect(JSON.parse(screen.getByTestId("signin-result").textContent))
.toMatchInlineSnapshot(`
Object {
"error": null,
"ok": true,
"status": 200,
"url": "https://path/to/credentials/url",
}
`)
})
test("redirection can be stopped using the 'email' provider", async () => {
render(
<SignInFlow providerId="email" callbackUrl={callbackUrl} redirect={false} />
)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).not.toBe(mockEmailResponse.url)
expect(screen.getByTestId("signin-result").textContent).not.toBe(
"no response"
)
})
// snapshot the expected return shape from `signIn` oauth
expect(JSON.parse(screen.getByTestId("signin-result").textContent))
.toMatchInlineSnapshot(`
Object {
"error": null,
"ok": true,
"status": 200,
"url": "https://path/to/email/url",
}
`)
})
test("if callback URL contains a hash we force a window reload when re-directing", async () => {
const mockUrlWithHash = "https://path/to/email/url#foo-bar-baz"
server.use(
rest.post("*/api/auth/signin/email", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
...mockEmailResponse,
url: mockUrlWithHash,
})
)
})
)
render(<SignInFlow providerId="email" callbackUrl={mockUrlWithHash} />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(mockUrlWithHash)
// the browser will not refresh the page if the redirect URL contains a hash, hence we force it on the client, see #1289
expect(window.location.reload).toHaveBeenCalledTimes(1)
})
})
test("params are propagated to the signin URL when supplied", async () => {
let matchedParams = ""
const authParams = "foo=bar&bar=foo"
server.use(
rest.post("*/auth/signin/github", (req, res, ctx) => {
matchedParams = req.url.search
return res(ctx.status(200), ctx.json(mockGithubResponse))
})
)
render(<SignInFlow providerId="github" authorizationParams={authParams} />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(matchedParams).toEqual(`?${authParams}`)
})
})
test("when it fails to fetch the providers, it redirected back to signin page", async () => {
const errorMsg = "Error when retrieving providers"
server.use(
rest.get("*/api/auth/providers", (req, res, ctx) =>
res(ctx.status(500), ctx.json(errorMsg))
)
)
render(<SignInFlow providerId="github" />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(`http://localhost/api/auth/error`)
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
error: "Error when retrieving providers",
url: "/api/auth/providers",
})
})
})
function SignInFlow({
providerId,
callbackUrl,
redirect = true,
authorizationParams = {},
}) {
const [response, setResponse] = useState(null)
async function handleSignIn() {
const result = await signIn(
providerId,
{ callbackUrl, redirect },
authorizationParams
)
setResponse(result)
}
return (
<>
<p data-testid="signin-result">
{response ? JSON.stringify(response) : "no response"}
</p>
<button onClick={handleSignIn}>Sign in</button>
</>
)
}

View File

@@ -0,0 +1,124 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockSignOutResponse } from "./helpers/mocks"
import { signOut } from "../../react"
import { rest } from "msw"
import { getBroadcastEvents } from "./helpers/utils"
const { location } = window
beforeAll(() => {
server.listen()
// Allows to mutate `window.location`...
delete window.location
window.location = {
reload: jest.fn(),
href: location.href,
}
})
beforeEach(() => {
// eslint-disable-next-line no-proto
jest.spyOn(window.localStorage.__proto__, "setItem")
})
afterEach(() => {
jest.clearAllMocks()
server.resetHandlers()
})
afterAll(() => {
window.location = location
server.close()
})
const callbackUrl = "https://redirects/to"
test("by default it redirects to the current URL if the server did not provide one", async () => {
server.use(
rest.post("*/api/auth/signout", (req, res, ctx) =>
res(ctx.status(200), ctx.json({ ...mockSignOutResponse, url: undefined }))
)
)
render(<SignOutFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(window.location.href)
})
})
test("it redirects to the URL allowed by the server", async () => {
render(<SignOutFlow callbackUrl={callbackUrl} />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(mockSignOutResponse.url)
})
})
test("if url contains a hash during redirection a page reload happens", async () => {
const mockUrlWithHash = "https://path/to/email/url#foo-bar-baz"
server.use(
rest.post("*/api/auth/signout", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
...mockSignOutResponse,
url: mockUrlWithHash,
})
)
})
)
render(<SignOutFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(window.location.href).toBe(mockUrlWithHash)
})
})
test("will broadcast the signout event to other tabs", async () => {
render(<SignOutFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
const broadcastCalls = getBroadcastEvents()
const [broadcastedEvent] = broadcastCalls
expect(broadcastCalls).toHaveLength(1)
expect(broadcastedEvent.eventName).toBe("nextauth.message")
expect(broadcastedEvent.value).toStrictEqual({
data: {
trigger: "signout",
},
event: "session",
})
})
})
function SignOutFlow({ callbackUrl, redirect = true }) {
const [response, setResponse] = useState(null)
async function handleSignOut() {
const result = await signOut({ callbackUrl, redirect })
setResponse(result)
}
return (
<>
<p data-testid="signout-result">
{response ? JSON.stringify(response) : "no response"}
</p>
<button onClick={handleSignOut}>Sign out</button>
</>
)
}

View File

@@ -0,0 +1,140 @@
import { rest } from "msw"
import { renderHook } from "@testing-library/react-hooks"
import { render, waitFor } from "@testing-library/react"
import { SessionProvider, useSession, signOut } from "../../react"
import { server, mockSession } from "./helpers/mocks"
const origConsoleError = console.error
const { location } = window
let _href = window.location.href
beforeAll(() => {
// Prevent noise on the terminal... `next-auth` will log to `console.error`
// every time a request fails, which makes the tests output very noisy...
console.error = jest.fn()
// Allows to mutate `window.location`...
delete window.location
window.location = {}
Object.defineProperty(window.location, "href", {
get: () => _href,
// whatwg-fetch or whatwg-url does not seem to work with relative URLs
set: (href) => {
_href = href.startsWith("/") ? `http://localhost${href}` : href
return _href
},
})
server.listen()
})
afterEach(() => {
server.resetHandlers()
_href = "http://localhost/"
// clear the internal session cache...
signOut({ redirect: false })
})
afterAll(() => {
console.error = origConsoleError
window.location = location
server.close()
})
test("it won't allow to fetch the session in isolation without a session context", () => {
function App() {
useSession()
return null
}
expect(() => render(<App />)).toThrow(
"[next-auth]: `useSession` must be wrapped in a <SessionProvider />"
)
})
test("when fetching the session, there won't be `data` and `status` will be 'loading'", () => {
const { result } = renderHook(() => useSession(), {
wrapper: SessionProvider,
})
expect(result.current.data).toBe(undefined)
expect(result.current.status).toBe("loading")
})
test("when session is fetched, `data` will contain the session data and `status` will be 'authenticated'", async () => {
const { result } = renderHook(() => useSession(), {
wrapper: SessionProvider,
})
await waitFor(() => {
expect(result.current.data).toEqual(mockSession)
expect(result.current.status).toBe("authenticated")
})
})
test("when it fails to fetch the session, `data` will be null and `status` will be 'unauthenticated'", async () => {
server.use(
rest.get(`http://localhost/api/auth/session`, (_, res, ctx) =>
res(ctx.status(401), ctx.json({}))
)
)
const { result } = renderHook(() => useSession(), {
wrapper: SessionProvider,
})
return waitFor(() => {
expect(result.current.data).toEqual(null)
expect(result.current.status).toBe("unauthenticated")
})
})
test("it'll redirect to sign-in page if the session is required and the user is not authenticated", async () => {
server.use(
rest.get(`http://localhost/api/auth/session`, (req, res, ctx) =>
res(ctx.status(401), ctx.json({}))
)
)
const callbackUrl = window.location.href
const { result } = renderHook(() => useSession({ required: true }), {
wrapper: SessionProvider,
})
await waitFor(() => {
expect(result.current.data).toEqual(null)
expect(result.current.status).toBe("loading")
})
expect(window.location.href).toBe(
`http://localhost/api/auth/signin?${new URLSearchParams({
error: "SessionRequired",
callbackUrl,
})}`
)
})
test("will call custom redirect logic if supplied when the user could not authenticate", async () => {
server.use(
rest.get(`http://localhost/api/auth/session`, (_, res, ctx) =>
res(ctx.status(401), ctx.json({}))
)
)
const customRedirect = jest.fn()
const { result } = renderHook(
() => useSession({ required: true, onUnauthenticated: customRedirect }),
{
wrapper: SessionProvider,
}
)
await waitFor(() => {
expect(result.current.data).toEqual(null)
expect(result.current.status).toBe("loading")
})
expect(customRedirect).toHaveBeenCalledTimes(1)
})

120
node_modules/next-auth/src/client/_utils.ts generated vendored Normal file
View File

@@ -0,0 +1,120 @@
import type { IncomingMessage } from "http"
import type { LoggerInstance, Session } from ".."
export interface AuthClientConfig {
baseUrl: string
basePath: string
baseUrlServer: string
basePathServer: string
/** Stores last session response */
_session?: Session | null | undefined
/** Used for timestamp since last sycned (in seconds) */
_lastSync: number
/**
* Stores the `SessionProvider`'s session update method to be able to
* trigger session updates from places like `signIn` or `signOut`
*/
_getSession: (...args: any[]) => any
}
export interface CtxOrReq {
req?: Partial<IncomingMessage> & { body?: any }
ctx?: { req: Partial<IncomingMessage> & { body?: any } }
}
/**
* If passed 'appContext' via getInitialProps() in _app.js
* then get the req object from ctx and use that for the
* req value to allow `fetchData` to
* work seemlessly in getInitialProps() on server side
* pages *and* in _app.js.
*/
export async function fetchData<T = any>(
path: string,
__NEXTAUTH: AuthClientConfig,
logger: LoggerInstance,
{ ctx, req = ctx?.req }: CtxOrReq = {}
): Promise<T | null> {
const url = `${apiBaseUrl(__NEXTAUTH)}/${path}`
try {
const options: RequestInit = {
headers: {
"Content-Type": "application/json",
...(req?.headers?.cookie ? { cookie: req.headers.cookie } : {}),
},
}
if (req?.body) {
options.body = JSON.stringify(req.body)
options.method = "POST"
}
const res = await fetch(url, options)
const data = await res.json()
if (!res.ok) throw data
return Object.keys(data).length > 0 ? data : null // Return null if data empty
} catch (error) {
logger.error("CLIENT_FETCH_ERROR", { error: error as Error, url })
return null
}
}
export function apiBaseUrl(__NEXTAUTH: AuthClientConfig) {
if (typeof window === "undefined") {
// Return absolute path when called server side
return `${__NEXTAUTH.baseUrlServer}${__NEXTAUTH.basePathServer}`
}
// Return relative path when called client side
return __NEXTAUTH.basePath
}
/** Returns the number of seconds elapsed since January 1, 1970 00:00:00 UTC. */
export function now() {
return Math.floor(Date.now() / 1000)
}
export interface BroadcastMessage {
event?: "session"
data?: { trigger?: "signout" | "getSession" }
clientId: string
timestamp: number
}
/**
* Inspired by [Broadcast Channel API](https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API)
* Only not using it directly, because Safari does not support it.
*
* https://caniuse.com/?search=broadcastchannel
*/
export function BroadcastChannel(name = "nextauth.message") {
return {
/** Get notified by other tabs/windows. */
receive(onReceive: (message: BroadcastMessage) => void) {
const handler = (event: StorageEvent) => {
if (event.key !== name) return
const message: BroadcastMessage = JSON.parse(event.newValue ?? "{}")
if (message?.event !== "session" || !message?.data) return
onReceive(message)
}
window.addEventListener("storage", handler)
return () => window.removeEventListener("storage", handler)
},
/** Notify other tabs/windows. */
post(message: Record<string, unknown>) {
if (typeof window === "undefined") return
try {
localStorage.setItem(
name,
JSON.stringify({ ...message, timestamp: now() })
)
} catch {
/**
* The localStorage API isn't always available.
* It won't work in private mode prior to Safari 11 for example.
* Notifications are simply dropped if an error is encountered.
*/
}
},
}
}

127
node_modules/next-auth/src/core/errors.ts generated vendored Normal file
View File

@@ -0,0 +1,127 @@
import type { EventCallbacks, InternalOptions, LoggerInstance } from ".."
/**
* Same as the default `Error`, but it is JSON serializable.
* @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
*/
export class UnknownError extends Error {
code: string
constructor(error: Error | string) {
// Support passing error or string
super((error as Error)?.message ?? error)
this.name = "UnknownError"
this.code = (error as any).code
if (error instanceof Error) {
this.stack = error.stack
}
}
toJSON() {
return {
name: this.name,
message: this.message,
stack: this.stack,
}
}
}
export class OAuthCallbackError extends UnknownError {
name = "OAuthCallbackError"
}
/**
* Thrown when an Email address is already associated with an account
* but the user is trying an OAuth account that is not linked to it.
*/
export class AccountNotLinkedError extends UnknownError {
name = "AccountNotLinkedError"
}
export class MissingAPIRoute extends UnknownError {
name = "MissingAPIRouteError"
code = "MISSING_NEXTAUTH_API_ROUTE_ERROR"
}
export class MissingSecret extends UnknownError {
name = "MissingSecretError"
code = "NO_SECRET"
}
export class MissingAuthorize extends UnknownError {
name = "MissingAuthorizeError"
code = "CALLBACK_CREDENTIALS_HANDLER_ERROR"
}
export class MissingAdapter extends UnknownError {
name = "MissingAdapterError"
code = "EMAIL_REQUIRES_ADAPTER_ERROR"
}
export class MissingAdapterMethods extends UnknownError {
name = "MissingAdapterMethodsError"
code = "MISSING_ADAPTER_METHODS_ERROR"
}
export class UnsupportedStrategy extends UnknownError {
name = "UnsupportedStrategyError"
code = "CALLBACK_CREDENTIALS_JWT_ERROR"
}
export class InvalidCallbackUrl extends UnknownError {
name = "InvalidCallbackUrl"
code = "INVALID_CALLBACK_URL_ERROR"
}
type Method = (...args: any[]) => Promise<any>
export function upperSnake(s: string) {
return s.replace(/([A-Z])/g, "_$1").toUpperCase()
}
export function capitalize(s: string) {
return `${s[0].toUpperCase()}${s.slice(1)}`
}
/**
* Wraps an object of methods and adds error handling.
*/
export function eventsErrorHandler(
methods: Partial<EventCallbacks>,
logger: LoggerInstance
): Partial<EventCallbacks> {
return Object.keys(methods).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
const method: Method = methods[name as keyof Method]
return await method(...args)
} catch (e) {
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error)
}
}
return acc
}, {})
}
/** Handles adapter induced errors. */
export function adapterErrorHandler<TAdapter>(
adapter: TAdapter | undefined,
logger: LoggerInstance
): InternalOptions["adapter"] | undefined {
if (!adapter) return
return Object.keys(adapter).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
logger.debug(`adapter_${name}`, { args })
const method: Method = adapter[name as keyof Method]
return await method(...args)
} catch (error) {
logger.error(`adapter_error_${name}`, error as Error)
const e = new UnknownError(error as Error)
e.name = `${capitalize(name)}Error`
throw e
}
}
return acc
}, {})
}

336
node_modules/next-auth/src/core/index.ts generated vendored Normal file
View File

@@ -0,0 +1,336 @@
import logger, { setLogger } from "../utils/logger"
import { detectOrigin } from "../utils/detect-origin"
import * as routes from "./routes"
import renderPage from "./pages"
import { init } from "./init"
import { assertConfig } from "./lib/assert"
import { SessionStore } from "./lib/cookie"
import type { AuthAction, AuthOptions } from "./types"
import type { Cookie } from "./lib/cookie"
import type { ErrorType } from "./pages/error"
import { parse as parseCookie } from "cookie"
export interface RequestInternal {
/** @default "http://localhost:3000" */
origin?: string
method?: string
cookies?: Partial<Record<string, string>>
headers?: Record<string, any>
query?: Record<string, any>
body?: Record<string, any>
action: AuthAction
providerId?: string
error?: string
}
export interface NextAuthHeader {
key: string
value: string
}
export interface ResponseInternal<
Body extends string | Record<string, any> | any[] = any
> {
status?: number
headers?: NextAuthHeader[]
body?: Body
redirect?: string
cookies?: Cookie[]
}
export interface NextAuthHandlerParams {
req: Request | RequestInternal
options: AuthOptions
}
async function getBody(req: Request): Promise<Record<string, any> | undefined> {
try {
return await req.json()
} catch {}
}
// TODO:
async function toInternalRequest(
req: RequestInternal | Request
): Promise<RequestInternal> {
if (req instanceof Request) {
const url = new URL(req.url)
// TODO: handle custom paths?
const nextauth = url.pathname.split("/").slice(3)
const headers = Object.fromEntries(req.headers)
const query: Record<string, any> = Object.fromEntries(url.searchParams)
query.nextauth = nextauth
return {
action: nextauth[0] as AuthAction,
method: req.method,
headers,
body: await getBody(req),
cookies: parseCookie(req.headers.get("cookie") ?? ""),
providerId: nextauth[1],
error: url.searchParams.get("error") ?? nextauth[1],
origin: detectOrigin(
headers["x-forwarded-host"] ?? headers.host,
headers["x-forwarded-proto"]
),
query,
}
}
const { headers } = req
const host = headers?.["x-forwarded-host"] ?? headers?.host
req.origin = detectOrigin(host, headers?.["x-forwarded-proto"])
return req
}
export async function AuthHandler<
Body extends string | Record<string, any> | any[]
>(params: NextAuthHandlerParams): Promise<ResponseInternal<Body>> {
const { options: authOptions, req: incomingRequest } = params
const req = await toInternalRequest(incomingRequest)
setLogger(authOptions.logger, authOptions.debug)
const assertionResult = assertConfig({ options: authOptions, req })
if (Array.isArray(assertionResult)) {
assertionResult.forEach(logger.warn)
} else if (assertionResult instanceof Error) {
// Bail out early if there's an error in the user config
logger.error(assertionResult.code, assertionResult)
const htmlPages = ["signin", "signout", "error", "verify-request"]
if (!htmlPages.includes(req.action) || req.method !== "GET") {
const message = `There is a problem with the server configuration. Check the server logs for more information.`
return {
status: 500,
headers: [{ key: "Content-Type", value: "application/json" }],
body: { message } as any,
}
}
const { pages, theme } = authOptions
const authOnErrorPage =
pages?.error && req.query?.callbackUrl?.startsWith(pages.error)
if (!pages?.error || authOnErrorPage) {
if (authOnErrorPage) {
logger.error(
"AUTH_ON_ERROR_PAGE_ERROR",
new Error(
`The error page ${pages?.error} should not require authentication`
)
)
}
const render = renderPage({ theme })
return render.error({ error: "configuration" })
}
return {
redirect: `${pages.error}?error=Configuration`,
}
}
const { action, providerId, error, method = "GET" } = req
const { options, cookies } = await init({
authOptions,
action,
providerId,
origin: req.origin,
callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl,
csrfToken: req.body?.csrfToken,
cookies: req.cookies,
isPost: method === "POST",
})
const sessionStore = new SessionStore(
options.cookies.sessionToken,
req,
options.logger
)
if (method === "GET") {
const render = renderPage({ ...options, query: req.query, cookies })
const { pages } = options
switch (action) {
case "providers":
return (await routes.providers(options.providers)) as any
case "session": {
const session = await routes.session({ options, sessionStore })
if (session.cookies) cookies.push(...session.cookies)
return { ...session, cookies } as any
}
case "csrf":
return {
headers: [
{ key: "Content-Type", value: "application/json" },
{
key: "Cache-Control",
value: "private, no-cache, no-store",
},
{
key: "Pragma",
value: "no-cache",
},
{
key: "Expires",
value: "0",
},
],
body: { csrfToken: options.csrfToken } as any,
cookies,
}
case "signin":
if (pages.signIn) {
let signinUrl = `${pages.signIn}${
pages.signIn.includes("?") ? "&" : "?"
}callbackUrl=${encodeURIComponent(options.callbackUrl)}`
if (error)
signinUrl = `${signinUrl}&error=${encodeURIComponent(error)}`
return { redirect: signinUrl, cookies }
}
return render.signin()
case "signout":
if (pages.signOut) return { redirect: pages.signOut, cookies }
return render.signout()
case "callback":
if (options.provider) {
const callback = await routes.callback({
body: req.body,
query: req.query,
headers: req.headers,
cookies: req.cookies,
method,
options,
sessionStore,
})
if (callback.cookies) cookies.push(...callback.cookies)
return { ...callback, cookies }
}
break
case "verify-request":
if (pages.verifyRequest) {
return { redirect: pages.verifyRequest, cookies }
}
return render.verifyRequest()
case "error":
// These error messages are displayed in line on the sign in page
if (
[
"Signin",
"OAuthSignin",
"OAuthCallback",
"OAuthCreateAccount",
"EmailCreateAccount",
"Callback",
"OAuthAccountNotLinked",
"EmailSignin",
"CredentialsSignin",
"SessionRequired",
].includes(error as string)
) {
return { redirect: `${options.url}/signin?error=${error}`, cookies }
}
if (pages.error) {
return {
redirect: `${pages.error}${
pages.error.includes("?") ? "&" : "?"
}error=${error}`,
cookies,
}
}
return render.error({ error: error as ErrorType })
default:
}
} else if (method === "POST") {
switch (action) {
case "signin":
// Verified CSRF Token required for all sign-in routes
if (options.csrfTokenVerified && options.provider) {
const signin = await routes.signin({
query: req.query,
body: req.body,
options,
})
if (signin.cookies) cookies.push(...signin.cookies)
return { ...signin, cookies }
}
return { redirect: `${options.url}/signin?csrf=true`, cookies }
case "signout":
// Verified CSRF Token required for signout
if (options.csrfTokenVerified) {
const signout = await routes.signout({ options, sessionStore })
if (signout.cookies) cookies.push(...signout.cookies)
return { ...signout, cookies }
}
return { redirect: `${options.url}/signout?csrf=true`, cookies }
case "callback":
if (options.provider) {
// Verified CSRF Token required for credentials providers only
if (
options.provider.type === "credentials" &&
!options.csrfTokenVerified
) {
return { redirect: `${options.url}/signin?csrf=true`, cookies }
}
const callback = await routes.callback({
body: req.body,
query: req.query,
headers: req.headers,
cookies: req.cookies,
method,
options,
sessionStore,
})
if (callback.cookies) cookies.push(...callback.cookies)
return { ...callback, cookies }
}
break
case "_log": {
if (authOptions.logger) {
try {
const { code, level, ...metadata } = req.body ?? {}
logger[level](code, metadata)
} catch (error) {
// If logging itself failed...
logger.error("LOGGER_ERROR", error as Error)
}
}
return {}
}
case "session": {
// Verified CSRF Token required for session updates
if (options.csrfTokenVerified) {
const session = await routes.session({
options,
sessionStore,
newSession: req.body?.data,
isUpdate: true,
})
if (session.cookies) cookies.push(...session.cookies)
return { ...session, cookies } as any
}
// If CSRF token is invalid, return a 400 status code
// we should not redirect to a page as this is an API route
return { status: 400, body: {} as any, cookies }
}
default:
}
}
return {
status: 400,
body: `Error: This action with HTTP ${method} is not supported by NextAuth.js` as any,
}
}

155
node_modules/next-auth/src/core/init.ts generated vendored Normal file
View File

@@ -0,0 +1,155 @@
import { randomBytes, randomUUID } from "crypto"
import { AuthOptions } from ".."
import logger from "../utils/logger"
import { adapterErrorHandler, eventsErrorHandler } from "./errors"
import parseProviders from "./lib/providers"
import { createSecret } from "./lib/utils"
import * as cookie from "./lib/cookie"
import * as jwt from "../jwt"
import { defaultCallbacks } from "./lib/default-callbacks"
import { createCSRFToken } from "./lib/csrf-token"
import { createCallbackUrl } from "./lib/callback-url"
import { RequestInternal } from "."
import type { InternalOptions } from "./types"
import parseUrl from "../utils/parse-url"
interface InitParams {
origin?: string
authOptions: AuthOptions
providerId?: string
action: InternalOptions["action"]
/** Callback URL value extracted from the incoming request. */
callbackUrl?: string
/** CSRF token value extracted from the incoming request. From body if POST, from query if GET */
csrfToken?: string
/** Is the incoming request a POST request? */
isPost: boolean
cookies: RequestInternal["cookies"]
}
/** Initialize all internal options and cookies. */
export async function init({
authOptions,
providerId,
action,
origin,
cookies: reqCookies,
callbackUrl: reqCallbackUrl,
csrfToken: reqCsrfToken,
isPost,
}: InitParams): Promise<{
options: InternalOptions
cookies: cookie.Cookie[]
}> {
const url = parseUrl(origin)
const secret = createSecret({ authOptions, url })
const { providers, provider } = parseProviders({
providers: authOptions.providers,
url,
providerId,
})
const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle by default
// User provided options are overriden by other options,
// except for the options with special handling above
const options: InternalOptions = {
debug: false,
pages: {},
theme: {
colorScheme: "auto",
logo: "",
brandColor: "",
buttonText: "",
},
// Custom options override defaults
...authOptions,
// These computed settings can have values in authOptions but we override them
// and are request-specific.
url,
action,
// @ts-expect-errors
provider,
cookies: {
...cookie.defaultCookies(
authOptions.useSecureCookies ?? url.base.startsWith("https://")
),
// Allow user cookie options to override any cookie settings above
...authOptions.cookies,
},
secret,
providers,
// Session options
session: {
// If no adapter specified, force use of JSON Web Tokens (stateless)
strategy: authOptions.adapter ? "database" : "jwt",
maxAge,
updateAge: 24 * 60 * 60,
generateSessionToken: () => {
// Use `randomUUID` if available. (Node 15.6+)
return randomUUID?.() ?? randomBytes(32).toString("hex")
},
...authOptions.session,
},
// JWT options
jwt: {
secret, // Use application secret if no keys specified
maxAge, // same as session maxAge,
encode: jwt.encode,
decode: jwt.decode,
...authOptions.jwt,
},
// Event messages
events: eventsErrorHandler(authOptions.events ?? {}, logger),
adapter: adapterErrorHandler(authOptions.adapter, logger),
// Callback functions
callbacks: { ...defaultCallbacks, ...authOptions.callbacks },
logger,
callbackUrl: url.origin,
}
// Init cookies
const cookies: cookie.Cookie[] = []
const {
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
})
options.csrfToken = csrfToken
options.csrfTokenVerified = csrfTokenVerified
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
})
}
const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({
options,
cookieValue: reqCookies?.[options.cookies.callbackUrl.name],
paramValue: reqCallbackUrl,
})
options.callbackUrl = callbackUrl
if (callbackUrlCookie) {
cookies.push({
name: options.cookies.callbackUrl.name,
value: callbackUrlCookie,
options: options.cookies.callbackUrl.options,
})
}
return { options, cookies }
}

149
node_modules/next-auth/src/core/lib/assert.ts generated vendored Normal file
View File

@@ -0,0 +1,149 @@
import {
MissingAdapter,
MissingAPIRoute,
MissingAuthorize,
MissingSecret,
UnsupportedStrategy,
InvalidCallbackUrl,
MissingAdapterMethods,
} from "../errors"
import parseUrl from "../../utils/parse-url"
import { defaultCookies } from "./cookie"
import type { RequestInternal } from ".."
import type { WarningCode } from "../../utils/logger"
import type { AuthOptions } from "../types"
type ConfigError =
| MissingAPIRoute
| MissingSecret
| UnsupportedStrategy
| MissingAuthorize
| MissingAdapter
let warned = false
function isValidHttpUrl(url: string, baseUrl: string) {
try {
return /^https?:/.test(
new URL(url, url.startsWith("/") ? baseUrl : undefined).protocol
)
} catch {
return false
}
}
/**
* Verify that the user configured `next-auth` correctly.
* Good place to mention deprecations as well.
*
* REVIEW: Make some of these and corresponding docs less Next.js specific?
*/
export function assertConfig(params: {
options: AuthOptions
req: RequestInternal
}): ConfigError | WarningCode[] {
const { options, req } = params
const warnings: WarningCode[] = []
if (!warned) {
if (!req.origin) warnings.push("NEXTAUTH_URL")
// TODO: Make this throw an error in next major. This will also get rid of `NODE_ENV`
if (!options.secret && process.env.NODE_ENV !== "production")
warnings.push("NO_SECRET")
if (options.debug) warnings.push("DEBUG_ENABLED")
}
if (!options.secret && process.env.NODE_ENV === "production") {
return new MissingSecret("Please define a `secret` in production.")
}
// req.query isn't defined when asserting `getServerSession` for example
if (!req.query?.nextauth && !req.action) {
return new MissingAPIRoute(
"Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly."
)
}
const callbackUrlParam = req.query?.callbackUrl as string | undefined
const url = parseUrl(req.origin)
if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, url.base)) {
return new InvalidCallbackUrl(
`Invalid callback URL. Received: ${callbackUrlParam}`
)
}
const { callbackUrl: defaultCallbackUrl } = defaultCookies(
options.useSecureCookies ?? url.base.startsWith("https://")
)
const callbackUrlCookie =
req.cookies?.[options.cookies?.callbackUrl?.name ?? defaultCallbackUrl.name]
if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, url.base)) {
return new InvalidCallbackUrl(
`Invalid callback URL. Received: ${callbackUrlCookie}`
)
}
let hasCredentials, hasEmail
let hasTwitterOAuth2
for (const provider of options.providers) {
if (provider.type === "credentials") hasCredentials = true
else if (provider.type === "email") hasEmail = true
else if (provider.id === "twitter" && provider.version === "2.0")
hasTwitterOAuth2 = true
}
if (hasCredentials) {
const dbStrategy = options.session?.strategy === "database"
const onlyCredentials = !options.providers.some(
(p) => p.type !== "credentials"
)
if (dbStrategy && onlyCredentials) {
return new UnsupportedStrategy(
"Signin in with credentials only supported if JWT strategy is enabled"
)
}
const credentialsNoAuthorize = options.providers.some(
(p) => p.type === "credentials" && !p.authorize
)
if (credentialsNoAuthorize) {
return new MissingAuthorize(
"Must define an authorize() handler to use credentials authentication provider"
)
}
}
if (hasEmail) {
const { adapter } = options
if (!adapter) {
return new MissingAdapter("E-mail login requires an adapter.")
}
const missingMethods = [
"createVerificationToken",
"useVerificationToken",
"getUserByEmail",
].filter((method) => !adapter[method])
if (missingMethods.length) {
return new MissingAdapterMethods(
`Required adapter methods were missing: ${missingMethods.join(", ")}`
)
}
}
if (!warned) {
if (hasTwitterOAuth2) warnings.push("TWITTER_OAUTH_2_BETA")
warned = true
}
return warnings
}

233
node_modules/next-auth/src/core/lib/callback-handler.ts generated vendored Normal file
View File

@@ -0,0 +1,233 @@
import { AccountNotLinkedError } from "../errors"
import { fromDate } from "./utils"
import type { InternalOptions } from "../types"
import type { AdapterSession, AdapterUser } from "../../adapters"
import type { JWT } from "../../jwt"
import type { Account, User } from "../.."
import type { SessionToken } from "./cookie"
import { OAuthConfig } from "src/providers"
/**
* This function handles the complex flow of signing users in, and either creating,
* linking (or not linking) accounts depending on if the user is currently logged
* in, if they have account already and the authentication mechanism they are using.
*
* It prevents insecure behaviour, such as linking OAuth accounts unless a user is
* signed in and authenticated with an existing valid account.
*
* All verification (e.g. OAuth flows or email address verificaiton flows) are
* done prior to this handler being called to avoid additonal complexity in this
* handler.
*/
export default async function callbackHandler(params: {
sessionToken?: SessionToken
profile: User | AdapterUser | { email: string }
account: Account | null
options: InternalOptions
}) {
const { sessionToken, profile: _profile, account, options } = params
// Input validation
if (!account?.providerAccountId || !account.type)
throw new Error("Missing or invalid provider account")
if (!["email", "oauth"].includes(account.type))
throw new Error("Provider not supported")
const {
adapter,
jwt,
events,
session: { strategy: sessionStrategy, generateSessionToken },
} = options
// If no adapter is configured then we don't have a database and cannot
// persist data; in this mode we just return a dummy session object.
if (!adapter) {
return { user: _profile as User, account }
}
const profile = _profile as AdapterUser
const {
createUser,
updateUser,
getUser,
getUserByAccount,
getUserByEmail,
linkAccount,
createSession,
getSessionAndUser,
deleteSession,
} = adapter
let session: AdapterSession | JWT | null = null
let user: AdapterUser | null = null
let isNewUser = false
const useJwtSession = sessionStrategy === "jwt"
if (sessionToken) {
if (useJwtSession) {
try {
session = await jwt.decode({ ...jwt, token: sessionToken })
if (session && "sub" in session && session.sub) {
user = await getUser(session.sub)
}
} catch {
// If session can't be verified, treat as no session
}
} else {
const userAndSession = await getSessionAndUser(sessionToken)
if (userAndSession) {
session = userAndSession.session
user = userAndSession.user
}
}
}
if (account.type === "email") {
// If signing in with an email, check if an account with the same email address exists already
const userByEmail = await getUserByEmail(profile.email)
if (userByEmail) {
// If they are not already signed in as the same user, this flow will
// sign them out of the current session and sign them in as the new user
if (user?.id !== userByEmail.id && !useJwtSession && sessionToken) {
// Delete existing session if they are currently signed in as another user.
// This will switch user accounts for the session in cases where the user was
// already logged in with a different account.
await deleteSession(sessionToken)
}
// Update emailVerified property on the user object
user = await updateUser({ id: userByEmail.id, emailVerified: new Date() })
await events.updateUser?.({ user })
} else {
const { id: _, ...newUser } = { ...profile, emailVerified: new Date() }
// Create user account if there isn't one for the email address already
// @ts-expect-error see adapters.ts' FutureAdapter["createUser"]
user = await createUser(newUser)
await events.createUser?.({ user })
isNewUser = true
}
// Create new session
session = useJwtSession
? {}
: await createSession({
sessionToken: await generateSessionToken(),
userId: user.id,
expires: fromDate(options.session.maxAge),
})
return { session, user, isNewUser }
} else if (account.type === "oauth") {
// If signing in with OAuth account, check to see if the account exists already
const userByAccount = await getUserByAccount({
providerAccountId: account.providerAccountId,
provider: account.provider,
})
if (userByAccount) {
if (user) {
// If the user is already signed in with this account, we don't need to do anything
if (userByAccount.id === user.id) {
return { session, user, isNewUser }
}
// If the user is currently signed in, but the new account they are signing in
// with is already associated with another user, then we cannot link them
// and need to return an error.
throw new AccountNotLinkedError(
"The account is already associated with another user"
)
}
// If there is no active session, but the account being signed in with is already
// associated with a valid user then create session to sign the user in.
session = useJwtSession
? {}
: await createSession({
sessionToken: await generateSessionToken(),
userId: userByAccount.id,
expires: fromDate(options.session.maxAge),
})
return { session, user: userByAccount, isNewUser }
} else {
if (user) {
// If the user is already signed in and the OAuth account isn't already associated
// with another user account then we can go ahead and link the accounts safely.
// @ts-expect-error see adapters.ts' FutureAdapter["linkAccount"]
await linkAccount({ ...account, userId: user.id })
await events.linkAccount?.({ user, account, profile })
// As they are already signed in, we don't need to do anything after linking them
return { session, user, isNewUser }
}
// If the user is not signed in and it looks like a new OAuth account then we
// check there also isn't an user account already associated with the same
// email address as the one in the OAuth profile.
//
// This step is often overlooked in OAuth implementations, but covers the following cases:
//
// 1. It makes it harder for someone to accidentally create two accounts.
// e.g. by signin in with email, then again with an oauth account connected to the same email.
// 2. It makes it harder to hijack a user account using a 3rd party OAuth account.
// e.g. by creating an oauth account then changing the email address associated with it.
//
// It's quite common for services to automatically link accounts in this case, but it's
// better practice to require the user to sign in *then* link accounts to be sure
// someone is not exploiting a problem with a third party OAuth service.
//
// OAuth providers should require email address verification to prevent this, but in
// practice that is not always the case; this helps protect against that.
const userByEmail = profile.email
? await getUserByEmail(profile.email)
: null
if (userByEmail) {
const provider = options.provider as OAuthConfig<any>
if (provider?.allowDangerousEmailAccountLinking) {
// If you trust the oauth provider to correctly verify email addresses, you can opt-in to
// account linking even when the user is not signed-in.
user = userByEmail
} else {
// We end up here when we don't have an account with the same [provider].id *BUT*
// we do already have an account with the same email address as the one in the
// OAuth profile the user has just tried to sign in with.
//
// We don't want to have two accounts with the same email address, and we don't
// want to link them in case it's not safe to do so, so instead we prompt the user
// to sign in via email to verify their identity and then link the accounts.
throw new AccountNotLinkedError(
"Another account already exists with the same e-mail address"
)
}
} else {
// If the current user is not logged in and the profile isn't linked to any user
// accounts (by email or provider account id)...
//
// If no account matching the same [provider].id or .email exists, we can
// create a new account for the user, link it to the OAuth acccount and
// create a new session for them so they are signed in with it.
const { id: _, ...newUser } = { ...profile, emailVerified: null }
// @ts-expect-error see adapters.ts' FutureAdapter["createUser"]
user = await createUser(newUser)
}
await events.createUser?.({ user })
// @ts-expect-error see adapters.ts' FutureAdapter["linkAccount"]
await linkAccount({ ...account, userId: user.id })
await events.linkAccount?.({ user, account, profile })
session = useJwtSession
? {}
: await createSession({
sessionToken: await generateSessionToken(),
userId: user.id,
expires: fromDate(options.session.maxAge),
})
return { session, user, isNewUser: true }
}
}
throw new Error("Unsupported account type")
}

42
node_modules/next-auth/src/core/lib/callback-url.ts generated vendored Normal file
View File

@@ -0,0 +1,42 @@
import type { InternalOptions } from "../types"
interface CreateCallbackUrlParams {
options: InternalOptions
/** Try reading value from request body (POST) then from query param (GET) */
paramValue?: string
cookieValue?: string
}
/**
* Get callback URL based on query param / cookie + validation,
* and add it to `req.options.callbackUrl`.
*/
export async function createCallbackUrl({
options,
paramValue,
cookieValue,
}: CreateCallbackUrlParams) {
const { url, callbacks } = options
let callbackUrl = url.origin
if (paramValue) {
// If callbackUrl form field or query parameter is passed try to use it if allowed
callbackUrl = await callbacks.redirect({
url: paramValue,
baseUrl: url.origin,
})
} else if (cookieValue) {
// If no callbackUrl specified, try using the value from the cookie if allowed
callbackUrl = await callbacks.redirect({
url: cookieValue,
baseUrl: url.origin,
})
}
return {
callbackUrl,
// Save callback URL in a cookie so that it can be used for subsequent requests in signin/signout/callback flow
callbackUrlCookie: callbackUrl !== cookieValue ? callbackUrl : undefined,
}
}

250
node_modules/next-auth/src/core/lib/cookie.ts generated vendored Normal file
View File

@@ -0,0 +1,250 @@
import type { CookiesOptions } from "../.."
import type { CookieOption, LoggerInstance, SessionStrategy } from "../types"
import type { NextRequest } from "next/server"
import type { NextApiRequest } from "next"
// Uncomment to recalculate the estimated size
// of an empty session cookie
// import { serialize } from "cookie"
// console.log(
// "Cookie estimated to be ",
// serialize(`__Secure.next-auth.session-token.0`, "", {
// expires: new Date(),
// httpOnly: true,
// maxAge: Number.MAX_SAFE_INTEGER,
// path: "/",
// sameSite: "strict",
// secure: true,
// domain: "example.com",
// }).length,
// " bytes"
// )
const ALLOWED_COOKIE_SIZE = 4096
// Based on commented out section above
const ESTIMATED_EMPTY_COOKIE_SIZE = 163
const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE
// REVIEW: Is there any way to defer two types of strings?
/** Stringified form of `JWT`. Extract the content with `jwt.decode` */
export type JWTString = string
export type SetCookieOptions = Partial<CookieOption["options"]> & {
expires?: Date | string
encode?: (val: unknown) => string
}
/**
* If `options.session.strategy` is set to `jwt`, this is a stringified `JWT`.
* In case of `strategy: "database"`, this is the `sessionToken` of the session in the database.
*/
export type SessionToken<T extends SessionStrategy = "jwt"> = T extends "jwt"
? JWTString
: string
/**
* Use secure cookies if the site uses HTTPS
* This being conditional allows cookies to work non-HTTPS development URLs
* Honour secure cookie option, which sets 'secure' and also adds '__Secure-'
* prefix, but enable them by default if the site URL is HTTPS; but not for
* non-HTTPS URLs like http://localhost which are used in development).
* For more on prefixes see https://googlechrome.github.io/samples/cookie-prefixes/
*
* @TODO Review cookie settings (names, options)
*/
export function defaultCookies(useSecureCookies: boolean): CookiesOptions {
const cookiePrefix = useSecureCookies ? "__Secure-" : ""
return {
// default cookie options
sessionToken: {
name: `${cookiePrefix}next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
},
callbackUrl: {
name: `${cookiePrefix}next-auth.callback-url`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
},
csrfToken: {
// Default to __Host- for CSRF token for additional protection if using useSecureCookies
// NB: The `__Host-` prefix is stricter than the `__Secure-` prefix.
name: `${useSecureCookies ? "__Host-" : ""}next-auth.csrf-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
},
pkceCodeVerifier: {
name: `${cookiePrefix}next-auth.pkce.code_verifier`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
maxAge: 60 * 15, // 15 minutes in seconds
},
},
state: {
name: `${cookiePrefix}next-auth.state`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
maxAge: 60 * 15, // 15 minutes in seconds
},
},
nonce: {
name: `${cookiePrefix}next-auth.nonce`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
},
}
}
export interface Cookie extends CookieOption {
value: string
}
type Chunks = Record<string, string>
export class SessionStore {
#chunks: Chunks = {}
#option: CookieOption
#logger: LoggerInstance | Console
constructor(
option: CookieOption,
req: Partial<{
cookies: NextRequest["cookies"] | NextApiRequest["cookies"]
headers: NextRequest["headers"] | NextApiRequest["headers"]
}>,
logger: LoggerInstance | Console
) {
this.#logger = logger
this.#option = option
const { cookies } = req
const { name: cookieName } = option
if (typeof cookies?.getAll === "function") {
// Next.js ^v13.0.1 (Edge Env)
for (const { name, value } of cookies.getAll()) {
if (name.startsWith(cookieName)) {
this.#chunks[name] = value
}
}
} else if (cookies instanceof Map) {
for (const name of cookies.keys()) {
if (name.startsWith(cookieName)) this.#chunks[name] = cookies.get(name)
}
} else {
for (const name in cookies) {
if (name.startsWith(cookieName)) this.#chunks[name] = cookies[name]
}
}
}
/**
* The JWT Session or database Session ID
* constructed from the cookie chunks.
*/
get value() {
// Sort the chunks by their keys before joining
const sortedKeys = Object.keys(this.#chunks).sort((a, b) => {
const aSuffix = parseInt(a.split(".").pop() ?? "0")
const bSuffix = parseInt(b.split(".").pop() ?? "0")
return aSuffix - bSuffix
})
// Use the sorted keys to join the chunks in the correct order
return sortedKeys.map((key) => this.#chunks[key]).join("")
}
/** Given a cookie, return a list of cookies, chunked to fit the allowed cookie size. */
#chunk(cookie: Cookie): Cookie[] {
const chunkCount = Math.ceil(cookie.value.length / CHUNK_SIZE)
if (chunkCount === 1) {
this.#chunks[cookie.name] = cookie.value
return [cookie]
}
const cookies: Cookie[] = []
for (let i = 0; i < chunkCount; i++) {
const name = `${cookie.name}.${i}`
const value = cookie.value.substr(i * CHUNK_SIZE, CHUNK_SIZE)
cookies.push({ ...cookie, name, value })
this.#chunks[name] = value
}
this.#logger.debug("CHUNKING_SESSION_COOKIE", {
message: `Session cookie exceeds allowed ${ALLOWED_COOKIE_SIZE} bytes.`,
emptyCookieSize: ESTIMATED_EMPTY_COOKIE_SIZE,
valueSize: cookie.value.length,
chunks: cookies.map((c) => c.value.length + ESTIMATED_EMPTY_COOKIE_SIZE),
})
return cookies
}
/** Returns cleaned cookie chunks. */
#clean(): Record<string, Cookie> {
const cleanedChunks: Record<string, Cookie> = {}
for (const name in this.#chunks) {
delete this.#chunks?.[name]
cleanedChunks[name] = {
name,
value: "",
options: { ...this.#option.options, maxAge: 0 },
}
}
return cleanedChunks
}
/**
* Given a cookie value, return new cookies, chunked, to fit the allowed cookie size.
* If the cookie has changed from chunked to unchunked or vice versa,
* it deletes the old cookies as well.
*/
chunk(value: string, options: Partial<Cookie["options"]>): Cookie[] {
// Assume all cookies should be cleaned by default
const cookies: Record<string, Cookie> = this.#clean()
// Calculate new chunks
const chunked = this.#chunk({
name: this.#option.name,
value,
options: { ...this.#option.options, ...options },
})
// Update stored chunks / cookies
for (const chunk of chunked) {
cookies[chunk.name] = chunk
}
return Object.values(cookies)
}
/** Returns a list of cookies that should be cleaned. */
clean(): Cookie[] {
return Object.values(this.#clean())
}
}

55
node_modules/next-auth/src/core/lib/csrf-token.ts generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import { createHash, randomBytes } from "crypto"
import type { InternalOptions } from "../types"
interface CreateCSRFTokenParams {
options: InternalOptions
cookieValue?: string
isPost: boolean
bodyValue?: string
}
/**
* Ensure CSRF Token cookie is set for any subsequent requests.
* Used as part of the strategy for mitigation for CSRF tokens.
*
* Creates a cookie like 'next-auth.csrf-token' with the value 'token|hash',
* where 'token' is the CSRF token and 'hash' is a hash made of the token and
* the secret, and the two values are joined by a pipe '|'. By storing the
* value and the hash of the value (with the secret used as a salt) we can
* verify the cookie was set by the server and not by a malicous attacker.
*
* For more details, see the following OWASP links:
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
* https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf
*/
export function createCSRFToken({
options,
cookieValue,
isPost,
bodyValue,
}: CreateCSRFTokenParams) {
if (cookieValue) {
const [csrfToken, csrfTokenHash] = cookieValue.split("|")
const expectedCsrfTokenHash = createHash("sha256")
.update(`${csrfToken}${options.secret}`)
.digest("hex")
if (csrfTokenHash === expectedCsrfTokenHash) {
// If hash matches then we trust the CSRF token value
// If this is a POST request and the CSRF Token in the POST request matches
// the cookie we have already verified is the one we have set, then the token is verified!
const csrfTokenVerified = isPost && csrfToken === bodyValue
return { csrfTokenVerified, csrfToken }
}
}
// New CSRF token
const csrfToken = randomBytes(32).toString("hex")
const csrfTokenHash = createHash("sha256")
.update(`${csrfToken}${options.secret}`)
.digest("hex")
const cookie = `${csrfToken}|${csrfTokenHash}`
return { cookie, csrfToken }
}

View File

@@ -0,0 +1,18 @@
import { CallbacksOptions } from "../.."
export const defaultCallbacks: CallbacksOptions = {
signIn() {
return true
},
redirect({ url, baseUrl }) {
if (url.startsWith("/")) return `${baseUrl}${url}`
else if (new URL(url).origin === baseUrl) return url
return baseUrl
},
session({ session }) {
return session
},
jwt({ token }) {
return token
},
}

View File

@@ -0,0 +1,21 @@
import type { AdapterUser } from "../../../adapters"
import type { InternalOptions } from "../../types"
/**
* Query the database for a user by email address.
* If is an existing user return a user object (otherwise use placeholder).
*/
export default async function getAdapterUserFromEmail({
email,
adapter,
}: {
email: string
adapter: InternalOptions<"email">["adapter"]
}): Promise<AdapterUser> {
// @ts-expect-error -- adapter is checked to be defined in `init`
const { getUserByEmail } = adapter
const adapterUser = email ? await getUserByEmail(email) : null
if (adapterUser) return adapterUser
return { id: email, email, emailVerified: null }
}

51
node_modules/next-auth/src/core/lib/email/signin.ts generated vendored Normal file
View File

@@ -0,0 +1,51 @@
import { randomBytes } from "crypto"
import { hashToken } from "../utils"
import type { InternalOptions } from "../../types"
/**
* Starts an e-mail login flow, by generating a token,
* and sending it to the user's e-mail (with the help of a DB adapter)
*/
export default async function email(
identifier: string,
options: InternalOptions<"email">
): Promise<string> {
const { url, adapter, provider, callbackUrl, theme } = options
// Generate token
const token =
(await provider.generateVerificationToken?.()) ??
randomBytes(32).toString("hex")
const ONE_DAY_IN_SECONDS = 86400
const expires = new Date(
Date.now() + (provider.maxAge ?? ONE_DAY_IN_SECONDS) * 1000
)
// Generate a link with email, unhashed token and callback url
const params = new URLSearchParams({ callbackUrl, token, email: identifier })
const _url = `${url}/callback/${provider.id}?${params}`
await Promise.all([
// Send to user
provider.sendVerificationRequest({
identifier,
token,
expires,
url: _url,
provider,
theme,
}),
// Save in database
// @ts-expect-error -- adapter is checked to be defined in `init`
adapter.createVerificationToken?.({
identifier,
token: hashToken(token, options),
expires,
}),
])
return `${url}/verify-request?${new URLSearchParams({
provider: provider.id,
type: provider.type,
})}`
}

View File

@@ -0,0 +1,63 @@
import { openidClient } from "./client"
import { oAuth1Client, oAuth1TokenStore } from "./client-legacy"
import * as checks from "./checks"
import type { AuthorizationParameters } from "openid-client"
import type { InternalOptions } from "../../types"
import type { RequestInternal } from "../.."
import type { Cookie } from "../cookie"
/**
*
* Generates an authorization/request token URL.
*
* [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) | [OAuth 1](https://oauth.net/core/1.0a/#auth_step2)
*/
export default async function getAuthorizationUrl({
options,
query,
}: {
options: InternalOptions<"oauth">
query: RequestInternal["query"]
}) {
const { logger, provider } = options
let params: any = {}
if (typeof provider.authorization === "string") {
const parsedUrl = new URL(provider.authorization)
const parsedParams = Object.fromEntries(parsedUrl.searchParams)
params = { ...params, ...parsedParams }
} else {
params = { ...params, ...provider.authorization?.params }
}
params = { ...params, ...query }
// Handle OAuth v1.x
if (provider.version?.startsWith("1.")) {
const client = oAuth1Client(options)
const tokens = (await client.getOAuthRequestToken(params)) as any
const url = `${provider.authorization?.url}?${new URLSearchParams({
oauth_token: tokens.oauth_token,
oauth_token_secret: tokens.oauth_token_secret,
...tokens.params,
})}`
oAuth1TokenStore.set(tokens.oauth_token, tokens.oauth_token_secret)
logger.debug("GET_AUTHORIZATION_URL", { url, provider })
return { redirect: url }
}
const client = await openidClient(options)
const authorizationParams: AuthorizationParameters = params
const cookies: Cookie[] = []
await checks.state.create(options, cookies, authorizationParams)
await checks.pkce.create(options, cookies, authorizationParams)
await checks.nonce.create(options, cookies, authorizationParams)
const url = client.authorizationUrl(authorizationParams)
logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider })
return { redirect: url, cookies }
}

183
node_modules/next-auth/src/core/lib/oauth/callback.ts generated vendored Normal file
View File

@@ -0,0 +1,183 @@
import { TokenSet } from "openid-client"
import { openidClient } from "./client"
import { oAuth1Client, oAuth1TokenStore } from "./client-legacy"
import * as _checks from "./checks"
import { OAuthCallbackError } from "../../errors"
import type { CallbackParamsType } from "openid-client"
import type { LoggerInstance, Profile } from "../../.."
import type { OAuthChecks, OAuthConfig } from "../../../providers"
import type { InternalOptions } from "../../types"
import type { RequestInternal } from "../.."
import type { Cookie } from "../cookie"
export default async function oAuthCallback(params: {
options: InternalOptions<"oauth">
query: RequestInternal["query"]
body: RequestInternal["body"]
method: Required<RequestInternal>["method"]
cookies: RequestInternal["cookies"]
}) {
const { options, query, body, method, cookies } = params
const { logger, provider } = options
const errorMessage = body?.error ?? query?.error
if (errorMessage) {
const error = new Error(errorMessage)
logger.error("OAUTH_CALLBACK_HANDLER_ERROR", {
error,
error_description: query?.error_description,
providerId: provider.id,
})
logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", { body })
throw error
}
if (provider.version?.startsWith("1.")) {
try {
const client = await oAuth1Client(options)
// Handle OAuth v1.x
const { oauth_token, oauth_verifier } = query ?? {}
const tokens = (await (client as any).getOAuthAccessToken(
oauth_token,
oAuth1TokenStore.get(oauth_token),
oauth_verifier
)) as TokenSet
let profile: Profile = await (client as any).get(
provider.profileUrl,
tokens.oauth_token,
tokens.oauth_token_secret
)
if (typeof profile === "string") {
profile = JSON.parse(profile)
}
const newProfile = await getProfile({ profile, tokens, provider, logger })
return { ...newProfile, cookies: [] }
} catch (error) {
logger.error("OAUTH_V1_GET_ACCESS_TOKEN_ERROR", error as Error)
throw error
}
}
if (query?.oauth_token) oAuth1TokenStore.delete(query.oauth_token)
try {
const client = await openidClient(options)
let tokens: TokenSet
const checks: OAuthChecks = {}
const resCookies: Cookie[] = []
await _checks.state.use(cookies, resCookies, options, checks)
await _checks.pkce.use(cookies, resCookies, options, checks)
await _checks.nonce.use(cookies, resCookies, options, checks)
const params: CallbackParamsType = {
...client.callbackParams({
url: `http://n?${new URLSearchParams(query)}`,
// TODO: Ask to allow object to be passed upstream:
// https://github.com/panva/node-openid-client/blob/3ae206dfc78c02134aa87a07f693052c637cab84/types/index.d.ts#L439
// @ts-expect-error
body,
method,
}),
...provider.token?.params,
}
if (provider.token?.request) {
const response = await provider.token.request({
provider,
params,
checks,
client,
})
tokens = new TokenSet(response.tokens)
} else if (provider.idToken) {
tokens = await client.callback(provider.callbackUrl, params, checks)
} else {
tokens = await client.oauthCallback(provider.callbackUrl, params, checks)
}
// REVIEW: How can scope be returned as an array?
if (Array.isArray(tokens.scope)) {
tokens.scope = tokens.scope.join(" ")
}
let profile: Profile
if (provider.userinfo?.request) {
profile = await provider.userinfo.request({
provider,
tokens,
client,
})
} else if (provider.idToken) {
profile = tokens.claims()
} else {
profile = await client.userinfo(tokens, {
params: provider.userinfo?.params,
})
}
const profileResult = await getProfile({
profile,
provider,
tokens,
logger,
})
return { ...profileResult, cookies: resCookies }
} catch (error) {
throw new OAuthCallbackError(error as Error)
}
}
export interface GetProfileParams {
profile: Profile
tokens: TokenSet
provider: OAuthConfig<any>
logger: LoggerInstance
}
/** Returns profile, raw profile and auth provider details */
async function getProfile({
profile: OAuthProfile,
tokens,
provider,
logger,
}: GetProfileParams) {
try {
logger.debug("PROFILE_DATA", { OAuthProfile })
const profile = await provider.profile(OAuthProfile, tokens)
profile.email = profile.email?.toLowerCase()
if (!profile.id)
throw new TypeError(
`Profile id is missing in ${provider.name} OAuth profile response`
)
// Return profile, raw profile and auth provider details
return {
profile,
account: {
provider: provider.id,
type: provider.type,
providerAccountId: profile.id.toString(),
...tokens,
},
OAuthProfile,
}
} catch (error) {
// If we didn't get a response either there was a problem with the provider
// response *or* the user cancelled the action with the provider.
//
// Unfortuately, we can't tell which - at least not in a way that works for
// all providers, so we return an empty object; the user should then be
// redirected back to the sign up page. We log the error to help developers
// who might be trying to debug this when configuring a new provider.
logger.error("OAUTH_PARSE_PROFILE_ERROR", {
error: error as Error,
OAuthProfile,
})
}
}

199
node_modules/next-auth/src/core/lib/oauth/checks.ts generated vendored Normal file
View File

@@ -0,0 +1,199 @@
import {
AuthorizationParameters,
generators,
OpenIDCallbackChecks,
} from "openid-client"
import * as jwt from "../../../jwt"
import type { RequestInternal } from "../.."
import type { OAuthChecks } from "../../../providers"
import type { CookiesOptions, InternalOptions } from "../../types"
import type { Cookie } from "../cookie"
/** Returns a signed cookie. */
export async function signCookie(
type: keyof CookiesOptions,
value: string,
maxAge: number,
options: InternalOptions<"oauth">
): Promise<Cookie> {
const { cookies, logger } = options
logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge })
const { name } = cookies[type]
const expires = new Date()
expires.setTime(expires.getTime() + maxAge * 1000)
return {
name,
value: await jwt.encode({
...options.jwt,
maxAge,
token: { value },
salt: name,
}),
options: { ...cookies[type].options, expires },
}
}
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
export const PKCE_CODE_CHALLENGE_METHOD = "S256"
export const pkce = {
async create(
options: InternalOptions<"oauth">,
cookies: Cookie[],
resParams: AuthorizationParameters
) {
if (!options.provider?.checks?.includes("pkce")) return
const code_verifier = generators.codeVerifier()
const value = generators.codeChallenge(code_verifier)
resParams.code_challenge = value
resParams.code_challenge_method = PKCE_CODE_CHALLENGE_METHOD
const maxAge =
options.cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE
cookies.push(
await signCookie("pkceCodeVerifier", code_verifier, maxAge, options)
)
},
/**
* Returns code_verifier if the provider is configured to use PKCE,
* and clears the container cookie afterwards.
* An error is thrown if the code_verifier is missing or invalid.
* @see https://www.rfc-editor.org/rfc/rfc7636
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#pkce
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">,
checks: OAuthChecks
): Promise<string | undefined> {
if (!options.provider?.checks?.includes("pkce")) return
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
if (!codeVerifier)
throw new TypeError("PKCE code_verifier cookie was missing.")
const { name } = options.cookies.pkceCodeVerifier
const value = (await jwt.decode({
...options.jwt,
token: codeVerifier,
salt: name,
})) as any
if (!value?.value)
throw new TypeError("PKCE code_verifier value could not be parsed.")
resCookies.push({
name,
value: "",
options: { ...options.cookies.pkceCodeVerifier.options, maxAge: 0 },
})
checks.code_verifier = value.value
},
}
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
export const state = {
async create(
options: InternalOptions<"oauth">,
cookies: Cookie[],
resParams: AuthorizationParameters
) {
if (!options.provider.checks?.includes("state")) return
const value = generators.state()
resParams.state = value
const maxAge = options.cookies.state.options.maxAge ?? STATE_MAX_AGE
cookies.push(await signCookie("state", value, maxAge, options))
},
/**
* Returns state if the provider is configured to use state,
* and clears the container cookie afterwards.
* An error is thrown if the state is missing or invalid.
* @see https://www.rfc-editor.org/rfc/rfc6749#section-10.12
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">,
checks: OAuthChecks
) {
if (!options.provider.checks?.includes("state")) return
const state = cookies?.[options.cookies.state.name]
if (!state) throw new TypeError("State cookie was missing.")
const { name } = options.cookies.state
const value = (await jwt.decode({
...options.jwt,
token: state,
salt: name,
})) as any
if (!value?.value) throw new TypeError("State value could not be parsed.")
resCookies.push({
name,
value: "",
options: { ...options.cookies.state.options, maxAge: 0 },
})
checks.state = value.value
},
}
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
export const nonce = {
async create(
options: InternalOptions<"oauth">,
cookies: Cookie[],
resParams: AuthorizationParameters
) {
if (!options.provider.checks?.includes("nonce")) return
const value = generators.nonce()
resParams.nonce = value
const maxAge = options.cookies.nonce.options.maxAge ?? NONCE_MAX_AGE
cookies.push(await signCookie("nonce", value, maxAge, options))
},
/**
* Returns nonce if the provider is configured to use nonce,
* and clears the container cookie afterwards.
* An error is thrown if the nonce is missing or invalid.
* @see https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#nonce
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">,
checks: OpenIDCallbackChecks
): Promise<string | undefined> {
if (!options.provider?.checks?.includes("nonce")) return
const nonce = cookies?.[options.cookies.nonce.name]
if (!nonce) throw new TypeError("Nonce cookie was missing.")
const { name } = options.cookies.nonce
const value = (await jwt.decode({
...options.jwt,
token: nonce,
salt: name,
})) as any
if (!value?.value) throw new TypeError("Nonce value could not be parsed.")
resCookies.push({
name,
value: "",
options: { ...options.cookies.nonce.options, maxAge: 0 },
})
checks.nonce = value.value
},
}

View File

@@ -0,0 +1,73 @@
// This is kept around for being backwards compatible with OAuth 1.0 providers.
// We have the intentions to provide only minor fixes for this in the future.
import { OAuth } from "oauth"
import type { InternalOptions } from "../../types"
/**
* Client supporting OAuth 1.x
*/
export function oAuth1Client(options: InternalOptions<"oauth">) {
const provider = options.provider
const oauth1Client = new OAuth(
provider.requestTokenUrl as string,
provider.accessTokenUrl as string,
provider.clientId as string,
provider.clientSecret as string,
provider.version ?? "1.0",
provider.callbackUrl,
provider.encoding ?? "HMAC-SHA1"
)
// Promisify get() for OAuth1
const originalGet = oauth1Client.get.bind(oauth1Client)
// @ts-expect-error
oauth1Client.get = async (...args) => {
return await new Promise((resolve, reject) => {
originalGet(...args, (error, result) => {
if (error) {
return reject(error)
}
resolve(result)
})
})
}
// Promisify getOAuth1AccessToken() for OAuth1
const originalGetOAuth1AccessToken =
oauth1Client.getOAuthAccessToken.bind(oauth1Client)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
oauth1Client.getOAuthAccessToken = async (...args: any[]) => {
return await new Promise((resolve, reject) => {
originalGetOAuth1AccessToken(
...args,
(error: any, oauth_token: any, oauth_token_secret: any) => {
if (error) {
return reject(error)
}
resolve({ oauth_token, oauth_token_secret } as any)
}
)
})
}
const originalGetOAuthRequestToken =
oauth1Client.getOAuthRequestToken.bind(oauth1Client)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
oauth1Client.getOAuthRequestToken = async (params = {}) => {
return await new Promise((resolve, reject) => {
originalGetOAuthRequestToken(
params,
(error, oauth_token, oauth_token_secret, params) => {
if (error) {
return reject(error)
}
resolve({ oauth_token, oauth_token_secret, params } as any)
}
)
})
}
return oauth1Client
}
export const oAuth1TokenStore = new Map()

48
node_modules/next-auth/src/core/lib/oauth/client.ts generated vendored Normal file
View File

@@ -0,0 +1,48 @@
import { Issuer, custom } from "openid-client"
import type { Client } from "openid-client"
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
*/
export async function openidClient(
options: InternalOptions<"oauth">
): Promise<Client> {
const provider = options.provider
if (provider.httpOptions) custom.setHttpOptionsDefaults(provider.httpOptions)
let issuer: Issuer
if (provider.wellKnown) {
issuer = await Issuer.discover(provider.wellKnown)
} else {
issuer = new Issuer({
issuer: provider.issuer as string,
authorization_endpoint: provider.authorization?.url,
token_endpoint: provider.token?.url,
userinfo_endpoint: provider.userinfo?.url,
jwks_uri: provider.jwks_endpoint,
})
}
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
}

93
node_modules/next-auth/src/core/lib/providers.ts generated vendored Normal file
View File

@@ -0,0 +1,93 @@
import { merge } from "../../utils/merge"
import type { InternalProvider, OAuthConfigInternal } from "../types"
import type { OAuthConfig, Provider } from "../../providers"
import type { InternalUrl } from "../../utils/parse-url"
/**
* Adds `signinUrl` and `callbackUrl` to each provider
* and deep merge user-defined options.
*/
export default function parseProviders(params: {
providers: Provider[]
url: InternalUrl
providerId?: string
}): {
providers: InternalProvider[]
provider?: InternalProvider
} {
const { url, providerId } = params
const providers = params.providers.map<InternalProvider>(
({ options: userOptions, ...rest }) => {
if (rest.type === "oauth") {
const normalizedOptions = normalizeOAuthOptions(rest)
const normalizedUserOptions = normalizeOAuthOptions(userOptions, true)
const id = normalizedUserOptions?.id ?? rest.id
return merge(normalizedOptions, {
...normalizedUserOptions,
signinUrl: `${url}/signin/${id}`,
callbackUrl: `${url}/callback/${id}`,
})
}
const id = (userOptions?.id as string) ?? rest.id
return merge(rest, {
...userOptions,
signinUrl: `${url}/signin/${id}`,
callbackUrl: `${url}/callback/${id}`,
})
}
)
return {
providers,
provider: providers.find(({ id }) => id === providerId),
}
}
/**
* Transform OAuth options `authorization`, `token` and `profile` strings to `{ url: string; params: Record<string, string> }`
*/
function normalizeOAuthOptions(
oauthOptions?: Partial<OAuthConfig<any>> | Record<string, unknown>,
isUserOptions = false
) {
if (!oauthOptions) return
const normalized = Object.entries(oauthOptions).reduce<
OAuthConfigInternal<Record<string, unknown>>
>(
(acc, [key, value]) => {
if (
["authorization", "token", "userinfo"].includes(key) &&
typeof value === "string"
) {
const url = new URL(value)
acc[key] = {
url: `${url.origin}${url.pathname}`,
params: Object.fromEntries(url.searchParams ?? []),
}
} else {
acc[key] = value
}
return acc
},
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter
{} as any
)
if (!isUserOptions && !normalized.version?.startsWith("1.")) {
// If provider has as an "openid-configuration" well-known endpoint
// or an "openid" scope request, it will also likely be able to receive an `id_token`
// Only do this if this function is not called with user options to avoid overriding in later stage.
normalized.idToken = Boolean(
normalized.idToken ??
normalized.wellKnown?.includes("openid-configuration") ??
normalized.authorization?.params?.scope?.includes("openid")
)
if (!normalized.checks) normalized.checks = ["state"]
}
return normalized
}

44
node_modules/next-auth/src/core/lib/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,44 @@
import { createHash } from "crypto"
import type { AuthOptions } from "../.."
import type { InternalOptions } from "../types"
import type { InternalUrl } from "../../utils/parse-url"
/**
* Takes a number in seconds and returns the date in the future.
* Optionally takes a second date parameter. In that case
* the date in the future will be calculated from that date instead of now.
*/
export function fromDate(time: number, date = Date.now()) {
return new Date(date + time * 1000)
}
export function hashToken(token: string, options: InternalOptions<"email">) {
const { provider, secret } = options
return (
createHash("sha256")
// Prefer provider specific secret, but use default secret if none specified
.update(`${token}${provider.secret ?? secret}`)
.digest("hex")
)
}
/**
* Secret used salt cookies and tokens (e.g. for CSRF protection).
* If no secret option is specified then it creates one on the fly
* based on options passed here. If options contains unique data, such as
* OAuth provider secrets and database credentials it should be sufficent. If no secret provided in production, we throw an error. */
export function createSecret(params: {
authOptions: AuthOptions
url: InternalUrl
}) {
const { authOptions, url } = params
return (
authOptions.secret ??
// TODO: Remove falling back to default secret, and error in dev if one isn't provided
createHash("sha256")
.update(JSON.stringify({ ...url, ...authOptions }))
.digest("hex")
)
}

112
node_modules/next-auth/src/core/pages/error.tsx generated vendored Normal file
View File

@@ -0,0 +1,112 @@
import { Theme } from "../.."
import { InternalUrl } from "../../utils/parse-url"
/**
* The following errors are passed as error query parameters to the default or overridden error page.
*
* [Documentation](https://next-auth.js.org/configuration/pages#error-page) */
export type ErrorType =
| "default"
| "configuration"
| "accessdenied"
| "verification"
export interface ErrorProps {
url?: InternalUrl
theme?: Theme
error?: ErrorType
}
interface ErrorView {
status: number
heading: string
message: JSX.Element
signin?: JSX.Element
}
/** Renders an error page. */
export default function ErrorPage(props: ErrorProps) {
const { url, error = "default", theme } = props
const signinPageUrl = `${url}/signin`
const errors: Record<ErrorType, ErrorView> = {
default: {
status: 200,
heading: "Error",
message: (
<p>
<a className="site" href={url?.origin}>
{url?.host}
</a>
</p>
),
},
configuration: {
status: 500,
heading: "Server error",
message: (
<div>
<p>There is a problem with the server configuration.</p>
<p>Check the server logs for more information.</p>
</div>
),
},
accessdenied: {
status: 403,
heading: "Access Denied",
message: (
<div>
<p>You do not have permission to sign in.</p>
<p>
<a className="button" href={signinPageUrl}>
Sign in
</a>
</p>
</div>
),
},
verification: {
status: 403,
heading: "Unable to sign in",
message: (
<div>
<p>The sign in link is no longer valid.</p>
<p>It may have been used already or it may have expired.</p>
</div>
),
signin: (
<a className="button" href={signinPageUrl}>
Sign in
</a>
),
},
}
const { status, heading, message, signin } =
errors[error.toLowerCase()] ?? errors.default
return {
status,
html: (
<div className="error">
{theme?.brandColor && (
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--brand-color: ${theme?.brandColor}
}
`,
}}
/>
)}
<div className="card">
{theme?.logo && <img src={theme.logo} alt="Logo" className="logo" />}
<h1>{heading}</h1>
<div className="message">{message}</div>
{signin}
</div>
</div>
),
}
}

79
node_modules/next-auth/src/core/pages/index.ts generated vendored Normal file
View File

@@ -0,0 +1,79 @@
import renderToString from "preact-render-to-string"
import SigninPage from "./signin"
import SignoutPage from "./signout"
import VerifyRequestPage from "./verify-request"
import ErrorPage from "./error"
import css from "../../css"
import type { InternalOptions } from "../types"
import type { RequestInternal, ResponseInternal } from ".."
import type { Cookie } from "../lib/cookie"
import type { ErrorType } from "./error"
type RenderPageParams = {
query?: RequestInternal["query"]
cookies?: Cookie[]
} & Partial<
Pick<
InternalOptions,
"url" | "callbackUrl" | "csrfToken" | "providers" | "theme"
>
>
/**
* Unless the user defines their [own pages](https://next-auth.js.org/configuration/pages),
* we render a set of default ones, using Preact SSR.
*/
export default function renderPage(params: RenderPageParams) {
const { url, theme, query, cookies } = params
function send({ html, title, status }: any): ResponseInternal {
return {
cookies,
status,
headers: [{ key: "Content-Type", value: "text/html" }],
body: `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>${css()}</style><title>${title}</title></head><body class="__next-auth-theme-${
theme?.colorScheme ?? "auto"
}"><div class="page">${renderToString(html)}</div></body></html>`,
}
}
return {
signin(props?: any) {
return send({
html: SigninPage({
csrfToken: params.csrfToken,
providers: params.providers,
callbackUrl: params.callbackUrl,
theme,
...query,
...props,
}),
title: "Sign In",
})
},
signout(props?: any) {
return send({
html: SignoutPage({
csrfToken: params.csrfToken,
url,
theme,
...props,
}),
title: "Sign Out",
})
},
verifyRequest(props?: any) {
return send({
html: VerifyRequestPage({ url, theme, ...props }),
title: "Verify Request",
})
},
error(props?: { error?: ErrorType }) {
return send({
...ErrorPage({ url, theme, ...props }),
title: "Error",
})
},
}
}

279
node_modules/next-auth/src/core/pages/signin.tsx generated vendored Normal file
View File

@@ -0,0 +1,279 @@
import type { InternalProvider, Theme } from "../types"
import type React from "react"
/**
* The following errors are passed as error query parameters to the default or overridden sign-in page.
*
* [Documentation](https://next-auth.js.org/configuration/pages#sign-in-page) */
export type SignInErrorTypes =
| "Signin"
| "OAuthSignin"
| "OAuthCallback"
| "OAuthCreateAccount"
| "EmailCreateAccount"
| "Callback"
| "OAuthAccountNotLinked"
| "EmailSignin"
| "CredentialsSignin"
| "SessionRequired"
| "default"
export interface SignInServerPageParams {
csrfToken: string
providers: InternalProvider[]
callbackUrl: string
email: string
error: SignInErrorTypes
theme: Theme
}
function hexToRgba(hex?: string, alpha = 1) {
if (!hex) {
return
}
// Remove the "#" character if it's included
hex = hex.replace(/^#/, "")
// Expand 3-digit hex codes to their 6-digit equivalents
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]
}
// Parse the hex value to separate R, G, and B components
const bigint = parseInt(hex, 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255
// Ensure the alpha value is within the valid range [0, 1]
alpha = Math.min(Math.max(alpha, 0), 1)
// Construct the RGBA string
const rgba = `rgba(${r}, ${g}, ${b}, ${alpha})`
return rgba
}
export default function SigninPage(props: SignInServerPageParams) {
const {
csrfToken,
providers,
callbackUrl,
theme,
email,
error: errorType,
} = props
// We only want to render providers
const providersToRender = providers.filter((provider) => {
if (provider.type === "oauth" || provider.type === "email") {
// Always render oauth and email type providers
return true
} else if (provider.type === "credentials" && provider.credentials) {
// Only render credentials type provider if credentials are defined
return true
}
// Don't render other provider types
return false
})
if (typeof document !== "undefined" && theme.buttonText) {
document.documentElement.style.setProperty(
"--button-text-color",
theme.buttonText
)
}
if (typeof document !== "undefined" && theme.brandColor) {
document.documentElement.style.setProperty(
"--brand-color",
theme.brandColor
)
}
const errors: Record<SignInErrorTypes, string> = {
Signin: "Try signing in with a different account.",
OAuthSignin: "Try signing in with a different account.",
OAuthCallback: "Try signing in with a different account.",
OAuthCreateAccount: "Try signing in with a different account.",
EmailCreateAccount: "Try signing in with a different account.",
Callback: "Try signing in with a different account.",
OAuthAccountNotLinked:
"To confirm your identity, sign in with the same account you used originally.",
EmailSignin: "The e-mail could not be sent.",
CredentialsSignin:
"Sign in failed. Check the details you provided are correct.",
SessionRequired: "Please sign in to access this page.",
default: "Unable to sign in.",
}
const error = errorType && (errors[errorType] ?? errors.default)
const providerLogoPath = "https://authjs.dev/img/providers"
return (
<div className="signin">
{theme.brandColor && (
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--brand-color: ${theme.brandColor}
}
`,
}}
/>
)}
{theme.buttonText && (
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--button-text-color: ${theme.buttonText}
}
`,
}}
/>
)}
<div className="card">
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
{error && (
<div className="error">
<p>{error}</p>
</div>
)}
{providersToRender.map((provider, i: number) => {
let bg, text, logo, logoDark, bgDark, textDark
if (provider.type === "oauth") {
;({
bg = "",
text = "",
logo = "",
bgDark = bg,
textDark = text,
logoDark = "",
} = provider.style ?? {})
logo = logo.startsWith("/")
? `${providerLogoPath}${logo as string}`
: logo
logoDark = logoDark.startsWith("/")
? `${providerLogoPath}${logoDark as string}`
: logoDark || logo
logoDark ||= logo
}
return (
<div key={provider.id} className="provider">
{provider.type === "oauth" && (
<form action={provider.signinUrl} method="POST">
<input type="hidden" name="csrfToken" value={csrfToken} />
{callbackUrl && (
<input
type="hidden"
name="callbackUrl"
value={callbackUrl}
/>
)}
<button
type="submit"
className="button"
style={
// eslint-disable-next-line
{
"--provider-bg": bg,
"--provider-dark-bg": bgDark,
"--provider-color": text,
"--provider-dark-color": textDark,
"--provider-bg-hover": hexToRgba(bg, 0.8),
"--provider-dark-bg-hover": hexToRgba(bgDark, 0.8),
} as React.CSSProperties
}
>
{logo && (
<img
loading="lazy"
height={24}
width={24}
id="provider-logo"
src={`${
logo.startsWith("/") ? providerLogoPath : ""
}${logo}`}
/>
)}
{logoDark && (
<img
loading="lazy"
height={24}
width={24}
id="provider-logo-dark"
src={`${
logo.startsWith("/") ? providerLogoPath : ""
}${logoDark}`}
/>
)}
<span>Sign in with {provider.name}</span>
</button>
</form>
)}
{(provider.type === "email" || provider.type === "credentials") &&
i > 0 &&
providersToRender[i - 1].type !== "email" &&
providersToRender[i - 1].type !== "credentials" && <hr />}
{provider.type === "email" && (
<form action={provider.signinUrl} method="POST">
<input type="hidden" name="csrfToken" value={csrfToken} />
<label
className="section-header"
htmlFor={`input-email-for-${provider.id}-provider`}
>
Email
</label>
<input
id={`input-email-for-${provider.id}-provider`}
autoFocus
type="email"
name="email"
value={email}
placeholder="email@example.com"
required
/>
<button id="submitButton" type="submit">
Sign in with {provider.name}
</button>
</form>
)}
{provider.type === "credentials" && (
<form action={provider.callbackUrl} method="POST">
<input type="hidden" name="csrfToken" value={csrfToken} />
{Object.keys(provider.credentials).map((credential) => {
return (
<div key={`input-group-${provider.id}`}>
<label
className="section-header"
htmlFor={`input-${credential}-for-${provider.id}-provider`}
>
{provider.credentials[credential].label ?? credential}
</label>
<input
name={credential}
id={`input-${credential}-for-${provider.id}-provider`}
type={provider.credentials[credential].type ?? "text"}
placeholder={
provider.credentials[credential].placeholder ?? ""
}
{...provider.credentials[credential]}
/>
</div>
)
})}
<button type="submit">Sign in with {provider.name}</button>
</form>
)}
{(provider.type === "email" || provider.type === "credentials") &&
i + 1 < providersToRender.length && <hr />}
</div>
)
})}
</div>
</div>
)
}

48
node_modules/next-auth/src/core/pages/signout.tsx generated vendored Normal file
View File

@@ -0,0 +1,48 @@
import { Theme } from "../.."
import { InternalUrl } from "../../utils/parse-url"
export interface SignoutProps {
url: InternalUrl
csrfToken: string
theme: Theme
}
export default function SignoutPage(props: SignoutProps) {
const { url, csrfToken, theme } = props
return (
<div className="signout">
{theme.brandColor && (
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--brand-color: ${theme.brandColor}
}
`,
}}
/>
)}
{theme.buttonText && (
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--button-text-color: ${theme.buttonText}
}
`,
}}
/>
)}
<div className="card">
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
<h1>Signout</h1>
<p>Are you sure you want to sign out?</p>
<form action={`${url}/signout`} method="POST">
<input type="hidden" name="csrfToken" value={csrfToken} />
<button id="submitButton" type="submit">Sign out</button>
</form>
</div>
</div>
)
}

View File

@@ -0,0 +1,37 @@
import { Theme } from "../.."
import { InternalUrl } from "../../utils/parse-url"
interface VerifyRequestPageProps {
url: InternalUrl
theme: Theme
}
export default function VerifyRequestPage(props: VerifyRequestPageProps) {
const { url, theme } = props
return (
<div className="verify-request">
{theme.brandColor && (
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--brand-color: ${theme.brandColor}
}
`,
}}
/>
)}
<div className="card">
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
<h1>Check your email</h1>
<p>A sign in link has been sent to your email address.</p>
<p>
<a className="site" href={url.origin}>
{url.host}
</a>
</p>
</div>
</div>
)
}

429
node_modules/next-auth/src/core/routes/callback.ts generated vendored Normal file
View File

@@ -0,0 +1,429 @@
import oAuthCallback from "../lib/oauth/callback"
import callbackHandler from "../lib/callback-handler"
import { hashToken } from "../lib/utils"
import getAdapterUserFromEmail from "../lib/email/getUserFromEmail"
import type { InternalOptions } from "../types"
import type { RequestInternal, ResponseInternal } from ".."
import type { Cookie, SessionStore } from "../lib/cookie"
import type { User } from "../.."
import type { AdapterSession } from "../../adapters"
/** Handle callbacks from login services */
export default async function callback(params: {
options: InternalOptions
query: RequestInternal["query"]
method: Required<RequestInternal>["method"]
body: RequestInternal["body"]
headers: RequestInternal["headers"]
cookies: RequestInternal["cookies"]
sessionStore: SessionStore
}): Promise<ResponseInternal> {
const { options, query, body, method, headers, sessionStore } = params
const {
provider,
adapter,
url,
callbackUrl,
pages,
jwt,
events,
callbacks,
session: { strategy: sessionStrategy, maxAge: sessionMaxAge },
logger,
} = options
const cookies: Cookie[] = []
const useJwtSession = sessionStrategy === "jwt"
if (provider.type === "oauth") {
try {
const {
profile,
account,
OAuthProfile,
cookies: oauthCookies,
} = await oAuthCallback({
query,
body,
method,
options,
cookies: params.cookies,
})
if (oauthCookies.length) cookies.push(...oauthCookies)
try {
// Make it easier to debug when adding a new provider
logger.debug("OAUTH_CALLBACK_RESPONSE", {
profile,
account,
OAuthProfile,
})
// If we don't have a profile object then either something went wrong
// or the user cancelled signing in. We don't know which, so we just
// direct the user to the signin page for now. We could do something
// else in future.
//
// Note: In oAuthCallback an error is logged with debug info, so it
// should at least be visible to developers what happened if it is an
// error with the provider.
if (!profile || !account || !OAuthProfile) {
return { redirect: `${url}/signin`, cookies }
}
// Check if user is allowed to sign in
// Attempt to get Profile from OAuth provider details before invoking
// signIn callback - but if no user object is returned, that is fine
// (that just means it's a new user signing in for the first time).
let userOrProfile = profile
if (adapter) {
const { getUserByAccount } = adapter
const userByAccount = await getUserByAccount({
providerAccountId: account.providerAccountId,
provider: provider.id,
})
if (userByAccount) userOrProfile = userByAccount
}
try {
const isAllowed = await callbacks.signIn({
user: userOrProfile,
account,
profile: OAuthProfile,
})
if (!isAllowed) {
return { redirect: `${url}/error?error=AccessDenied`, cookies }
} else if (typeof isAllowed === "string") {
return { redirect: isAllowed, cookies }
}
} catch (error) {
return {
redirect: `${url}/error?error=${encodeURIComponent(
(error as Error).message,
)}`,
cookies,
}
}
// Sign user in
const { user, session, isNewUser } = await callbackHandler({
sessionToken: sessionStore.value,
profile,
account,
options,
})
if (useJwtSession) {
const defaultToken = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString(),
}
const token = await callbacks.jwt({
token: defaultToken,
user,
account,
profile: OAuthProfile,
isNewUser,
trigger: isNewUser ? "signUp" : "signIn",
})
// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
} else {
// Save Session Token in cookie
cookies.push({
name: options.cookies.sessionToken.name,
value: (session as AdapterSession).sessionToken,
options: {
...options.cookies.sessionToken.options,
expires: (session as AdapterSession).expires,
},
})
}
// @ts-expect-error
await events.signIn?.({ user, account, profile, isNewUser })
// Handle first logins on new accounts
// e.g. option to send users to a new account landing page on initial login
// Note that the callback URL is preserved, so the journey can still be resumed
if (isNewUser && pages.newUser) {
return {
redirect: `${pages.newUser}${
pages.newUser.includes("?") ? "&" : "?"
}callbackUrl=${encodeURIComponent(callbackUrl)}`,
cookies,
}
}
// Callback URL is already verified at this point, so safe to use if specified
return { redirect: callbackUrl, cookies }
} catch (error) {
if ((error as Error).name === "AccountNotLinkedError") {
// If the email on the account is already linked, but not with this OAuth account
return {
redirect: `${url}/error?error=OAuthAccountNotLinked`,
cookies,
}
} else if ((error as Error).name === "CreateUserError") {
return { redirect: `${url}/error?error=OAuthCreateAccount`, cookies }
}
logger.error("OAUTH_CALLBACK_HANDLER_ERROR", error as Error)
return { redirect: `${url}/error?error=Callback`, cookies }
}
} catch (error) {
if ((error as Error).name === "OAuthCallbackError") {
logger.error("OAUTH_CALLBACK_ERROR", {
error: error as Error,
providerId: provider.id,
})
return { redirect: `${url}/error?error=OAuthCallback`, cookies }
}
logger.error("OAUTH_CALLBACK_ERROR", error as Error)
return { redirect: `${url}/error?error=Callback`, cookies }
}
} else if (provider.type === "email") {
try {
const paramToken = query?.token as string | undefined
const paramIdentifier = query?.email as string | undefined
// If token is missing, the sign-in URL was manually opened without this param or the `sendVerificationRequest` method did not send the link correctly in the email.
if (!paramToken) {
return { redirect: `${url}/error?error=configuration`, cookies }
}
// @ts-expect-error -- Verified in `assertConfig`. adapter: Adapter<true>
const invite = await adapter.useVerificationToken({
// @ts-expect-error User-land adapters might decide to omit the identifier during lookup
identifier: paramIdentifier,
token: hashToken(paramToken, options),
})
const invalidInvite =
!invite ||
invite.expires.valueOf() < Date.now() ||
// The user might have configured the link to not contain the identifier
// so we only compare if it exists
(paramIdentifier && invite.identifier !== paramIdentifier)
if (invalidInvite) {
return { redirect: `${url}/error?error=Verification`, cookies }
}
const profile = await getAdapterUserFromEmail({
email: invite.identifier,
adapter,
})
const account = {
providerAccountId: profile.email,
type: "email" as const,
provider: provider.id,
}
// Check if user is allowed to sign in
try {
const signInCallbackResponse = await callbacks.signIn({
user: profile,
account,
})
if (!signInCallbackResponse) {
return { redirect: `${url}/error?error=AccessDenied`, cookies }
} else if (typeof signInCallbackResponse === "string") {
return { redirect: signInCallbackResponse, cookies }
}
} catch (error) {
return {
redirect: `${url}/error?error=${encodeURIComponent(
(error as Error).message,
)}`,
cookies,
}
}
// Sign user in
const { user, session, isNewUser } = await callbackHandler({
sessionToken: sessionStore.value,
profile,
account,
options,
})
if (useJwtSession) {
const defaultToken = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString(),
}
const token = await callbacks.jwt({
token: defaultToken,
user,
account,
isNewUser,
trigger: isNewUser ? "signUp" : "signIn",
})
// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
} else {
// Save Session Token in cookie
cookies.push({
name: options.cookies.sessionToken.name,
value: (session as AdapterSession).sessionToken,
options: {
...options.cookies.sessionToken.options,
expires: (session as AdapterSession).expires,
},
})
}
await events.signIn?.({ user, account, isNewUser })
// Handle first logins on new accounts
// e.g. option to send users to a new account landing page on initial login
// Note that the callback URL is preserved, so the journey can still be resumed
if (isNewUser && pages.newUser) {
return {
redirect: `${pages.newUser}${
pages.newUser.includes("?") ? "&" : "?"
}callbackUrl=${encodeURIComponent(callbackUrl)}`,
cookies,
}
}
// Callback URL is already verified at this point, so safe to use if specified
return { redirect: callbackUrl, cookies }
} catch (error) {
if ((error as Error).name === "CreateUserError") {
return { redirect: `${url}/error?error=EmailCreateAccount`, cookies }
}
logger.error("CALLBACK_EMAIL_ERROR", error as Error)
return { redirect: `${url}/error?error=Callback`, cookies }
}
} else if (provider.type === "credentials" && method === "POST") {
const credentials = body
let user: User | null
try {
user = await provider.authorize(credentials, {
query,
body,
headers,
method,
})
if (!user) {
return {
status: 401,
redirect: `${url}/error?${new URLSearchParams({
error: "CredentialsSignin",
provider: provider.id,
})}`,
cookies,
}
}
} catch (error) {
return {
status: 401,
redirect: `${url}/error?error=${encodeURIComponent(
(error as Error).message,
)}`,
cookies,
}
}
/** @type {import("src").Account} */
const account = {
providerAccountId: user.id,
type: "credentials",
provider: provider.id,
}
try {
const isAllowed = await callbacks.signIn({
user,
// @ts-expect-error
account,
credentials,
})
if (!isAllowed) {
return {
status: 403,
redirect: `${url}/error?error=AccessDenied`,
cookies,
}
} else if (typeof isAllowed === "string") {
return { redirect: isAllowed, cookies }
}
} catch (error) {
return {
redirect: `${url}/error?error=${encodeURIComponent(
(error as Error).message,
)}`,
cookies,
}
}
const defaultToken = {
name: user.name,
email: user.email,
picture: user.image,
sub: user.id?.toString(),
}
const token = await callbacks.jwt({
token: defaultToken,
user,
// @ts-expect-error
account,
isNewUser: false,
trigger: "signIn",
})
// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
// @ts-expect-error
await events.signIn?.({ user, account })
return { redirect: callbackUrl, cookies }
}
return {
status: 500,
body: `Error: Callback for provider type ${provider.type} not supported`,
cookies,
}
}

5
node_modules/next-auth/src/core/routes/index.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
export { default as callback } from './callback'
export { default as signin } from './signin'
export { default as signout } from './signout'
export { default as session } from './session'
export { default as providers } from './providers'

30
node_modules/next-auth/src/core/routes/providers.ts generated vendored Normal file
View File

@@ -0,0 +1,30 @@
import type { ResponseInternal } from ".."
import type { InternalProvider } from "../types"
export interface PublicProvider {
id: string
name: string
type: string
signinUrl: string
callbackUrl: string
}
/**
* Return a JSON object with a list of all OAuth providers currently configured
* and their signin and callback URLs. This makes it possible to automatically
* generate buttons for all providers when rendering client side.
*/
export default function providers(
providers: InternalProvider[]
): ResponseInternal<Record<string, PublicProvider>> {
return {
headers: [{ key: "Content-Type", value: "application/json" }],
body: providers.reduce<Record<string, PublicProvider>>(
(acc, { id, name, type, signinUrl, callbackUrl }) => {
acc[id] = { id, name, type, signinUrl, callbackUrl }
return acc
},
{}
),
}
}

191
node_modules/next-auth/src/core/routes/session.ts generated vendored Normal file
View File

@@ -0,0 +1,191 @@
import { fromDate } from "../lib/utils"
import type { InternalOptions } from "../types"
import type { ResponseInternal } from ".."
import type { Session } from "../.."
import type { SessionStore } from "../lib/cookie"
interface SessionParams {
options: InternalOptions
sessionStore: SessionStore
isUpdate?: boolean
newSession?: any
}
/**
* Return a session object (without any private fields)
* for Single Page App clients
*/
export default async function session(
params: SessionParams
): Promise<ResponseInternal<Session | {}>> {
const { options, sessionStore, newSession, isUpdate } = params
const {
adapter,
jwt,
events,
callbacks,
logger,
session: { strategy: sessionStrategy, maxAge: sessionMaxAge },
} = options
const response: ResponseInternal<Session | {}> = {
body: {},
headers: [
{ key: "Content-Type", value: "application/json" },
...(isUpdate
? []
: [
{
key: "Cache-Control",
value: "private, no-cache, no-store",
},
{
key: "Pragma",
value: "no-cache",
},
{
key: "Expires",
value: "0",
},
]),
].filter(Boolean),
cookies: [],
}
const sessionToken = sessionStore.value
if (!sessionToken) return response
if (sessionStrategy === "jwt") {
try {
const decodedToken = await jwt.decode({ ...jwt, token: sessionToken })
if (!decodedToken) throw new Error("JWT invalid")
// @ts-expect-error
const token = await callbacks.jwt({
token: decodedToken,
...(isUpdate && { trigger: "update" }),
session: newSession,
})
const newExpires = fromDate(sessionMaxAge)
// By default, only exposes a limited subset of information to the client
// as needed for presentation purposes (e.g. "you are logged in as...").
// @ts-expect-error Property 'user' is missing in type
const updatedSession = await callbacks.session({
session: {
user: {
name: decodedToken?.name,
email: decodedToken?.email,
image: decodedToken?.picture,
},
expires: newExpires.toISOString(),
},
token,
})
// Return session payload as response
response.body = updatedSession
// Refresh JWT expiry by re-signing it, with an updated expiry date
const newToken = await jwt.encode({
...jwt,
token,
maxAge: options.session.maxAge,
})
// Set cookie, to also update expiry date on cookie
const sessionCookies = sessionStore.chunk(newToken, {
expires: newExpires,
})
response.cookies?.push(...sessionCookies)
await events.session?.({ session: updatedSession, token })
} catch (error) {
// If JWT not verifiable, make sure the cookie for it is removed and return empty object
logger.error("JWT_SESSION_ERROR", error as Error)
response.cookies?.push(...sessionStore.clean())
}
} else {
try {
// @ts-expect-error -- adapter is checked to be defined in `init`
const { getSessionAndUser, deleteSession, updateSession } = adapter
let userAndSession = await getSessionAndUser(sessionToken)
// If session has expired, clean up the database
if (
userAndSession &&
userAndSession.session.expires.valueOf() < Date.now()
) {
await deleteSession(sessionToken)
userAndSession = null
}
if (userAndSession) {
const { user, session } = userAndSession
const sessionUpdateAge = options.session.updateAge
// Calculate last updated date to throttle write updates to database
// Formula: ({expiry date} - sessionMaxAge) + sessionUpdateAge
// e.g. ({expiry date} - 30 days) + 1 hour
const sessionIsDueToBeUpdatedDate =
session.expires.valueOf() -
sessionMaxAge * 1000 +
sessionUpdateAge * 1000
const newExpires = fromDate(sessionMaxAge)
// Trigger update of session expiry date and write to database, only
// if the session was last updated more than {sessionUpdateAge} ago
if (sessionIsDueToBeUpdatedDate <= Date.now()) {
await updateSession({ sessionToken, expires: newExpires })
}
// Pass Session through to the session callback
// @ts-expect-error Property 'token' is missing in type
const sessionPayload = await callbacks.session({
// By default, only exposes a limited subset of information to the client
// as needed for presentation purposes (e.g. "you are logged in as...").
session: {
user: { name: user.name, email: user.email, image: user.image },
expires: session.expires.toISOString(),
},
user,
newSession,
...(isUpdate ? { trigger: "update" } : {}),
})
// Return session payload as response
response.body = sessionPayload
// Set cookie again to update expiry
response.cookies?.push({
name: options.cookies.sessionToken.name,
value: sessionToken,
options: {
...options.cookies.sessionToken.options,
expires: newExpires,
},
})
// @ts-expect-error
await events.session?.({ session: sessionPayload })
} else if (sessionToken) {
// If `sessionToken` was found set but it's not valid for a session then
// remove the sessionToken cookie from browser.
response.cookies?.push(...sessionStore.clean())
}
} catch (error) {
logger.error("SESSION_ERROR", error as Error)
}
}
return response
}

126
node_modules/next-auth/src/core/routes/signin.ts generated vendored Normal file
View File

@@ -0,0 +1,126 @@
import getAuthorizationUrl from "../lib/oauth/authorization-url"
import emailSignin from "../lib/email/signin"
import getAdapterUserFromEmail from "../lib/email/getUserFromEmail"
import type { RequestInternal, ResponseInternal } from ".."
import type { InternalOptions } from "../types"
import type { Account } from "../.."
/** Handle requests to /api/auth/signin */
export default async function signin(params: {
options: InternalOptions<"oauth" | "email">
query: RequestInternal["query"]
body: RequestInternal["body"]
}): Promise<ResponseInternal> {
const { options, query, body } = params
const { url, callbacks, logger, provider } = options
if (!provider.type) {
return {
status: 500,
// @ts-expect-error
text: `Error: Type not specified for ${provider.name}`,
}
}
if (provider.type === "oauth") {
try {
const response = await getAuthorizationUrl({ options, query })
return response
} catch (error) {
logger.error("SIGNIN_OAUTH_ERROR", {
error: error as Error,
providerId: provider.id,
})
return { redirect: `${url}/error?error=OAuthSignin` }
}
} else if (provider.type === "email") {
let email: string = body?.email
if (!email) return { redirect: `${url}/error?error=EmailSignin` }
const normalizer: (identifier: string) => string =
provider.normalizeIdentifier ??
((identifier) => {
const trimmedEmail = identifier.trim()
// Validate email format according to RFC 5321/5322
// Reject emails with quotes in the local part to prevent address parser exploits
// Reject multiple @ symbols which could indicate an exploit attempt
const atCount = (trimmedEmail.match(/@/g) ?? []).length
if (atCount !== 1) {
throw new Error("Invalid email address format.")
}
// Check for quotes in the email address which could be used for exploits
if (trimmedEmail.includes('"')) {
throw new Error("Invalid email address format.")
}
// Get the first two elements only,
// separated by `@` from user input.
let [local, domain] = trimmedEmail.toLowerCase().split("@")
// Validate that both local and domain parts exist and are non-empty
if (!local || !domain) {
throw new Error("Invalid email address format.")
}
// The part before "@" can contain a ","
// but we remove it on the domain part
domain = domain.split(",")[0]
// Additional validation: domain must have at least one dot
if (!domain.includes(".")) {
throw new Error("Invalid email address format.")
}
return `${local}@${domain}`
})
try {
email = normalizer(body?.email)
} catch (error) {
logger.error("SIGNIN_EMAIL_ERROR", { error, providerId: provider.id })
return { redirect: `${url}/error?error=EmailSignin` }
}
const user = await getAdapterUserFromEmail({
email,
adapter: options.adapter,
})
const account: Account = {
providerAccountId: email,
userId: email,
type: "email",
provider: provider.id,
}
// Check if user is allowed to sign in
try {
const signInCallbackResponse = await callbacks.signIn({
user,
account,
email: { verificationRequest: true },
})
if (!signInCallbackResponse) {
return { redirect: `${url}/error?error=AccessDenied` }
} else if (typeof signInCallbackResponse === "string") {
return { redirect: signInCallbackResponse }
}
} catch (error) {
return {
redirect: `${url}/error?${new URLSearchParams({
error: error as string,
})}`,
}
}
try {
const redirect = await emailSignin(email, options)
return { redirect }
} catch (error) {
logger.error("SIGNIN_EMAIL_ERROR", { error, providerId: provider.id })
return { redirect: `${url}/error?error=EmailSignin` }
}
}
return { redirect: `${url}/signin` }
}

45
node_modules/next-auth/src/core/routes/signout.ts generated vendored Normal file
View File

@@ -0,0 +1,45 @@
import type { InternalOptions } from "../types"
import type { ResponseInternal } from ".."
import type { SessionStore } from "../lib/cookie"
/** Handle requests to /api/auth/signout */
export default async function signout(params: {
options: InternalOptions
sessionStore: SessionStore
}): Promise<ResponseInternal> {
const { options, sessionStore } = params
const { adapter, events, jwt, callbackUrl, logger, session } = options
const sessionToken = sessionStore?.value
if (!sessionToken) {
return { redirect: callbackUrl }
}
if (session.strategy === "jwt") {
// Dispatch signout event
try {
const decodedJwt = await jwt.decode({ ...jwt, token: sessionToken })
// @ts-expect-error
await events.signOut?.({ token: decodedJwt })
} catch (error) {
// Do nothing if decoding the JWT fails
logger.error("SIGNOUT_ERROR", error)
}
} else {
try {
// @ts-expect-error -- adapter is checked to be defined in `init`
const session = await adapter.deleteSession(sessionToken)
// Dispatch signout event
// @ts-expect-error
await events.signOut?.({ session })
} catch (error) {
// If error, log it but continue
logger.error("SIGNOUT_ERROR", error as Error)
}
}
// Remove Session Token
const sessionCookies = sessionStore.clean()
return { redirect: callbackUrl, cookies: sessionCookies }
}

626
node_modules/next-auth/src/core/types.ts generated vendored Normal file
View File

@@ -0,0 +1,626 @@
import type { Adapter, AdapterUser } from "../adapters"
import type {
Provider,
CredentialInput,
ProviderType,
EmailConfig,
CredentialsConfig,
OAuthConfig,
AuthorizationEndpointHandler,
TokenEndpointHandler,
UserinfoEndpointHandler,
} from "../providers"
import type { TokenSetParameters } from "openid-client"
import type { JWT, JWTOptions } from "../jwt"
import type { LoggerInstance } from "../utils/logger"
import type { CookieSerializeOptions } from "cookie"
import type { NextApiRequest, NextApiResponse } from "next"
import type { InternalUrl } from "../utils/parse-url"
export type Awaitable<T> = T | PromiseLike<T>
export type { LoggerInstance }
/**
* Configure your NextAuth instance
*
* [Documentation](https://next-auth.js.org/configuration/options#options)
*/
export interface AuthOptions {
/**
* An array of authentication providers for signing in
* (e.g. Google, Facebook, Twitter, GitHub, Email, etc) in any order.
* This can be one of the built-in providers or an object with a custom provider.
* * **Default value**: `[]`
* * **Required**: *Yes*
*
* [Documentation](https://next-auth.js.org/configuration/options#providers) | [Providers documentation](https://providers.authjs.dev)
*/
providers: Provider[]
/**
* A random string used to hash tokens, sign cookies and generate cryptographic keys.
* If not specified, it falls back to `jwt.secret` or `NEXTAUTH_SECRET` from environment variables.
* Otherwise, it will use a hash of all configuration options, including Client ID / Secrets for entropy.
*
* NOTE: The last behavior is extremely volatile, and will throw an error in production.
* * **Default value**: `string` (SHA hash of the "options" object)
* * **Required**: No - **but strongly recommended**!
*
* [Documentation](https://next-auth.js.org/configuration/options#secret)
*/
secret?: string
/**
* Configure your session settings, such as determining whether to use JWT or a database,
* setting the idle session expiration duration, or implementing write operation throttling for database usage.
* * **Default value**: See the documentation page
* * **Required**: No
*
* [Documentation](https://next-auth.js.org/configuration/options#session)
*/
session?: Partial<SessionOptions>
/**
* JSON Web Tokens are enabled by default if you have not specified an adapter.
* JSON Web Tokens are encrypted (JWE) by default. We recommend you keep this behaviour.
* * **Default value**: See the documentation page
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#jwt)
*/
jwt?: Partial<JWTOptions>
/**
* Specify URLs to be used if you want to create custom sign in, sign out and error pages.
* Pages specified will override the corresponding built-in page.
* * **Default value**: `{}`
* * **Required**: *No*
* @example
*
* ```js
* pages: {
* signIn: '/auth/signin',
* signOut: '/auth/signout',
* error: '/auth/error',
* verifyRequest: '/auth/verify-request',
* newUser: '/auth/new-user'
* }
* ```
*
* [Documentation](https://next-auth.js.org/configuration/options#pages) | [Pages documentation](https://next-auth.js.org/configuration/pages)
*/
pages?: Partial<PagesOptions>
/**
* Callbacks are asynchronous functions you can use to control what happens when an action is performed.
* Callbacks are *extremely powerful*, especially in scenarios involving JSON Web Tokens
* as they **allow you to implement access controls without a database** and to **integrate with external databases or APIs**.
* * **Default value**: See the Callbacks documentation
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#callbacks) | [Callbacks documentation](https://next-auth.js.org/configuration/callbacks)
*/
callbacks?: Partial<CallbacksOptions>
/**
* Events are asynchronous functions that do not return a response, they are useful for audit logging.
* You can specify a handler for any of these events below - e.g. for debugging or to create an audit log.
* The content of the message object varies depending on the flow
* (e.g. OAuth or Email authentication flow, JWT or database sessions, etc),
* but typically contains a user object and/or contents of the JSON Web Token
* and other information relevant to the event.
* * **Default value**: `{}`
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#events) | [Events documentation](https://next-auth.js.org/configuration/events)
*/
events?: Partial<EventCallbacks>
/**
* You can use the adapter option to pass in your database adapter.
*
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#adapter) |
* [Adapters Overview](https://next-auth.js.org/adapters/overview)
*/
adapter?: Adapter
/**
* Set debug to true to enable debug messages for authentication and database operations.
* * **Default value**: `false`
* * **Required**: *No*
*
* - ⚠ If you added a custom `logger`, this setting is ignored.
*
* [Documentation](https://next-auth.js.org/configuration/options#debug) | [Logger documentation](https://next-auth.js.org/configuration/options#logger)
*/
debug?: boolean
/**
* Override any of the logger levels (`undefined` levels will use the built-in logger),
* and intercept logs in NextAuth. You can use this option to send NextAuth logs to a third-party logging service.
* * **Default value**: `console`
* * **Required**: *No*
*
* @example
*
* ```js
* // /pages/api/auth/[...nextauth].js
* import log from "logging-service"
* export default NextAuth({
* logger: {
* error(code, ...message) {
* log.error(code, message)
* },
* warn(code, ...message) {
* log.warn(code, message)
* },
* debug(code, ...message) {
* log.debug(code, message)
* }
* }
* })
* ```
*
* - ⚠ When set, the `debug` option is ignored
*
* [Documentation](https://next-auth.js.org/configuration/options#logger) |
* [Debug documentation](https://next-auth.js.org/configuration/options#debug)
*/
logger?: Partial<LoggerInstance>
/**
* Changes the theme of pages.
* Set to `"light"` if you want to force pages to always be light.
* Set to `"dark"` if you want to force pages to always be dark.
* Set to `"auto"`, (or leave this option out)if you want the pages to follow the preferred system theme.
* * **Default value**: `"auto"`
* * **Required**: *No*
*
* [Documentation](https://next-auth.js.org/configuration/options#theme) | [Pages documentation]("https://next-auth.js.org/configuration/pages")
*/
theme?: Theme
/**
* When set to `true` then all cookies set by NextAuth.js will only be accessible from HTTPS URLs.
* This option defaults to `false` on URLs that start with `http://` (e.g. http://localhost:3000) for developer convenience.
* You can manually set this option to `false` to disable this security feature and allow cookies
* to be accessible from non-secured URLs (this is not recommended).
* * **Default value**: `true` for HTTPS and `false` for HTTP sites
* * **Required**: No
*
* [Documentation](https://next-auth.js.org/configuration/options#usesecurecookies)
*
* - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options,
* but **may have complex implications** or side effects.
* You should **try to avoid using advanced options** unless you are very comfortable using them.
*/
useSecureCookies?: boolean
/**
* You can override the default cookie names and options for any of the cookies used by NextAuth.js.
* You can specify one or more cookies with custom properties,
* but if you specify custom options for a cookie you must provide all the options for that cookie.
* If you use this feature, you will likely want to create conditional behavior
* to support setting different cookies policies in development and production builds,
* as you will be opting out of the built-in dynamic policy.
* * **Default value**: `{}`
* * **Required**: No
*
* - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options,
* but **may have complex implications** or side effects.
* You should **try to avoid using advanced options** unless you are very comfortable using them.
*
* [Documentation](https://next-auth.js.org/configuration/options#cookies) | [Usage example](https://next-auth.js.org/configuration/options#example)
*/
cookies?: Partial<CookiesOptions>
}
/**
* Change the theme of the built-in pages.
*
* [Documentation](https://next-auth.js.org/configuration/options#theme) |
* [Pages](https://next-auth.js.org/configuration/pages)
*/
export interface Theme {
colorScheme?: "auto" | "dark" | "light"
logo?: string
brandColor?: string
buttonText?: string
}
/**
* Different tokens returned by OAuth Providers.
* Some of them are available with different casing,
* but they refer to the same value.
*/
export type TokenSet = TokenSetParameters
/**
* Usually contains information about the provider being used
* and also extends `TokenSet`, which is different tokens returned by OAuth Providers.
*/
export interface Account extends Partial<TokenSet> {
/**
* This value depends on the type of the provider being used to create the account.
* - oauth: The OAuth account's id, returned from the `profile()` callback.
* - email: The user's email address.
* - credentials: `id` returned from the `authorize()` callback
*/
providerAccountId: string
/** id of the user this account belongs to. */
userId?: string
/** id of the provider used for this account */
provider: string
/** Provider's type for this account */
type: ProviderType
}
/** The OAuth profile returned from your provider */
export interface Profile {
sub?: string
name?: string
email?: string
image?: string
}
/** [Documentation](https://next-auth.js.org/configuration/callbacks) */
export interface CallbacksOptions<P = Profile, A = Account> {
/**
* Use this callback to control if a user is allowed to sign in.
* Returning true will continue the sign-in flow.
* Throwing an error or returning a string will stop the flow, and redirect the user.
*
* [Documentation](https://next-auth.js.org/configuration/callbacks#sign-in-callback)
*/
signIn: (params: {
user: User | AdapterUser
account: A | null
/**
* If OAuth provider is used, it contains the full
* OAuth profile returned by your provider.
*/
profile?: P
/**
* If Email provider is used, on the first call, it contains a
* `verificationRequest: true` property to indicate it is being triggered in the verification request flow.
* When the callback is invoked after a user has clicked on a sign in link,
* this property will not be present. You can check for the `verificationRequest` property
* to avoid sending emails to addresses or domains on a blocklist or to only explicitly generate them
* for email address in an allow list.
*/
email?: {
verificationRequest?: boolean
}
/** If Credentials provider is used, it contains the user credentials */
credentials?: Record<string, CredentialInput>
}) => Awaitable<string | boolean>
/**
* This callback is called anytime the user is redirected to a callback URL (e.g. on signin or signout).
* By default only URLs on the same URL as the site are allowed,
* you can use this callback to customise that behaviour.
*
* [Documentation](https://next-auth.js.org/configuration/callbacks#redirect-callback)
*/
redirect: (params: {
/** URL provided as callback URL by the client */
url: string
/** Default base URL of site (can be used as fallback) */
baseUrl: string
}) => Awaitable<string>
/**
* This callback is called whenever a session is checked.
* (Eg.: invoking the `/api/session` endpoint, using `useSession` or `getSession`)
*
* ⚠ By default, only a subset (email, name, image)
* of the token is returned for increased security.
*
* If you want to make something available you added to the token through the `jwt` callback,
* you have to explicitly forward it here to make it available to the client.
*
* [Documentation](https://next-auth.js.org/configuration/callbacks#session-callback) |
* [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
* [`useSession`](https://next-auth.js.org/getting-started/client#usesession) |
* [`getSession`](https://next-auth.js.org/getting-started/client#getsession) |
*
*/
session: (
params:
| {
session: Session
/** Available when {@link SessionOptions.strategy} is set to `"jwt"` */
token: JWT
/** Available when {@link SessionOptions.strategy} is set to `"database"`. */
user: AdapterUser
} & {
/**
* Available when using {@link SessionOptions.strategy} `"database"`, this is the data
* sent from the client via the [`useSession().update`](https://next-auth.js.org/getting-started/client#update-session) method.
*
* ⚠ Note, you should validate this data before using it.
*/
newSession: any
trigger: "update"
}
) => Awaitable<Session | DefaultSession>
/**
* This callback is called whenever a JSON Web Token is created (i.e. at sign in)
* or updated (i.e whenever a session is accessed in the client).
* Its content is forwarded to the `session` callback,
* where you can control what should be returned to the client.
* Anything else will be kept from your front-end.
*
* The JWT is encrypted by default.
*
* [Documentation](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
* [`session` callback](https://next-auth.js.org/configuration/callbacks#session-callback)
*/
jwt: (
// TODO: remove in `@auth/core` in favor of `trigger: "signUp"`
params: {
/**
* When `trigger` is `"signIn"` or `"signUp"`, it will be a subset of {@link JWT},
* `name`, `email` and `picture` will be included.
*
* Otherwise, it will be the full {@link JWT} for subsequent calls.
*/
token: JWT
/**
* Either the result of the {@link OAuthConfig.profile} or the {@link CredentialsConfig.authorize} callback.
* @note available when `trigger` is `"signIn"` or `"signUp"`.
*
* Resources:
* - [Credentials Provider](https://next-auth.js.org/providers/credentials)
* - [User database model](https://authjs.dev/reference/adapters#user)
*/
user: User | AdapterUser
/**
* Contains information about the provider that was used to sign in.
* Also includes {@link TokenSet}
* @note available when `trigger` is `"signIn"` or `"signUp"`
*/
account: A | null
/**
* The OAuth profile returned from your provider.
* (In case of OIDC it will be the decoded ID Token or /userinfo response)
* @note available when `trigger` is `"signIn"`.
*/
profile?: P
/**
* Check why was the jwt callback invoked. Possible reasons are:
* - user sign-in: First time the callback is invoked, `user`, `profile` and `account` will be present.
* - user sign-up: a user is created for the first time in the database (when {@link SessionOptions.strategy} is set to `"database"`})
* - update event: Triggered by the [`useSession().update`](https://next-auth.js.org/getting-started/client#update-session) method.
* In case of the latter, `trigger` will be `undefined`.
*/
trigger?: "signIn" | "signUp" | "update"
/** @deprecated use `trigger === "signUp"` instead */
isNewUser?: boolean
/**
* When using {@link SessionOptions.strategy} `"jwt"`, this is the data
* sent from the client via the [`useSession().update`](https://next-auth.js.org/getting-started/client#update-session) method.
*
* ⚠ Note, you should validate this data before using it.
*/
session?: any
}
) => Awaitable<JWT>
}
/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */
export interface CookieOption {
name: string
options: CookieSerializeOptions
}
/** [Documentation](https://next-auth.js.org/configuration/options#cookies) */
export interface CookiesOptions {
sessionToken: CookieOption
callbackUrl: CookieOption
csrfToken: CookieOption
pkceCodeVerifier: CookieOption
state: CookieOption
nonce: CookieOption
}
/**
* The various event callbacks you can register for from next-auth
*
* [Documentation](https://next-auth.js.org/configuration/events)
*/
export interface EventCallbacks {
/**
* If using a `credentials` type auth, the user is the raw response from your
* credential provider.
* For other providers, you'll get the User object from your adapter, the account,
* and an indicator if the user was new to your Adapter.
*/
signIn: (message: {
user: User
account: Account | null
profile?: Profile
isNewUser?: boolean
}) => Awaitable<void>
/**
* The message object will contain one of these depending on
* if you use JWT or database persisted sessions:
* - `token`: The JWT token for this session.
* - `session`: The session object from your adapter that is being ended.
*/
signOut: (message: { session: Session; token: JWT }) => Awaitable<void>
createUser: (message: { user: User }) => Awaitable<void>
updateUser: (message: { user: User }) => Awaitable<void>
linkAccount: (message: {
user: User | AdapterUser
account: Account
profile: User | AdapterUser
}) => Awaitable<void>
/**
* The message object will contain one of these depending on
* if you use JWT or database persisted sessions:
* - `token`: The JWT token for this session.
* - `session`: The session object from your adapter.
*/
session: (message: { session: Session; token: JWT }) => Awaitable<void>
}
export type EventType = keyof EventCallbacks
/** [Documentation](https://next-auth.js.org/configuration/pages) */
export interface PagesOptions {
signIn: string
signOut: string
/** Error code passed in query string as ?error= */
error: string
verifyRequest: string
/** If set, new users will be directed here on first sign in */
newUser: string
}
export type ISODateString = string
export interface DefaultSession {
user?: {
name?: string | null
email?: string | null
image?: string | null
}
expires: ISODateString
}
/**
* Returned by `useSession`, `getSession`, returned by the `session` callback
* and also the shape received as a prop on the `SessionProvider` React Context
*
* [`useSession`](https://next-auth.js.org/getting-started/client#usesession) |
* [`getSession`](https://next-auth.js.org/getting-started/client#getsession) |
* [`SessionProvider`](https://next-auth.js.org/getting-started/client#sessionprovider) |
* [`session` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback)
*/
export interface Session extends DefaultSession {}
export type SessionStrategy = "jwt" | "database"
/** [Documentation](https://next-auth.js.org/configuration/options#session) */
export interface SessionOptions {
/**
* Choose how you want to save the user session.
* The default is `"jwt"`, an encrypted JWT (JWE) in the session cookie.
*
* If you use an `adapter` however, we default it to `"database"` instead.
* You can still force a JWT session by explicitly defining `"jwt"`.
*
* When using `"database"`, the session cookie will only contain a `sessionToken` value,
* which is used to look up the session in the database.
*
* [Documentation](https://next-auth.js.org/configuration/options#session) | [Adapter](https://next-auth.js.org/configuration/options#adapter) | [About JSON Web Tokens](https://next-auth.js.org/faq#json-web-tokens)
*/
strategy: SessionStrategy
/**
* Relative time from now in seconds when to expire the session
* @default 2592000 // 30 days
*/
maxAge: number
/**
* How often the session should be updated in seconds.
* If set to `0`, session is updated every time.
* @default 86400 // 1 day
*/
updateAge: number
/**
* Generate a custom session token for database-based sessions.
* By default, a random UUID or string is generated depending on the Node.js version.
* However, you can specify your own custom string (such as CUID) to be used.
* @default `randomUUID` or `randomBytes.toHex` depending on the Node.js version
*/
generateSessionToken: () => Awaitable<string>
}
export interface DefaultUser {
id: string
name?: string | null
email?: string | null
image?: string | null
}
/**
* The shape of the returned object in the OAuth providers' `profile` callback,
* available in the `jwt` and `session` callbacks,
* or the second parameter of the `session` callback, when using a database.
*
* [`signIn` callback](https://next-auth.js.org/configuration/callbacks#sign-in-callback) |
* [`session` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
* [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
* [`profile` OAuth provider callback](https://next-auth.js.org/configuration/providers/oauth#using-a-custom-provider)
*/
export interface User extends DefaultUser {}
// Below are types that are only supposed be used by next-auth internally
/** @internal */
export interface OAuthConfigInternal<P>
extends Omit<OAuthConfig<P>, "authorization" | "token" | "userinfo"> {
authorization?: AuthorizationEndpointHandler
token?: TokenEndpointHandler
userinfo?: UserinfoEndpointHandler
}
/** @internal */
export type InternalProvider<T = ProviderType> = (T extends "oauth"
? OAuthConfigInternal<any>
: T extends "email"
? EmailConfig
: T extends "credentials"
? CredentialsConfig
: never) & {
signinUrl: string
callbackUrl: string
}
export type AuthAction =
| "providers"
| "session"
| "csrf"
| "signin"
| "signout"
| "callback"
| "verify-request"
| "error"
| "_log"
type NonNullableFields<T> = {
[P in keyof T]-?: NonNullable<T[P]>
}
/** @internal */
export interface InternalOptions<TProviderType = ProviderType> {
providers: InternalProvider[]
/**
* Parsed from `NEXTAUTH_URL` or `x-forwarded-host` and `x-forwarded-proto` if the host is trusted.
* @default "http://localhost:3000/api/auth"
*/
url: InternalUrl
action: AuthAction
provider: InternalProvider<TProviderType>
csrfToken?: string
csrfTokenVerified?: boolean
secret: string
theme: Theme
debug: boolean
logger: LoggerInstance
session: Required<SessionOptions>
pages: Partial<PagesOptions>
jwt: JWTOptions
events: Partial<EventCallbacks>
adapter?: NonNullableFields<Adapter>
callbacks: CallbacksOptions
cookies: CookiesOptions
callbackUrl: string
}
/** @internal */
export interface NextAuthRequest extends NextApiRequest {
options: InternalOptions
}
/** @internal */
export type NextAuthResponse<T = any> = NextApiResponse<T>
/** @internal */
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
export type NextAuthApiHandler<Result = void, Response = any> = (
req: NextAuthRequest,
res: NextAuthResponse<Response>
) => Awaitable<Result>

319
node_modules/next-auth/src/css/index.css generated vendored Normal file
View File

@@ -0,0 +1,319 @@
:root {
--border-width: 1px;
--border-radius: 0.5rem;
--color-error: #c94b4b;
--color-info: #157efb;
--color-info-hover: #0f6ddb;
--color-info-text: #fff;
}
.__next-auth-theme-auto,
.__next-auth-theme-light {
--color-background: #ececec;
--color-background-hover: rgba(236, 236, 236, 0.8);
--color-background-card: #fff;
--color-text: #000;
--color-primary: #444;
--color-control-border: #bbb;
--color-button-active-background: #f9f9f9;
--color-button-active-border: #aaa;
--color-separator: #ccc;
}
.__next-auth-theme-dark {
--color-background: #161b22;
--color-background-hover: rgba(22, 27, 34, 0.8);
--color-background-card: #0d1117;
--color-text: #fff;
--color-primary: #ccc;
--color-control-border: #555;
--color-button-active-background: #060606;
--color-button-active-border: #666;
--color-separator: #444;
}
@media (prefers-color-scheme: dark) {
.__next-auth-theme-auto {
--color-background: #161b22;
--color-background-hover: rgba(22, 27, 34, 0.8);
--color-background-card: #0d1117;
--color-text: #fff;
--color-primary: #ccc;
--color-control-border: #555;
--color-button-active-background: #060606;
--color-button-active-border: #666;
--color-separator: #444;
}
button,
a.button {
color: var(--provider-dark-color, var(--color-primary));
background-color: var(--provider-dark-bg, var(--color-background));
&:hover {
background-color: var(--provider-dark-bg-hover, var(--color-background-hover)) !important;
}
}
#provider-logo {
display: none !important;
}
#provider-logo-dark {
width: 25px;
display: block !important;
}
}
html {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
*,
*:before,
*:after {
-webkit-box-sizing: inherit;
-moz-box-sizing: inherit;
box-sizing: inherit;
margin: 0;
padding: 0;
}
body {
background-color: var(--color-background);
margin: 0;
padding: 0;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
h1 {
margin-bottom: 1.5rem;
padding: 0 1rem;
font-weight: 400;
color: var(--color-text);
}
p {
margin-bottom: 1.5rem;
padding: 0 1rem;
color: var(--color-text);
}
form {
margin: 0;
padding: 0;
}
label {
font-weight: 500;
text-align: left;
margin-bottom: 0.25rem;
display: block;
color: var(--color-text);
}
input[type] {
box-sizing: border-box;
display: block;
width: 100%;
padding: 0.5rem 1rem;
border: var(--border-width) solid var(--color-control-border);
background: var(--color-background-card);
font-size: 1rem;
border-radius: var(--border-radius);
color: var(--color-text);
&:focus {
box-shadow: none;
}
}
p {
font-size: 1.1rem;
line-height: 2rem;
}
a.button {
text-decoration: none;
line-height: 1rem;
&:link,
&:visited {
background-color: var(--color-background);
color: var(--color-primary);
}
}
button span {
flex-grow: 1;
}
button,
a.button {
padding: 0.75rem 1rem;
color: var(--provider-color, var(--color-primary));
background-color: var(--provider-bg);
font-size: 1.1rem;
min-height: 62px;
border-color: rgba(0, 0, 0, 0.1);
border-radius: var(--border-radius);
transition: all 0.1s ease-in-out;
font-weight: 500;
position: relative;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background-color: var(--provider-bg-hover, var(--color-background-hover));
cursor: pointer;
}
&:active {
cursor: pointer;
}
#provider-logo {
width: 25px;
display: block;
}
#provider-logo-dark {
display: none;
}
}
#submitButton {
color: var(--button-text-color, var(--color-info-text));
background-color: var(--brand-color, var(--color-info));
width: 100%;
&:hover {
background-color: var(--button-hover-bg, var(--color-info-hover)) !important;
}
}
a.site {
color: var(--color-primary);
text-decoration: none;
font-size: 1rem;
line-height: 2rem;
&:hover {
text-decoration: underline;
}
}
.page {
position: absolute;
width: 100%;
height: 100%;
display: grid;
place-items: center;
margin: 0;
padding: 0;
box-sizing: border-box;
> div {
text-align: center;
}
}
.error {
a.button {
padding-left: 2rem;
padding-right: 2rem;
margin-top: 0.5rem;
}
.message {
margin-bottom: 1.5rem;
}
}
.signin {
input[type="text"] {
margin-left: auto;
margin-right: auto;
display: block;
}
hr {
display: block;
border: 0;
border-top: 1px solid var(--color-separator);
margin: 2rem auto 1rem auto;
overflow: visible;
&::before {
content: "or";
background: var(--color-background-card);
color: #888;
padding: 0 0.4rem;
position: relative;
top: -0.7rem;
}
}
.error {
background: #f5f5f5;
font-weight: 500;
border-radius: 0.3rem;
background: var(--color-error);
p {
text-align: left;
padding: 0.5rem 1rem;
font-size: 0.9rem;
line-height: 1.2rem;
color: var(--color-info-text);
}
}
> div,
form {
display: block;
input[type] {
margin-bottom: 0.5rem;
}
button {
width: 100%;
}
}
.provider + .provider {
margin-top: 1rem;
}
}
.logo {
display: inline-block;
max-width: 150px;
margin: 1.25rem 0;
max-height: 70px;
}
.card {
background-color: var(--color-background-card);
border-radius: 2rem;
padding: 1.25rem 2rem;
.header {
color: var(--color-primary);
}
}
.section-header {
color: var(--color-text);
}
@media screen and (min-width: 450px) {
.card {
margin: 2rem 0;
width: 368px;
}
}
@media screen and (max-width: 450px) {
.card {
margin: 1rem 0;
width: 343px;
}
}

11
node_modules/next-auth/src/css/index.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
// To support serverless targets (which don"t work if you try to read in things
// like CSS files at run time) this file is replaced in production builds with
// a function that returns compiled CSS (embedded as a string in the function).
import fs from "fs"
import path from "path"
const pathToCss = path.join(process.cwd(), process.env.NODE_ENV === "development" ? "node_modules/next-auth/css/index.css" : "/src/css/index.css")
export default function css() {
return fs.readFileSync(pathToCss, "utf8")
}

10
node_modules/next-auth/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
export * from "./core/types"
export type { AuthOptions as NextAuthOptions } from "./core/types"
export type {
RequestInternal,
ResponseInternal as OutgoingResponse,
} from "./core"
export * from "./next"
export { default } from "./next"

132
node_modules/next-auth/src/jwt/index.ts generated vendored Normal file
View File

@@ -0,0 +1,132 @@
import { EncryptJWT, jwtDecrypt } from "jose"
import hkdf from "@panva/hkdf"
import { v4 as uuid } from "uuid"
import { SessionStore } from "../core/lib/cookie"
import type { GetServerSidePropsContext, NextApiRequest } from "next"
import type { NextRequest } from "next/server"
import type { JWT, JWTDecodeParams, JWTEncodeParams, JWTOptions } from "./types"
import type { LoggerInstance } from ".."
export * from "./types"
const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days
const now = () => (Date.now() / 1000) | 0
/** Issues a JWT. By default, the JWT is encrypted using "A256GCM". */
export async function encode(params: JWTEncodeParams) {
/** @note empty `salt` means a session token. See {@link JWTEncodeParams.salt}. */
const { token = {}, secret, maxAge = DEFAULT_MAX_AGE, salt = "" } = params
const encryptionSecret = await getDerivedEncryptionKey(secret, salt)
return await new EncryptJWT(token)
.setProtectedHeader({ alg: "dir", enc: "A256GCM" })
.setIssuedAt()
.setExpirationTime(now() + maxAge)
.setJti(uuid())
.encrypt(encryptionSecret)
}
/** Decodes a NextAuth.js issued JWT. */
export async function decode(params: JWTDecodeParams): Promise<JWT | null> {
/** @note empty `salt` means a session token. See {@link JWTDecodeParams.salt}. */
const { token, secret, salt = "" } = params
if (!token) return null
const encryptionSecret = await getDerivedEncryptionKey(secret, salt)
const { payload } = await jwtDecrypt(token, encryptionSecret, {
clockTolerance: 15,
})
return payload
}
export interface GetTokenParams<R extends boolean = false> {
/** The request containing the JWT either in the cookies or in the `Authorization` header. */
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest
/**
* Use secure prefix for cookie name, unless URL in `NEXTAUTH_URL` is http://
* or not set (e.g. development or test instance) case use unprefixed name
*/
secureCookie?: boolean
/** If the JWT is in the cookie, what name `getToken()` should look for. */
cookieName?: string
/**
* `getToken()` will return the raw JWT if this is set to `true`
* @default false
*/
raw?: R
/**
* The same `secret` used in the `NextAuth` configuration.
* Defaults to the `NEXTAUTH_SECRET` environment variable.
*/
secret?: string
decode?: JWTOptions["decode"]
logger?: LoggerInstance | Console
}
/**
* Takes a NextAuth.js request (`req`) and returns either the NextAuth.js issued JWT's payload,
* or the raw JWT string. We look for the JWT in the either the cookies, or the `Authorization` header.
* [Documentation](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken)
*/
export async function getToken<R extends boolean = false>(
params: GetTokenParams<R>
): Promise<R extends true ? string : JWT | null> {
const {
req,
secureCookie = process.env.NEXTAUTH_URL?.startsWith("https://") ??
!!process.env.VERCEL,
cookieName = secureCookie
? "__Secure-next-auth.session-token"
: "next-auth.session-token",
raw,
decode: _decode = decode,
logger = console,
secret = process.env.NEXTAUTH_SECRET ?? process.env.AUTH_SECRET,
} = params
if (!req) throw new Error("Must pass `req` to JWT getToken()")
const sessionStore = new SessionStore(
{ name: cookieName, options: { secure: secureCookie } },
{ cookies: req.cookies, headers: req.headers },
logger
)
let token = sessionStore.value
const authorizationHeader =
req.headers instanceof Headers
? req.headers.get("authorization")
: req.headers?.authorization
if (!token && authorizationHeader?.split(" ")[0] === "Bearer") {
const urlEncodedToken = authorizationHeader.split(" ")[1]
token = decodeURIComponent(urlEncodedToken)
}
// @ts-expect-error
if (!token) return null
// @ts-expect-error
if (raw) return token
try {
// @ts-expect-error
return await _decode({ token, secret })
} catch {
// @ts-expect-error
return null
}
}
async function getDerivedEncryptionKey(
keyMaterial: string | Buffer,
salt: string
) {
return await hkdf(
"sha256",
keyMaterial,
salt,
`NextAuth.js Generated Encryption Key${salt ? ` (${salt})` : ""}`,
32
)
}

66
node_modules/next-auth/src/jwt/types.ts generated vendored Normal file
View File

@@ -0,0 +1,66 @@
import type { Awaitable } from ".."
export interface DefaultJWT extends Record<string, unknown> {
name?: string | null
email?: string | null
picture?: string | null
sub?: string
}
/**
* Returned by the `jwt` callback and `getToken`, when using JWT sessions
*
* [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | [`getToken`](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken)
*/
export interface JWT extends Record<string, unknown>, DefaultJWT {}
export interface JWTEncodeParams {
/** The JWT payload. */
token?: JWT
/**
* Used in combination with `secret` when deriving the encryption secret for the various NextAuth.js-issued JWTs.
* @note When no `salt` is passed, we assume this is a session token.
* This is for backwards-compatibility with currently active sessions, so they won't be invalidated when upgrading the package.
*/
salt?: string
/** The key material used to encode the NextAuth.js issued JWTs. Defaults to `NEXTAUTH_SECRET`. */
secret: string | Buffer
/**
* The maximum age of the NextAuth.js issued JWT in seconds.
* @default 30 * 24 * 60 * 60 // 30 days
*/
maxAge?: number
}
export interface JWTDecodeParams {
/** The NextAuth.js issued JWT to be decoded */
token?: string
/**
* Used in combination with `secret` when deriving the encryption secret for the various NextAuth.js-issued JWTs.
* @note When no `salt` is passed, we assume this is a session token.
* This is for backwards-compatibility with currently active sessions, so they won't be invalidated when upgrading the package.
*/
salt?: string
/** The key material used to decode the NextAuth.js issued JWTs. Defaults to `NEXTAUTH_SECRET`. */
secret: string | Buffer
}
export interface JWTOptions {
/**
* The secret used to encode/decode the NextAuth.js issued JWT.
* @deprecated Set the `NEXTAUTH_SECRET` environment variable or
* use the top-level `secret` option instead
*/
secret: string
/**
* The maximum age of the NextAuth.js issued JWT in seconds.
* @default 30 * 24 * 60 * 60 // 30 days
*/
maxAge: number
/** Override this method to control the NextAuth.js issued JWT encoding. */
encode: (params: JWTEncodeParams) => Awaitable<string>
/** Override this method to control the NextAuth.js issued JWT decoding. */
decode: (params: JWTDecodeParams) => Awaitable<JWT | null>
}
export type Secret = string | Buffer

2
node_modules/next-auth/src/middleware.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export { default } from "./next/middleware"
export * from "./next/middleware"

261
node_modules/next-auth/src/next/index.ts generated vendored Normal file
View File

@@ -0,0 +1,261 @@
import { AuthHandler } from "../core"
import { setCookie, getBody, toResponse } from "./utils"
import type {
GetServerSidePropsContext,
NextApiRequest,
NextApiResponse,
} from "next"
import { type NextRequest } from "next/server"
import type { AuthOptions, Session } from ".."
import type {
CallbacksOptions,
AuthAction,
NextAuthRequest,
NextAuthResponse,
Awaitable,
} from "../core/types"
interface RouteHandlerContext {
params: Awaitable<{ nextauth: string[] }>
}
async function NextAuthApiHandler(
req: NextApiRequest,
res: NextApiResponse,
options: AuthOptions
) {
const { nextauth, ...query } = req.query
options.secret ??=
options.jwt?.secret ??
process.env.NEXTAUTH_SECRET ??
process.env.AUTH_SECRET
const handler = await AuthHandler({
req: {
body: req.body,
query,
cookies: req.cookies,
headers: req.headers,
method: req.method,
action: nextauth?.[0] as AuthAction,
providerId: nextauth?.[1],
error: (req.query.error as string | undefined) ?? nextauth?.[1],
},
options,
})
res.status(handler.status ?? 200)
handler.cookies?.forEach((cookie) => setCookie(res, cookie))
handler.headers?.forEach((h) => res.setHeader(h.key, h.value))
if (handler.redirect) {
// If the request expects a return URL, send it as JSON
// instead of doing an actual redirect.
if (req.body?.json !== "true") {
// Could chain. .end() when lowest target is Node 14
// https://github.com/nodejs/node/issues/33148
res.status(302).setHeader("Location", handler.redirect)
res.end()
return
}
return res.json({ url: handler.redirect })
}
return res.send(handler.body)
}
// @see https://beta.nextjs.org/docs/routing/route-handlers
async function NextAuthRouteHandler(
req: NextRequest,
context: RouteHandlerContext,
options: AuthOptions
) {
options.secret ??= process.env.NEXTAUTH_SECRET ?? process.env.AUTH_SECRET
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { headers, cookies } = require("next/headers")
const nextauth = (await context.params)?.nextauth
const query = Object.fromEntries(req.nextUrl.searchParams)
const body = await getBody(req)
const internalResponse = await AuthHandler({
req: {
body,
query,
cookies: Object.fromEntries(
(await cookies()).getAll().map((c) => [c.name, c.value])
),
headers: Object.fromEntries((await headers()) as Headers),
method: req.method,
action: nextauth?.[0] as AuthAction,
providerId: nextauth?.[1],
error: query.error ?? nextauth?.[1],
},
options,
})
const response = toResponse(internalResponse)
const redirect = response.headers.get("Location")
if (body?.json === "true" && redirect) {
response.headers.delete("Location")
response.headers.set("Content-Type", "application/json")
return new Response(JSON.stringify({ url: redirect }), {
status: internalResponse.status,
headers: response.headers,
})
}
return response
}
function NextAuth(options: AuthOptions): any
function NextAuth(
req: NextApiRequest,
res: NextApiResponse,
options: AuthOptions
): any
function NextAuth(
req: NextRequest,
res: RouteHandlerContext,
options: AuthOptions
): any
/** The main entry point to next-auth */
function NextAuth(
...args:
| [AuthOptions]
| Parameters<typeof NextAuthRouteHandler>
| Parameters<typeof NextAuthApiHandler>
) {
if (args.length === 1) {
return async (
req: NextAuthRequest | NextRequest,
res: NextAuthResponse | RouteHandlerContext
) => {
if ((res as any)?.params) {
return await NextAuthRouteHandler(
req as NextRequest,
res as RouteHandlerContext,
args[0]
)
}
return await NextAuthApiHandler(
req as NextApiRequest,
res as NextApiResponse,
args[0]
)
}
}
if ((args[1] as any)?.params) {
return NextAuthRouteHandler(
...(args as Parameters<typeof NextAuthRouteHandler>)
)
}
return NextAuthApiHandler(...(args as Parameters<typeof NextAuthApiHandler>))
}
export default NextAuth
type GetServerSessionOptions = Partial<Omit<AuthOptions, "callbacks">> & {
callbacks?: Omit<AuthOptions["callbacks"], "session"> & {
session?: (...args: Parameters<CallbacksOptions["session"]>) => any
}
}
type GetServerSessionParams<O extends GetServerSessionOptions> =
| [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"], O]
| [NextApiRequest, NextApiResponse, O]
| [O]
| []
export async function getServerSession<
O extends GetServerSessionOptions,
R = O["callbacks"] extends { session: (...args: any[]) => infer U }
? U
: Session
>(...args: GetServerSessionParams<O>): Promise<R | null> {
const isRSC = args.length === 0 || args.length === 1
let req, res, options: AuthOptions
if (isRSC) {
options = Object.assign({}, args[0], { providers: [] })
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { headers, cookies } = require("next/headers")
req = {
headers: Object.fromEntries((await headers()) as Headers),
cookies: Object.fromEntries(
(await cookies()).getAll().map((c) => [c.name, c.value])
),
}
res = { getHeader() {}, setCookie() {}, setHeader() {} }
} else {
req = args[0]
res = args[1]
options = Object.assign({}, args[2], { providers: [] })
}
options.secret ??= process.env.NEXTAUTH_SECRET ?? process.env.AUTH_SECRET
const session = await AuthHandler<Session | {} | string>({
options,
req: {
action: "session",
method: "GET",
cookies: req.cookies,
headers: req.headers,
},
})
const { body, cookies, status = 200 } = session
cookies?.forEach((cookie) => setCookie(res, cookie))
if (body && typeof body !== "string" && Object.keys(body).length) {
if (status === 200) {
// @ts-expect-error
if (isRSC) delete body.expires
return body as R
}
throw new Error((body as any).message)
}
return null
}
let deprecatedWarningShown = false
/** @deprecated renamed to `getServerSession` */
export async function unstable_getServerSession<
O extends GetServerSessionOptions,
R = O["callbacks"] extends { session: (...args: any[]) => infer U }
? U
: Session
>(...args: GetServerSessionParams<O>): Promise<R | null> {
if (!deprecatedWarningShown && process.env.NODE_ENV !== "production") {
console.warn(
"`unstable_getServerSession` has been renamed to `getServerSession`."
)
deprecatedWarningShown = true
}
return await getServerSession(...args)
}
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface ProcessEnv {
NEXTAUTH_URL?: string
NEXTAUTH_SECRET?: string
AUTH_SECRET?: string
VERCEL?: "1"
}
}
}

243
node_modules/next-auth/src/next/middleware.ts generated vendored Normal file
View File

@@ -0,0 +1,243 @@
import type { NextMiddleware, NextFetchEvent } from "next/server"
import type { Awaitable, CookieOption, AuthOptions } from ".."
import type { JWT, JWTOptions } from "../jwt"
import { NextResponse, NextRequest } from "next/server"
import { getToken } from "../jwt"
import parseUrl from "../utils/parse-url"
type AuthorizedCallback = (params: {
token: JWT | null
req: NextRequest
}) => Awaitable<boolean>
export interface NextAuthMiddlewareOptions {
/**
* Where to redirect the user in case of an error if they weren't logged in.
* Similar to `pages` in `NextAuth`.
*
* ---
* [Documentation](https://next-auth.js.org/configuration/pages)
*/
pages?: AuthOptions["pages"]
/**
* You can override the default cookie names and options for any of the cookies
* by this middleware. Similar to `cookies` in `NextAuth`.
*
* Useful if the token is stored in not a default cookie.
*
* ---
* [Documentation](https://next-auth.js.org/configuration/options#cookies)
*
* - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options,
* but **may have complex implications** or side effects.
* You should **try to avoid using advanced options** unless you are very comfortable using them.
*
*/
cookies?: Partial<
Record<
keyof Pick<keyof AuthOptions["cookies"], "sessionToken">,
Omit<CookieOption, "options">
>
>
/**
* If a custom jwt `decode` method is set in `[...nextauth].ts`, the same method should be set here also.
*
* ---
* [Documentation](https://next-auth.js.org/configuration/nextjs#custom-jwt-decode-method)
*/
jwt?: Partial<Pick<JWTOptions, "decode">>
callbacks?: {
/**
* Callback that receives the user's JWT payload
* and returns `true` to allow the user to continue.
*
* This is similar to the `signIn` callback in `NextAuthOptions`.
*
* If it returns `false`, the user is redirected to the sign-in page instead
*
* The default is to let the user continue if they have a valid JWT (basic authentication).
*
* How to restrict a page and all of it's subpages for admins-only:
* @example
*
* ```js
* // `middleware.js`
* import { withAuth } from "next-auth/middleware"
*
* export default withAuth({
* callbacks: {
* authorized: ({ token }) => token?.user.isAdmin
* }
* })
*
* export const config = { matcher: ["/admin"] }
*
* ```
*
* ---
* [Documentation](https://next-auth.js.org/configuration/nextjs#middleware) | [`signIn` callback](configuration/callbacks#sign-in-callback)
*/
authorized?: AuthorizedCallback
}
/**
* The same `secret` used in the `NextAuth` configuration.
* Defaults to the `NEXTAUTH_SECRET` environment variable.
*/
secret?: string
}
// TODO: `NextMiddleware` should allow returning `void`
// Simplify when https://github.com/vercel/next.js/pull/38625 is merged.
type NextMiddlewareResult = ReturnType<NextMiddleware> | void // eslint-disable-line @typescript-eslint/no-invalid-void-type
async function handleMiddleware(
req: NextRequest,
options: NextAuthMiddlewareOptions | undefined,
onSuccess?: (token: JWT | null) => Promise<NextMiddlewareResult>
) {
const { pathname, search, origin, basePath } = req.nextUrl
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
const errorPage = options?.pages?.error ?? "/api/auth/error"
const authPath = parseUrl(process.env.NEXTAUTH_URL).path
const publicPaths = ["/_next", "/favicon.ico"]
// Avoid infinite redirects/invalid response
// on paths that never require authentication
if (
`${basePath}${pathname}`.startsWith(authPath) ||
[signInPage, errorPage].includes(pathname) ||
publicPaths.some((p) => pathname.startsWith(p))
) {
return
}
const secret =
options?.secret ?? process.env.NEXTAUTH_SECRET ?? process.env.AUTH_SECRET
if (!secret) {
console.error(
`[next-auth][error][NO_SECRET]`,
`\nhttps://next-auth.js.org/errors#no_secret`
)
const errorUrl = new URL(`${basePath}${errorPage}`, origin)
errorUrl.searchParams.append("error", "Configuration")
return NextResponse.redirect(errorUrl)
}
const token = await getToken({
req,
decode: options?.jwt?.decode,
cookieName: options?.cookies?.sessionToken?.name,
secret,
})
const isAuthorized =
(await options?.callbacks?.authorized?.({ req, token })) ?? !!token
// the user is authorized, let the middleware handle the rest
if (isAuthorized) return await onSuccess?.(token)
// the user is not logged in, redirect to the sign-in page
const signInUrl = new URL(`${basePath}${signInPage}`, origin)
signInUrl.searchParams.append(
"callbackUrl",
`${basePath}${pathname}${search}`
)
return NextResponse.redirect(signInUrl)
}
export interface NextRequestWithAuth extends NextRequest {
nextauth: { token: JWT | null }
}
export type NextMiddlewareWithAuth = (
request: NextRequestWithAuth,
event: NextFetchEvent
) => NextMiddlewareResult | Promise<NextMiddlewareResult>
export type WithAuthArgs =
| [NextRequestWithAuth]
| [NextRequestWithAuth, NextFetchEvent]
| [NextRequestWithAuth, NextAuthMiddlewareOptions]
| [NextMiddlewareWithAuth]
| [NextMiddlewareWithAuth, NextAuthMiddlewareOptions]
| [NextAuthMiddlewareOptions]
| []
/**
* Middleware that checks if the user is authenticated/authorized.
* If if they aren't, they will be redirected to the login page.
* Otherwise, continue.
*
* @example
*
* ```js
* // `middleware.js`
* export { default } from "next-auth/middleware"
* ```
*
* ---
* [Documentation](https://next-auth.js.org/configuration/nextjs#middleware)
*/
export function withAuth(): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
req: NextRequestWithAuth
): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
req: NextRequestWithAuth,
event: NextFetchEvent
): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
req: NextRequestWithAuth,
options: NextAuthMiddlewareOptions
): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
middleware: NextMiddlewareWithAuth,
options: NextAuthMiddlewareOptions
): NextMiddlewareWithAuth
export function withAuth(
middleware: NextMiddlewareWithAuth
): NextMiddlewareWithAuth
export function withAuth(
options: NextAuthMiddlewareOptions
): NextMiddlewareWithAuth
export function withAuth(
...args: WithAuthArgs
): ReturnType<NextMiddlewareWithAuth> | NextMiddlewareWithAuth {
if (!args.length || args[0] instanceof Request) {
// @ts-expect-error
return handleMiddleware(...args)
}
if (typeof args[0] === "function") {
const middleware = args[0]
const options = args[1] as NextAuthMiddlewareOptions | undefined
return async (...args: Parameters<NextMiddlewareWithAuth>) =>
await handleMiddleware(args[0], options, async (token) => {
args[0].nextauth = { token }
return await middleware(...args)
})
}
const options = args[0]
return async (...args: Parameters<NextMiddleware>) =>
await handleMiddleware(args[0], options)
}
export default withAuth

60
node_modules/next-auth/src/next/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,60 @@
import { serialize } from "cookie"
import { Cookie } from "../core/lib/cookie"
import { type ResponseInternal } from "../core"
export function setCookie(res, cookie: Cookie) {
// Preserve any existing cookies that have already been set in the same session
let setCookieHeader = res.getHeader("Set-Cookie") ?? []
// If not an array (i.e. a string with a single cookie) convert it into an array
if (!Array.isArray(setCookieHeader)) {
setCookieHeader = [setCookieHeader]
}
const { name, value, options } = cookie
const cookieHeader = serialize(name, value, options)
setCookieHeader.push(cookieHeader)
res.setHeader("Set-Cookie", setCookieHeader)
}
export async function getBody(
req: Request
): Promise<Record<string, any> | undefined> {
if (!("body" in req) || !req.body || req.method !== "POST") return
const contentType = req.headers.get("content-type")
if (contentType?.includes("application/json")) {
return await req.json()
} else if (contentType?.includes("application/x-www-form-urlencoded")) {
const params = new URLSearchParams(await req.text())
return Object.fromEntries(params)
}
}
export function toResponse(res: ResponseInternal): Response {
const headers = new Headers(
res.headers?.reduce((acc, { key, value }) => {
acc[key] = value
return acc
}, {})
)
res.cookies?.forEach((cookie) => {
const { name, value, options } = cookie
const cookieHeader = serialize(name, value, options)
if (headers.has("Set-Cookie")) headers.append("Set-Cookie", cookieHeader)
else headers.set("Set-Cookie", cookieHeader)
})
let body = res.body
if (headers.get("content-type") === "application/json")
body = JSON.stringify(res.body)
else if (headers.get("content-type") === "application/x-www-form-urlencoded")
body = new URLSearchParams(res.body).toString()
const status = res.redirect ? 302 : res.status ?? 200
const response = new Response(body, { headers, status })
if (res.redirect) response.headers.set("Location", res.redirect)
return response
}

178
node_modules/next-auth/src/providers/42-school.ts generated vendored Normal file
View File

@@ -0,0 +1,178 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface UserData {
id: number
email: string
login: string
first_name: string
last_name: string
usual_full_name: null | string
usual_first_name: null | string
url: string
phone: "hidden" | string | null
displayname: string
image_url: string | null
"staff?": boolean
correction_point: number
pool_month: string | null
pool_year: string | null
location: string | null
wallet: number
anonymize_date: string
created_at: string
updated_at: string | null
alumni: boolean
"is_launched?": boolean
}
export interface CursusUser {
grade: string | null
level: number
skills: Array<{ id: number; name: string; level: number }>
blackholed_at: string | null
id: number
begin_at: string | null
end_at: string | null
cursus_id: number
has_coalition: boolean
created_at: string
updated_at: string | null
user: UserData
cursus: { id: number; created_at: string; name: string; slug: string }
}
export interface ProjectUser {
id: number
occurrence: number
final_mark: number | null
status: "in_progress" | "finished"
"validated?": boolean | null
current_team_id: number
project: {
id: number
name: string
slug: string
parent_id: number | null
}
cursus_ids: number[]
marked_at: string | null
marked: boolean
retriable_at: string | null
created_at: string
updated_at: string | null
}
export interface Achievement {
id: number
name: string
description: string
tier: "none" | "easy" | "medium" | "hard" | "challenge"
kind: "scolarity" | "project" | "pedagogy" | "scolarity"
visible: boolean
image: string | null
nbr_of_success: number | null
users_url: string
}
export interface LanguagesUser {
id: number
language_id: number
user_id: number
position: number
created_at: string
}
export interface TitlesUser {
id: number
user_id: number
title_id: number
selected: boolean
created_at: string
updated_at: string | null
}
export interface ExpertisesUser {
id: number
expertise_id: number
interested: boolean
value: number
contact_me: boolean
created_at: string
user_id: number
}
export interface Campus {
id: number
name: string
time_zone: string
language: {
id: number
name: string
identifier: string
created_at: string
updated_at: string | null
}
users_count: number
vogsphere_id: number
country: string
address: string
zip: string
city: string
website: string
facebook: string
twitter: string
active: boolean
email_extension: string
default_hidden_phone: boolean
}
export interface CampusUser {
id: number
user_id: number
campus_id: number
is_primary: boolean
created_at: string
updated_at: string | null
}
export interface FortyTwoProfile extends UserData, Record<string, any> {
groups: Array<{ id: string; name: string }>
cursus_users: CursusUser[]
projects_users: ProjectUser[]
languages_users: LanguagesUser[]
achievements: Achievement[]
titles: Array<{ id: string; name: string }>
titles_users: TitlesUser[]
partnerships: any[]
patroned: any[]
patroning: any[]
expertises_users: ExpertisesUser[]
roles: Array<{ id: string; name: string }>
campus: Campus[]
campus_users: CampusUser[]
user: any | null
}
export default function FortyTwo<P extends FortyTwoProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "42-school",
name: "42 School",
type: "oauth",
authorization: {
url: "https://api.intra.42.fr/oauth/authorize",
params: { scope: "public" },
},
token: "https://api.intra.42.fr/oauth/token",
userinfo: "https://api.intra.42.fr/v2/me",
profile(profile) {
return {
id: profile.id.toString(),
name: profile.usual_full_name,
email: profile.email,
image: profile.image_url,
}
},
options,
}
}

123
node_modules/next-auth/src/providers/apple.ts generated vendored Normal file
View File

@@ -0,0 +1,123 @@
import { OAuthConfig, OAuthUserConfig } from "."
/**
* See more at:
* [Retrieve the User's Information from Apple ID Servers
](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple#3383773)
*/
export interface AppleProfile extends Record<string, any> {
/**
* The issuer registered claim identifies the principal that issued the identity token.
* Since Apple generates the token, the value is `https://appleid.apple.com`.
*/
iss: "https://appleid.apple.com"
/**
* The audience registered claim identifies the recipient for which the identity token is intended.
* Since the token is meant for your application, the value is the `client_id` from your developer account.
*/
aud: string
/**
* The issued at registered claim indicates the time at which Apple issued the identity token,
* in terms of the number of seconds since Epoch, in UTC.
*/
iat: number
/**
* The expiration time registered identifies the time on or after which the identity token expires,
* in terms of number of seconds since Epoch, in UTC.
* The value must be greater than the current date/time when verifying the token.
*/
exp: number
/**
* The subject registered claim identifies the principal that's the subject of the identity token.
* Since this token is meant for your application, the value is the unique identifier for the user.
*/
sub: string
/**
* A String value used to associate a client session and the identity token.
* This value mitigates replay attacks and is present only if passed during the authorization request.
*/
nonce: string
/**
* A Boolean value that indicates whether the transaction is on a nonce-supported platform.
* If you sent a nonce in the authorization request but don't see the nonce claim in the identity token,
* check this claim to determine how to proceed.
* If this claim returns true, you should treat nonce as mandatory and fail the transaction;
* otherwise, you can proceed treating the nonce as options.
*/
nonce_supported: boolean
/**
* A String value representing the user's email address.
* The email address is either the user's real email address or the proxy address,
* depending on their status private email relay service.
*/
email: string
/**
* A String or Boolean value that indicates whether the service has verified the email.
* The value of this claim is always true, because the servers only return verified email addresses.
* The value can either be a String (`"true"`) or a Boolean (`true`).
*/
email_verified: "true" | true
/**
* A String or Boolean value that indicates whether the email shared by the user is the proxy address.
* The value can either be a String (`"true"` or `"false"`) or a Boolean (`true` or `false`).
*/
is_private_email: boolean | "true" | "false"
/**
* An Integer value that indicates whether the user appears to be a real person.
* Use the value of this claim to mitigate fraud. The possible values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal).
* For more information, see [`ASUserDetectionStatus`](https://developer.apple.com/documentation/authenticationservices/asuserdetectionstatus).
* This claim is present only on iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14 and later;
* the claim isn't present or supported for web-based apps.
*/
real_user_status: 0 | 1 | 2
/**
* A String value representing the transfer identifier used to migrate users to your team.
* This claim is present only during the 60-day transfer period after an you transfer an app.
* For more information, see [Bringing New Apps and Users into Your Team](https://developer.apple.com/documentation/sign_in_with_apple/bringing_new_apps_and_users_into_your_team).
*/
transfer_sub: string
at_hash: string
auth_time: number
}
export default function Apple<P extends AppleProfile>(
options: Omit<OAuthUserConfig<P>, "clientSecret"> & {
/**
* Apple requires the client secret to be a JWT. You can generate one using the following script:
* https://bal.so/apple-gen-secret
*
* Read more: [Creating the Client Secret
](https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens#3262048)
*/
clientSecret: string
}
): OAuthConfig<P> {
return {
id: "apple",
name: "Apple",
type: "oauth",
wellKnown: "https://appleid.apple.com/.well-known/openid-configuration",
authorization: {
params: { scope: "name email", response_mode: "form_post" },
},
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
}
},
checks: ["pkce"],
style: { logo: "/apple.svg", text: "#fff", bg: "#000" },
options,
}
}

37
node_modules/next-auth/src/providers/atlassian.ts generated vendored Normal file
View File

@@ -0,0 +1,37 @@
import type { OAuthConfig, OAuthUserConfig } from "."
interface AtlassianProfile extends Record<string, any> {
account_id: string
name: string
email: string
picture: string
}
export default function Atlassian<P extends AtlassianProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "atlassian",
name: "Atlassian",
type: "oauth",
authorization: {
url: "https://auth.atlassian.com/authorize",
params: {
audience: "api.atlassian.com",
prompt: "consent",
},
},
token: "https://auth.atlassian.com/oauth/token",
userinfo: "https://api.atlassian.com/me",
profile(profile) {
return {
id: profile.account_id,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
style: { logo: "/atlassian.svg", bg: "#fff", text: "#0052cc" },
options,
}
}

32
node_modules/next-auth/src/providers/auth0.ts generated vendored Normal file
View File

@@ -0,0 +1,32 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface Auth0Profile extends Record<string, any> {
sub: string
nickname: string
email: string
picture: string
}
export default function Auth0<P extends Auth0Profile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "auth0",
name: "Auth0",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
type: "oauth",
authorization: { params: { scope: "openid email profile" } },
checks: ["pkce", "state"],
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.nickname,
email: profile.email,
image: profile.picture,
}
},
style: { logo: "/auth0.svg", text: "#fff", bg: "#EB5424" },
options,
}
}

44
node_modules/next-auth/src/providers/authentik.ts generated vendored Normal file
View File

@@ -0,0 +1,44 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface AuthentikProfile extends Record<string, any> {
iss: string
sub: string
aud: string
exp: number
iat: number
auth_time: number
acr: string
c_hash: string
nonce: string
at_hash: string
email: string
email_verified: boolean
name: string
given_name: string
family_name: string
preferred_username: string
nickname: string
groups: string[]
}
export default function Authentik<P extends AuthentikProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "authentik",
name: "Authentik",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
type: "oauth",
authorization: { params: { scope: "openid email profile" } },
checks: ["pkce", "state"],
profile(profile) {
return {
id: profile.sub,
name: profile.name ?? profile.preferred_username,
email: profile.email,
image: profile.picture,
}
},
options,
}
}

48
node_modules/next-auth/src/providers/azure-ad-b2c.ts generated vendored Normal file
View File

@@ -0,0 +1,48 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface AzureB2CProfile extends Record<string, any> {
exp: number
nbf: number
ver: string
iss: string
sub: string
aud: string
iat: number
auth_time: number
oid: string
country: string
name: string
postalCode: string
emails: string[]
tfp: string
}
export default function AzureADB2C<P extends AzureB2CProfile>(
options: OAuthUserConfig<P> & {
primaryUserFlow?: string
tenantId?: string
}
): OAuthConfig<P> {
const { tenantId, primaryUserFlow } = options
const issuer =
options.issuer ??
`https://${tenantId}.b2clogin.com/${tenantId}.onmicrosoft.com/${primaryUserFlow}/v2.0`
return {
id: "azure-ad-b2c",
name: "Azure Active Directory B2C",
type: "oauth",
wellKnown: `${issuer}/.well-known/openid-configuration`,
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.emails[0],
// TODO: Find out how to retrieve the profile picture
image: null,
}
},
style: { logo: "/azure.svg", text: "#fff", bg: "#0072c6" },
options,
}
}

62
node_modules/next-auth/src/providers/azure-ad.ts generated vendored Normal file
View File

@@ -0,0 +1,62 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface AzureADProfile extends Record<string, any> {
sub: string
nickname: string
email: string
picture: string
}
export default function AzureAD<P extends AzureADProfile>(
options: OAuthUserConfig<P> & {
/**
* https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples
* @default 48
*/
profilePhotoSize?: 48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648
/** @default "common" */
tenantId?: string
}
): OAuthConfig<P> {
const tenant = options.tenantId ?? "common"
const profilePhotoSize = options.profilePhotoSize ?? 48
return {
id: "azure-ad",
name: "Azure Active Directory",
type: "oauth",
wellKnown: `https://login.microsoftonline.com/${tenant}/v2.0/.well-known/openid-configuration?appid=${options.clientId}`,
authorization: {
params: {
scope: "openid profile email",
},
},
async profile(profile, tokens) {
// https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples
const response = await fetch(
`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`,
{ headers: { Authorization: `Bearer ${tokens.access_token}` } }
)
// Confirm that profile photo was returned
let image
// TODO: Do this without Buffer
if (response.ok && typeof Buffer !== "undefined") {
try {
const pictureBuffer = await response.arrayBuffer()
const pictureBase64 = Buffer.from(pictureBuffer).toString("base64")
image = `data:image/jpeg;base64, ${pictureBase64}`
} catch {}
}
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: image ?? null,
}
},
style: { logo: "/azure.svg", text: "#fff", bg: "#0072c6" },
options,
}
}

32
node_modules/next-auth/src/providers/battlenet.ts generated vendored Normal file
View File

@@ -0,0 +1,32 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface BattleNetProfile extends Record<string, any> {
sub: string
battle_tag: string
}
/** See the [available regions](https://develop.battle.net/documentation/guides/regionality-and-apis) */
export type BattleNetIssuer =
| "https://www.battlenet.com.cn/oauth"
| `https://${"us" | "eu" | "kr" | "tw"}.battle.net/oauth`
export default function BattleNet<P extends BattleNetProfile>(
options: OAuthUserConfig<P> & { issuer: BattleNetIssuer }
): OAuthConfig<P> {
return {
id: "battlenet",
name: "Battle.net",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
profile(profile) {
return {
id: profile.sub,
name: profile.battle_tag,
email: null,
image: null,
}
},
style: { logo: "/battlenet.svg", bg: "#148eff", text: "#fff" },
options,
}
}

25
node_modules/next-auth/src/providers/box.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/** @type {import(".").OAuthProvider} */
export default function Box(options) {
return {
id: "box",
name: "Box",
type: "oauth",
authorization: "https://account.box.com/api/oauth2/authorize",
token: "https://api.box.com/oauth2/token",
userinfo: "https://api.box.com/2.0/users/me",
profile(profile) {
return {
id: profile.id,
name: profile.name,
email: profile.login,
image: profile.avatar_url,
}
},
style: {
logo: "/box.svg",
bg: "#0075C9",
text: "#fff",
},
options,
}
}

37
node_modules/next-auth/src/providers/boxyhq-saml.ts generated vendored Normal file
View File

@@ -0,0 +1,37 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface BoxyHQSAMLProfile extends Record<string, any> {
id: string
email: string
firstName?: string
lastName?: string
}
export default function SAMLJackson<P extends BoxyHQSAMLProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "boxyhq-saml",
name: "BoxyHQ SAML",
type: "oauth",
version: "2.0",
checks: ["pkce", "state"],
authorization: {
url: `${options.issuer}/api/oauth/authorize`,
params: {
provider: "saml",
},
},
token: `${options.issuer}/api/oauth/token`,
userinfo: `${options.issuer}/api/oauth/userinfo`,
profile(profile) {
return {
id: profile.id,
email: profile.email,
name: [profile.firstName, profile.lastName].filter(Boolean).join(" "),
image: null,
}
},
options,
}
}

25
node_modules/next-auth/src/providers/bungie.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/** @type {import(".").OAuthProvider} */
export default function Bungie(options) {
return {
id: "bungie",
name: "Bungie",
type: "oauth",
authorization: "https://www.bungie.net/en/OAuth/Authorize?reauth=true",
token: "https://www.bungie.net/platform/app/oauth/token/",
userinfo:
"https://www.bungie.net/platform/User/GetBungieAccount/{membershipId}/254/",
profile(profile) {
const { bungieNetUser: user } = profile.Response
return {
id: user.membershipId,
name: user.displayName,
email: null,
image: `https://www.bungie.net${
user.profilePicturePath.startsWith("/") ? "" : "/"
}${user.profilePicturePath}`,
}
},
options,
}
}

30
node_modules/next-auth/src/providers/cognito.ts generated vendored Normal file
View File

@@ -0,0 +1,30 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface CognitoProfile extends Record<string, any> {
sub: string
name: string
email: string
picture: string
}
export default function Cognito<P extends CognitoProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "cognito",
name: "Cognito",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
style: { logo: "/cognito.svg", bg: "#fff", text: "#C17B9E" },
options,
}
}

21
node_modules/next-auth/src/providers/coinbase.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
/** @type {import(".").OAuthProvider} */
export default function Coinbase(options) {
return {
id: "coinbase",
name: "Coinbase",
type: "oauth",
authorization:
"https://www.coinbase.com/oauth/authorize?scope=wallet:user:email+wallet:user:read",
token: "https://api.coinbase.com/oauth/token",
userinfo: "https://api.coinbase.com/v2/user",
profile(profile) {
return {
id: profile.data.id,
name: profile.data.name,
email: profile.data.email,
image: profile.data.avatar_url,
}
},
options,
}
}

45
node_modules/next-auth/src/providers/credentials.ts generated vendored Normal file
View File

@@ -0,0 +1,45 @@
import type { RequestInternal } from "../core"
import type { CommonProviderOptions } from "."
import type { User, Awaitable } from ".."
export interface CredentialInput {
label?: string
type?: string
value?: string
placeholder?: string
}
export interface CredentialsConfig<
C extends Record<string, CredentialInput> = Record<string, CredentialInput>
> extends CommonProviderOptions {
type: "credentials"
credentials: C
authorize: (
credentials: Record<keyof C, string> | undefined,
req: Pick<RequestInternal, "body" | "query" | "headers" | "method">
) => Awaitable<User | null>
}
export type CredentialsProvider = <C extends Record<string, CredentialInput>>(
options: Partial<CredentialsConfig<C>>
) => CredentialsConfig<C>
export type CredentialsProviderType = "Credentials"
type UserCredentialsConfig<C extends Record<string, CredentialInput>> = Partial<
Omit<CredentialsConfig<C>, "options">
> &
Pick<CredentialsConfig<C>, "authorize" | "credentials">
export default function Credentials<
C extends Record<string, CredentialInput> = Record<string, CredentialInput>
>(options: UserCredentialsConfig<C>): CredentialsConfig<C> {
return {
id: "credentials",
name: "Credentials",
type: "credentials",
credentials: {} as any,
authorize: () => null,
options,
}
}

50
node_modules/next-auth/src/providers/discord.ts generated vendored Normal file
View File

@@ -0,0 +1,50 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface DiscordProfile extends Record<string, any> {
accent_color: number
avatar: string
banner: string
banner_color: string
discriminator: string
email: string
flags: number
id: string
image_url: string
locale: string
mfa_enabled: boolean
premium_type: number
public_flags: number
username: string
verified: boolean
}
export default function Discord<P extends DiscordProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "discord",
name: "Discord",
type: "oauth",
authorization:
"https://discord.com/api/oauth2/authorize?scope=identify+email",
token: "https://discord.com/api/oauth2/token",
userinfo: "https://discord.com/api/users/@me",
profile(profile) {
if (profile.avatar === null) {
const defaultAvatarNumber = parseInt(profile.discriminator) % 5
profile.image_url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarNumber}.png`
} else {
const format = profile.avatar.startsWith("a_") ? "gif" : "png"
profile.image_url = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`
}
return {
id: profile.id,
name: profile.username,
email: profile.email,
image: profile.image_url,
}
},
style: { logo: "/discord.svg", bg: "#5865F2", text: "#fff" },
options,
}
}

51
node_modules/next-auth/src/providers/dropbox.js generated vendored Normal file
View File

@@ -0,0 +1,51 @@
/**
* @param {import("../core").Provider} options
* @example
*
* ```js
* // pages/api/auth/[...nextauth].js
* import Providers from `next-auth/providers`
* ...
* providers: [
* Providers.Dropbox({
* clientId: process.env.DROPBOX_CLIENT_ID,
* clientSecret: process.env.DROPBOX_CLIENT_SECRET
* })
* ]
* ...
*
* // pages/index
* import { signIn } from "next-auth/react"
* ...
* <button onClick={() => signIn("dropbox")}>
* Sign in
* </button>
* ...
* ```
* *Resources:*
* - [NextAuth.js Documentation](https://next-auth.js.org/providers/dropbox)
* - [Dropbox Documentation](https://developers.dropbox.com/oauth-guide)
* - [Configuration](https://www.dropbox.com/developers/apps)
*/
/** @type {import(".").OAuthProvider} */
export default function Dropbox(options) {
return {
id: "dropbox",
name: "Dropbox",
type: "oauth",
authorization:
"https://www.dropbox.com/oauth2/authorize?token_access_type=offline&scope=account_info.read",
token: "https://api.dropboxapi.com/oauth2/token",
userinfo: "https://api.dropboxapi.com/2/users/get_current_account",
profile(profile) {
return {
id: profile.account_id,
name: profile.name.display_name,
email: profile.email,
image: profile.profile_photo_url,
}
},
checks: ["state", "pkce"],
options,
}
}

View File

@@ -0,0 +1,31 @@
import type { OAuthConfig, OAuthUserConfig } from "./oauth"
export interface DuendeISUser extends Record<string, any> {
email: string
id: string
name: string
verified: boolean
}
export default function DuendeIdentityServer6<P extends DuendeISUser>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "duende-identityserver6",
name: "DuendeIdentityServer6",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid profile email" } },
checks: ["pkce", "state"],
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
}
},
options,
}
}

209
node_modules/next-auth/src/providers/email.ts generated vendored Normal file
View File

@@ -0,0 +1,209 @@
import { Transport, TransportOptions, createTransport } from "nodemailer"
import * as JSONTransport from "nodemailer/lib/json-transport/index.js"
import * as SendmailTransport from "nodemailer/lib/sendmail-transport/index.js"
import * as SESTransport from "nodemailer/lib/ses-transport/index.js"
import * as SMTPPool from "nodemailer/lib/smtp-pool/index.js"
import * as SMTPTransport from "nodemailer/lib/smtp-transport/index.js"
import * as StreamTransport from "nodemailer/lib/stream-transport/index.js"
import type { Awaitable } from ".."
import type { CommonProviderOptions } from "."
import type { Theme } from "../core/types"
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html for the string
type AllTransportOptions =
| string
| SMTPTransport
| SMTPTransport.Options
| SMTPPool
| SMTPPool.Options
| SendmailTransport
| SendmailTransport.Options
| StreamTransport
| StreamTransport.Options
| JSONTransport
| JSONTransport.Options
| SESTransport
| SESTransport.Options
| Transport<any>
| TransportOptions
export interface SendVerificationRequestParams {
identifier: string
url: string
expires: Date
provider: EmailConfig
token: string
theme: Theme
}
export interface EmailUserConfig {
server?: AllTransportOptions
type?: "email"
/** @default "NextAuth <no-reply@example.com>" */
from?: string
/**
* How long until the e-mail can be used to log the user in,
* in seconds. Defaults to 1 day
* @default 86400
*/
maxAge?: number
/** [Documentation](https://next-auth.js.org/providers/email#customizing-emails) */
sendVerificationRequest?: (
params: SendVerificationRequestParams
) => Awaitable<void>
/**
* By default, we are generating a random verification token.
* You can make it predictable or modify it as you like with this method.
* @example
* ```js
* Providers.Email({
* async generateVerificationToken() {
* return "ABC123"
* }
* })
* ```
* [Documentation](https://next-auth.js.org/providers/email#customizing-the-verification-token)
*/
generateVerificationToken?: () => Awaitable<string>
/** If defined, it is used to hash the verification token when saving to the database . */
secret?: string
/**
* Normalizes the user input before sending the verification request.
*
* ⚠️ Always make sure this method returns a single email address.
*
* @note Technically, the part of the email address local mailbox element
* (everything before the `@` symbol) should be treated as 'case sensitive'
* according to RFC 2821, but in practice this causes more problems than
* it solves, e.g.: when looking up users by e-mail from databases.
* By default, we treat email addresses as all lower case,
* but you can override this function to change this behavior.
*
* [Documentation](https://next-auth.js.org/providers/email#normalizing-the-e-mail-address) | [RFC 2821](https://tools.ietf.org/html/rfc2821) | [Email syntax](https://en.wikipedia.org/wiki/Email_address#Syntax)
*/
normalizeIdentifier?: (identifier: string) => string
}
export interface EmailConfig extends CommonProviderOptions {
// defaults
id: "email"
type: "email"
name: "Email"
server: AllTransportOptions
from: string
maxAge: number
sendVerificationRequest: (
params: SendVerificationRequestParams
) => Awaitable<void>
/**
* This is copied into EmailConfig in parseProviders() don't use elsewhere
*/
options: EmailUserConfig
// user options
// TODO figure out a better way than copying from EmailUserConfig
secret?: string
generateVerificationToken?: () => Awaitable<string>
normalizeIdentifier?: (identifier: string) => string
}
export type EmailProvider = (options: EmailUserConfig) => EmailConfig
// TODO: Rename to Token provider
// when started working on https://github.com/nextauthjs/next-auth/discussions/1465
export type EmailProviderType = "Email"
export default function Email(options: EmailUserConfig): EmailConfig {
return {
id: "email",
type: "email",
name: "Email",
// Server can be an SMTP connection string or a nodemailer config object
server: { host: "localhost", port: 25, auth: { user: "", pass: "" } },
from: "NextAuth <no-reply@example.com>",
maxAge: 24 * 60 * 60,
async sendVerificationRequest(params) {
const { identifier, url, provider, theme } = params
const { host } = new URL(url)
const transport = createTransport(provider.server)
const result = await transport.sendMail({
to: identifier,
from: provider.from,
subject: `Sign in to ${host}`,
text: text({ url, host }),
html: html({ url, host, theme }),
})
const failed = result.rejected.concat(result.pending).filter(Boolean)
if (failed.length) {
throw new Error(`Email (${failed.join(", ")}) could not be sent`)
}
},
options,
}
}
/**
* Email HTML body
* Insert invisible space into domains from being turned into a hyperlink by email
* clients like Outlook and Apple mail, as this is confusing because it seems
* like they are supposed to click on it to sign in.
*
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
*/
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, "&#8203;.")
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const brandColor = theme.brandColor || "#346df1"
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const buttonText = theme.buttonText || "#fff"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText,
}
return `
<body style="background: ${color.background};">
<table width="100%" border="0" cellspacing="20" cellpadding="0"
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
<tr>
<td align="center"
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
Sign in to <strong>${escapedHost}</strong>
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
target="_blank"
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
in</a></td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center"
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
If you did not request this email you can safely ignore it.
</td>
</tr>
</table>
</body>
`
}
/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}

33
node_modules/next-auth/src/providers/eveonline.ts generated vendored Normal file
View File

@@ -0,0 +1,33 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface EVEOnlineProfile extends Record<string, any> {
CharacterID: number
CharacterName: string
ExpiresOn: string
Scopes: string
TokenType: string
CharacterOwnerHash: string
IntellectualProperty: string
}
export default function EVEOnline<P extends EVEOnlineProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "eveonline",
name: "EVE Online",
type: "oauth",
authorization: "https://login.eveonline.com/v2/oauth/authorize?scope=publicData",
token: "https://login.eveonline.com/v2/oauth/token",
userinfo: "https://login.eveonline.com/oauth/verify",
profile(profile) {
return {
id: String(profile.CharacterID),
name: profile.CharacterName,
email: null,
image: `https://image.eveonline.com/Character/${profile.CharacterID}_128.jpg`,
}
},
options,
}
}

47
node_modules/next-auth/src/providers/facebook.ts generated vendored Normal file
View File

@@ -0,0 +1,47 @@
import type { OAuthConfig, OAuthUserConfig } from "."
interface FacebookPictureData {
url: string
}
interface FacebookPicture {
data: FacebookPictureData
}
export interface FacebookProfile extends Record<string, any> {
id: string
picture: FacebookPicture
}
export default function Facebook<P extends FacebookProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "facebook",
name: "Facebook",
type: "oauth",
authorization: "https://www.facebook.com/v11.0/dialog/oauth?scope=email",
token: "https://graph.facebook.com/oauth/access_token",
userinfo: {
url: "https://graph.facebook.com/me",
// https://developers.facebook.com/docs/graph-api/reference/user/#fields
params: { fields: "id,name,email,picture" },
async request({ tokens, client, provider }) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return await client.userinfo(tokens.access_token!, {
// @ts-expect-error
params: provider.userinfo?.params,
})
},
},
profile(profile: P) {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.picture.data.url,
}
},
style: { logo: "/facebook.svg", bg: "#006aff", text: "#fff" },
options,
}
}

25
node_modules/next-auth/src/providers/faceit.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/** @type {import(".").OAuthProvider} */
export default function FACEIT(options) {
return {
id: "faceit",
name: "FACEIT",
type: "oauth",
authorization: "https://accounts.faceit.com/accounts?redirect_popup=true",
headers: {
Authorization: `Basic ${Buffer.from(
`${options.clientId}:${options.clientSecret}`
).toString("base64")}`,
},
token: "https://api.faceit.com/auth/v1/oauth/token",
userinfo: "https://api.faceit.com/auth/v1/resources/userinfo",
profile(profile) {
return {
id: profile.guid,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
options,
}
}

60
node_modules/next-auth/src/providers/foursquare.js generated vendored Normal file
View File

@@ -0,0 +1,60 @@
import { get } from "https"
import { once } from "events"
/** @type {import("src/providers").OAuthProvider} */
/** @type {import(".").OAuthProvider} */
export default function Foursquare(options) {
const { apiVersion = "20230131" } = options
return {
id: "foursquare",
name: "Foursquare",
type: "oauth",
authorization: "https://foursquare.com/oauth2/authenticate",
token: "https://foursquare.com/oauth2/access_token",
userinfo: {
async request({ tokens }) {
const url = new URL("https://api.foursquare.com/v2/users/self")
url.searchParams.append("v", apiVersion)
url.searchParams.append("oauth_token", tokens.access_token)
const req = get(url, { timeout: 3500 })
const [response] = await Promise.race([
once(req, "response"),
once(req, "timeout"),
])
// timeout reached
if (!response) {
req.destroy()
throw new Error("HTTP Request Timed Out")
}
if (response.statusCode !== 200) {
throw new Error("Expected 200 OK from the userinfo endpoint")
}
const parts = []
for await (const part of response) {
parts.push(part)
}
return JSON.parse(Buffer.concat(parts))
},
},
profile({ response: { user: profile } }) {
return {
id: profile.id,
name: `${profile.firstName} ${profile.lastName}`,
email: profile.contact.email,
image: profile.photo
? `${profile.photo.prefix}original${profile.photo.suffix}`
: null,
}
},
style: {
logo: "/foursquare.svg",
text: "#fff",
bg: "#000",
},
options,
}
}

27
node_modules/next-auth/src/providers/freshbooks.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/** @type {import(".").OAuthProvider} */
export default function Freshbooks(options) {
return {
id: "freshbooks",
name: "Freshbooks",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.freshbooks.com/auth/oauth/token",
authorizationUrl:
"https://auth.freshbooks.com/service/auth/oauth/authorize?response_type=code",
profileUrl: "https://api.freshbooks.com/auth/api/v1/users/me",
async profile(profile) {
return {
id: profile.response.id,
name: `${profile.response.first_name} ${profile.response.last_name}`,
email: profile.response.email,
}
},
style: {
logo: "/freshbooks.svg",
text: "#fff",
bg: "#0075dd",
},
...options,
}
}

50
node_modules/next-auth/src/providers/fusionauth.ts generated vendored Normal file
View File

@@ -0,0 +1,50 @@
import { OAuthConfig, OAuthUserConfig } from "./oauth"
/** This is the default openid signature returned from FusionAuth
* it can be customized using [lambda functions](https://fusionauth.io/docs/v1/tech/lambdas)
*/
export interface FusionAuthProfile extends Record<string, any> {
aud: string
exp: number
iat: number
iss: string
sub: string
jti: string
authenticationType: string
email: string
email_verified: boolean
preferred_username: string
at_hash: string
c_hash: string
scope: string
sid: string
}
export default function FusionAuth<P extends FusionAuthProfile>(
// tenantId only needed if there is more than one tenant configured on the server
options: OAuthUserConfig<P> & { tenantId?: string }
): OAuthConfig<P> {
return {
id: "fusionauth",
name: "FusionAuth",
type: "oauth",
wellKnown: options?.tenantId
? `${options.issuer}/.well-known/openid-configuration?tenantId=${options.tenantId}`
: `${options.issuer}/.well-known/openid-configuration`,
authorization: {
params: {
scope: "openid offline_access",
...(options?.tenantId && { tenantId: options.tenantId }),
},
},
checks: ["pkce", "state"],
profile(profile) {
return {
id: profile.sub,
email: profile.email,
name: profile?.preferred_username,
}
},
options,
}
}

105
node_modules/next-auth/src/providers/github.ts generated vendored Normal file
View File

@@ -0,0 +1,105 @@
import type { OAuthConfig, OAuthUserConfig } from "."
/** @see https://docs.github.com/en/rest/users/users#get-the-authenticated-user */
export interface GithubProfile extends Record<string, any> {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string | null
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
name: string | null
company: string | null
blog: string | null
location: string | null
email: string | null
hireable: boolean | null
bio: string | null
twitter_username?: string | null
public_repos: number
public_gists: number
followers: number
following: number
created_at: string
updated_at: string
private_gists?: number
total_private_repos?: number
owned_private_repos?: number
disk_usage?: number
suspended_at?: string | null
collaborators?: number
two_factor_authentication: boolean
plan?: {
collaborators: number
name: string
space: number
private_repos: number
}
}
export interface GithubEmail extends Record<string, any> {
email: string
primary: boolean
verified: boolean
visibility: "public" | "private"
}
export default function Github<P extends GithubProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "github",
name: "GitHub",
type: "oauth",
issuer: `https://github.com/login/oauth`,
authorization: {
url: "https://github.com/login/oauth/authorize",
params: { scope: "read:user user:email" },
},
token: "https://github.com/login/oauth/access_token",
userinfo: {
url: "https://api.github.com/user",
async request({ client, tokens }) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const profile = await client.userinfo(tokens.access_token!)
if (!profile.email) {
// If the user does not have a public email, get another via the GitHub API
// See https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user
const res = await fetch("https://api.github.com/user/emails", {
headers: { Authorization: `token ${tokens.access_token}` },
})
if (res.ok) {
const emails: GithubEmail[] = await res.json()
profile.email = (emails.find((e) => e.primary) ?? emails[0]).email
}
}
return profile
},
},
profile(profile) {
return {
id: profile.id.toString(),
name: profile.name ?? profile.login,
email: profile.email,
image: profile.avatar_url,
}
},
style: { logo: "/github.svg", bg: "#24292f", text: "#fff" },
options,
}
}

73
node_modules/next-auth/src/providers/gitlab.ts generated vendored Normal file
View File

@@ -0,0 +1,73 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface GitLabProfile extends Record<string, any> {
id: number
username: string
email: string
name: string
state: string
avatar_url: string
web_url: string
created_at: string
bio: string
location?: string
public_email: string
skype: string
linkedin: string
twitter: string
website_url: string
organization: string
job_title: string
pronouns: string
bot: boolean
work_information?: string
followers: number
following: number
local_time: string
last_sign_in_at: string
confirmed_at: string
theme_id: number
last_activity_on: string
color_scheme_id: number
projects_limit: number
current_sign_in_at: string
identities: Array<{
provider: string
extern_uid: string
}>
can_create_group: boolean
can_create_project: boolean
two_factor_enabled: boolean
external: boolean
private_profile: boolean
commit_email: string
shared_runners_minutes_limit: number
extra_shared_runners_minutes_limit: number
}
export default function GitLab<P extends GitLabProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "gitlab",
name: "GitLab",
type: "oauth",
authorization: {
url: "https://gitlab.com/oauth/authorize",
params: { scope: "read_user" },
},
token: "https://gitlab.com/oauth/token",
userinfo: "https://gitlab.com/api/v4/user",
checks: ["pkce", "state"],
profile(profile) {
return {
id: profile.id.toString(),
name: profile.name ?? profile.username,
email: profile.email,
image: profile.avatar_url,
}
},
style: { logo: "/gitlab.svg", bg: "#FC6D26", text: "#fff" },
options,
}
}

43
node_modules/next-auth/src/providers/google.ts generated vendored Normal file
View File

@@ -0,0 +1,43 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface GoogleProfile extends Record<string, any> {
aud: string
azp: string
email: string
email_verified: boolean
exp: number
family_name: string
given_name: string
hd: string
iat: number
iss: string
jti: string
name: string
nbf: number
picture: string
sub: string
}
export default function Google<P extends GoogleProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "google",
name: "Google",
type: "oauth",
wellKnown: "https://accounts.google.com/.well-known/openid-configuration",
authorization: { params: { scope: "openid email profile" } },
idToken: true,
checks: ["pkce", "state"],
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
style: { logo: "/google.svg", bg: "#fff", text: "#000" },
options,
}
}

70
node_modules/next-auth/src/providers/hubspot.ts generated vendored Normal file
View File

@@ -0,0 +1,70 @@
import type { OAuthConfig, OAuthUserConfig } from "."
interface HubSpotProfile extends Record<string, any> {
// TODO: figure out additional fields, for now using
// https://legacydocs.hubspot.com/docs/methods/oauth2/get-access-token-information
user: string
user_id: string
hub_domain: string
hub_id: string
}
const HubSpotConfig = {
authorizationUrl: "https://app.hubspot.com/oauth/authorize",
tokenUrl: "https://api.hubapi.com/oauth/v1/token",
profileUrl: "https://api.hubapi.com/oauth/v1/access-tokens",
}
export default function HubSpot<P extends HubSpotProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "hubspot",
name: "HubSpot",
type: "oauth",
...HubSpotConfig,
authorization: {
url: HubSpotConfig.authorizationUrl,
params: {
scope: "oauth",
client_id: options.clientId,
},
},
client: {
token_endpoint_auth_method: "client_secret_post",
},
token: HubSpotConfig.tokenUrl,
userinfo: {
url: HubSpotConfig.profileUrl,
async request(context) {
const url = `${HubSpotConfig.profileUrl}/${context.tokens.access_token}`
const response = await fetch(url, {
headers: {
"Content-Type": "application/json",
},
method: "GET",
})
return await response.json()
},
},
profile(profile) {
return {
id: profile.user_id,
name: profile.user,
email: profile.user,
// TODO: get image from profile once it's available
// Details available https://community.hubspot.com/t5/APIs-Integrations/Profile-photo-is-not-retrieved-with-User-API/m-p/325521
image: null,
}
},
style: { logo: "/hubspot.svg", bg: "#ff7a59", text: "#fff" },
options,
}
}

View File

@@ -0,0 +1,21 @@
/** @type {import(".").OAuthProvider} */
export default function IdentityServer4(options) {
return {
id: "identity-server4",
name: "IdentityServer4",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid profile email" } },
checks: ["pkce", "state"],
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
}
},
options,
}
}

41
node_modules/next-auth/src/providers/index.ts generated vendored Normal file
View File

@@ -0,0 +1,41 @@
import type { OAuthConfig, OAuthProvider, OAuthProviderType } from "./oauth"
import type { EmailConfig, EmailProvider, EmailProviderType } from "./email"
import type {
CredentialsConfig,
CredentialsProvider,
CredentialsProviderType,
} from "./credentials"
export * from "./oauth"
export * from "./email"
export * from "./credentials"
export type ProviderType = "oauth" | "email" | "credentials"
export interface CommonProviderOptions {
id: string
name: string
type: ProviderType
options?: any
}
export type Provider = OAuthConfig<any> | EmailConfig | CredentialsConfig
export type BuiltInProviders = Record<OAuthProviderType, OAuthProvider> &
Record<CredentialsProviderType, CredentialsProvider> &
Record<EmailProviderType, EmailProvider>
export type AppProviders = Array<
Provider | ReturnType<BuiltInProviders[keyof BuiltInProviders]>
>
export interface AppProvider extends CommonProviderOptions {
signinUrl: string
callbackUrl: string
}
export type RedirectableProviderType = "email" | "credentials"
export type BuiltInProviderType = RedirectableProviderType | OAuthProviderType

56
node_modules/next-auth/src/providers/instagram.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
/**
* @type {import("src/providers").OAuthProvider} options
* @example
*
* ```js
* // pages/api/auth/[...nextauth].js
* import Providers from `next-auth/providers`
* ...
* providers: [
* Providers.Instagram({
* clientId: process.env.INSTAGRAM_CLIENT_ID,
* clientSecret: process.env.INSTAGRAM_CLIENT_SECRET
* })
* ]
* ...
*
* // pages/index
* import { signIn } from "next-auth/react"
* ...
* <button onClick={() => signIn("instagram")}>
* Sign in
* </button>
* ...
* ```
* [NextAuth.js Documentation](https://next-auth.js.org/providers/instagram) | [Instagram Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/getting-started) | [Configuration](https://developers.facebook.com/apps)
*/
/** @type {import(".").OAuthProvider} */
export default function Instagram(options) {
return {
id: "instagram",
name: "Instagram",
type: "oauth",
authorization:
"https://api.instagram.com/oauth/authorize?scope=user_profile",
token: "https://api.instagram.com/oauth/access_token",
userinfo:
"https://graph.instagram.com/me?fields=id,username,account_type,name",
client: {
token_endpoint_auth_method: "client_secret_post",
},
async profile(profile) {
return {
id: profile.id,
name: profile.username,
email: null,
image: null,
}
},
style: {
logo: "/instagram.svg",
bg: "#fff",
text: "#000",
},
options,
}
}

92
node_modules/next-auth/src/providers/kakao.ts generated vendored Normal file
View File

@@ -0,0 +1,92 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export type DateTime = string
export type Gender = "female" | "male"
export type AgeRange =
| "1-9"
| "10-14"
| "15-19"
| "20-29"
| "30-39"
| "40-49"
| "50-59"
| "60-69"
| "70-79"
| "80-89"
| "90-"
/**
* https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
* type from : https://gist.github.com/ziponia/cdce1ebd88f979b2a6f3f53416b56a77
*/
export interface KakaoProfile extends Record<string, any> {
id: number
has_signed_up?: boolean
connected_at?: DateTime
synched_at?: DateTime
properties?: {
id?: string
status?: string
registered_at?: DateTime
msg_blocked?: boolean
nickname?: string
profile_image?: string
thumbnail_image?: string
}
kakao_account?: {
profile_needs_agreement?: boolean
profile_nickname_needs_agreement?: boolean
profile_image_needs_agreement?: boolean
profile?: {
nickname?: string
thumbnail_image_url?: string
profile_image_url?: string
is_default_image?: boolean
}
name_needs_agreement?: boolean
name?: string
email_needs_agreement?: boolean
is_email_valid?: boolean
is_email_verified?: boolean
email?: string
age_range_needs_agreement?: boolean
age_range?: AgeRange
birthyear_needs_agreement?: boolean
birthyear?: string
birthday_needs_agreement?: boolean
birthday?: string
birthday_type?: string
gender_needs_agreement?: boolean
gender?: Gender
phone_number_needs_agreement?: boolean
phone_number?: string
ci_needs_agreement?: boolean
ci?: string
ci_authenticated_at?: DateTime
}
}
export default function Kakao<P extends KakaoProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "kakao",
name: "Kakao",
type: "oauth",
authorization: "https://kauth.kakao.com/oauth/authorize?scope",
token: "https://kauth.kakao.com/oauth/token",
userinfo: "https://kapi.kakao.com/v2/user/me",
client: {
token_endpoint_auth_method: "client_secret_post",
},
profile(profile) {
return {
id: String(profile.id),
name: profile.kakao_account?.profile?.nickname,
email: profile.kakao_account?.email,
image: profile.kakao_account?.profile?.profile_image_url,
}
},
options,
}
}

49
node_modules/next-auth/src/providers/keycloak.ts generated vendored Normal file
View File

@@ -0,0 +1,49 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface KeycloakProfile extends Record<string, any> {
exp: number
iat: number
auth_time: number
jti: string
iss: string
aud: string
sub: string
typ: string
azp: string
session_state: string
at_hash: string
acr: string
sid: string
email_verified: boolean
name: string
preferred_username: string
given_name: string
family_name: string
email: string
picture: string
user: any
}
export default function Keycloak<P extends KeycloakProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "keycloak",
name: "Keycloak",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
type: "oauth",
authorization: { params: { scope: "openid email profile" } },
checks: ["pkce", "state"],
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name ?? profile.preferred_username,
email: profile.email,
image: profile.picture,
}
},
style: { logo: "/keycloak.svg", bg: "#fff", text: "#000" },
options,
}
}

39
node_modules/next-auth/src/providers/line.ts generated vendored Normal file
View File

@@ -0,0 +1,39 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface LineProfile extends Record<string, any> {
iss: string
sub: string
aud: string
exp: number
iat: number
amr: string[]
name: string
picture: string
user: any
}
export default function LINE<P extends LineProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "line",
name: "LINE",
type: "oauth",
authorization: { params: { scope: "openid profile" } },
idToken: true,
wellKnown: "https://access.line.me/.well-known/openid-configuration",
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
}
},
client: {
id_token_signed_response_alg: "HS256",
},
style: { logo: "/line.svg", bg: "#00C300", text: "#fff" },
options,
}
}

61
node_modules/next-auth/src/providers/linkedin.ts generated vendored Normal file
View File

@@ -0,0 +1,61 @@
import type { OAuthConfig, OAuthUserConfig } from "."
interface Identifier {
identifier: string
}
interface Element {
identifiers?: Identifier[]
}
export interface LinkedInProfile extends Record<string, any> {
id: string
localizedFirstName: string
localizedLastName: string
profilePicture: {
"displayImage~": {
elements?: Element[]
}
}
}
export default function LinkedIn<P extends LinkedInProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "linkedin",
name: "LinkedIn",
type: "oauth",
authorization: {
url: "https://www.linkedin.com/oauth/v2/authorization",
params: { scope: "openid profile email" },
},
token: "https://www.linkedin.com/oauth/v2/accessToken",
client: {
token_endpoint_auth_method: "client_secret_post",
},
userinfo: {
url: "https://api.linkedin.com/v2/me",
params: {
projection: `(id,localizedFirstName,localizedLastName,profilePicture(displayImage~digitalmediaAsset:playableStreams))`,
},
},
async profile(profile, tokens) {
const emailResponse = await fetch(
"https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))",
{ headers: { Authorization: `Bearer ${tokens.access_token}` } }
)
const emailData = await emailResponse.json()
return {
id: profile.id,
name: `${profile.localizedFirstName} ${profile.localizedLastName}`,
email: emailData?.elements?.[0]?.["handle~"]?.emailAddress,
image:
profile.profilePicture?.["displayImage~"]?.elements?.[0]
?.identifiers?.[0]?.identifier,
}
},
style: { logo: "/linkedin.svg", bg: "#069", text: "#fff" },
options,
}
}

21
node_modules/next-auth/src/providers/mailchimp.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
/** @type {import(".").OAuthProvider} */
export default function Mailchimp(options) {
return {
id: "mailchimp",
name: "Mailchimp",
type: "oauth",
authorization: "https://login.mailchimp.com/oauth2/authorize",
token: "https://login.mailchimp.com/oauth2/token",
userinfo: "https://login.mailchimp.com/oauth2/metadata",
profile(profile) {
return {
id: profile.login.login_id,
name: profile.accountname,
email: profile.login.email,
image: null,
}
},
style: { logo: "/mailchimp.svg", bg: "#000", text: "#fff" },
options,
}
}

20
node_modules/next-auth/src/providers/mailru.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import(".").OAuthProvider} */
export default function MailRu(options) {
return {
id: "mailru",
name: "Mail.ru",
type: "oauth",
authorization: "https://oauth.mail.ru/login?scope=userinfo",
token: "https://oauth.mail.ru/token",
userinfo: "https://oauth.mail.ru/userinfo",
profile(profile) {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: profile.image,
}
},
options,
}
}

20
node_modules/next-auth/src/providers/medium.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import(".").OAuthProvider} */
export default function Medium(options) {
return {
id: "medium",
name: "Medium",
type: "oauth",
authorization: "https://medium.com/m/oauth/authorize?scope=basicProfile",
token: "https://api.medium.com/v1/tokens",
userinfo: "https://api.medium.com/v1/me",
profile(profile) {
return {
id: profile.data.id,
name: profile.data.name,
email: null,
image: profile.data.imageUrl,
}
},
options,
}
}

42
node_modules/next-auth/src/providers/naver.ts generated vendored Normal file
View File

@@ -0,0 +1,42 @@
import type { OAuthConfig, OAuthUserConfig } from "."
/** https://developers.naver.com/docs/login/profile/profile.md */
export interface NaverProfile extends Record<string, any> {
resultcode: string
message: string
response: {
id: string
nickname?: string
name?: string
email?: string
gender?: "F" | "M" | "U"
age?: string
birthday?: string
profile_image?: string
birthyear?: string
mobile?: string
}
}
export default function Naver<P extends NaverProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "naver",
name: "Naver",
type: "oauth",
authorization: "https://nid.naver.com/oauth2.0/authorize",
token: "https://nid.naver.com/oauth2.0/token",
userinfo: "https://openapi.naver.com/v1/nid/me",
profile(profile) {
return {
id: profile.response.id,
name: profile.response.nickname,
email: profile.response.email,
image: profile.response.profile_image,
}
},
checks: ["state"],
options,
}
}

20
node_modules/next-auth/src/providers/netlify.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import(".").OAuthProvider} */
export default function Netlify(options) {
return {
id: "netlify",
name: "Netlify",
type: "oauth",
authorization: "https://app.netlify.com/authorize",
token: "https://api.netlify.com/oauth/token",
userinfo: "https://api.netlify.com/api/v1/user",
profile(profile) {
return {
id: profile.id,
name: profile.full_name,
email: profile.email,
image: profile.avatar_url,
}
},
options,
}
}

71
node_modules/next-auth/src/providers/oauth-types.ts generated vendored Normal file
View File

@@ -0,0 +1,71 @@
// THIS FILE IS AUTOGENERATED. DO NOT EDIT.
export type OAuthProviderType =
| "42-school"
| "apple"
| "atlassian"
| "auth0"
| "authentik"
| "azure-ad-b2c"
| "azure-ad"
| "battlenet"
| "box"
| "boxyhq-saml"
| "bungie"
| "cognito"
| "coinbase"
| "credentials"
| "discord"
| "dropbox"
| "duende-identity-server6"
| "email"
| "eveonline"
| "facebook"
| "faceit"
| "foursquare"
| "freshbooks"
| "fusionauth"
| "github"
| "gitlab"
| "google"
| "hubspot"
| "identity-server4"
| "index"
| "instagram"
| "kakao"
| "keycloak"
| "line"
| "linkedin"
| "mailchimp"
| "mailru"
| "medium"
| "naver"
| "netlify"
| "oauth-types"
| "oauth"
| "okta"
| "onelogin"
| "osso"
| "osu"
| "passage"
| "patreon"
| "pinterest"
| "pipedrive"
| "reddit"
| "salesforce"
| "slack"
| "spotify"
| "strava"
| "todoist"
| "trakt"
| "twitch"
| "twitter"
| "united-effects"
| "vk"
| "wikimedia"
| "wordpress"
| "workos"
| "yandex"
| "zitadel"
| "zoho"
| "zoom"

171
node_modules/next-auth/src/providers/oauth.ts generated vendored Normal file
View File

@@ -0,0 +1,171 @@
import type { CommonProviderOptions } from "../providers"
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"
type Client = InstanceType<Issuer["Client"]>
export type { OAuthProviderType } from "./oauth-types"
type ChecksType = "pkce" | "state" | "none" | "nonce"
export type OAuthChecks = OpenIDCallbackChecks | OAuthCallbackChecks
type PartialIssuer = Partial<Pick<IssuerMetadata, "jwks_endpoint" | "issuer">>
type UrlParams = Record<string, unknown>
type EndpointRequest<C, R, P> = (
context: C & {
/** `openid-client` Client */
client: Client
/** Provider is passed for convenience, ans also contains the `callbackUrl`. */
provider: OAuthConfig<P> & {
signinUrl: string
callbackUrl: string
}
}
) => Awaitable<R>
/** Gives granular control of the request to the given endpoint */
interface AdvancedEndpointHandler<P extends UrlParams, C, R> {
/** Endpoint URL. Can contain parameters. Optionally, you can use `params` */
url?: string
/** These will be prepended to the `url` */
params?: P
/**
* Control the corresponding OAuth endpoint request completely.
* Useful if your provider relies on some custom behaviour
* or it diverges from the OAuth spec.
*
* - ⚠ **This is an advanced option.**
* You should **try to avoid using advanced options** unless you are very comfortable using them.
*/
request?: EndpointRequest<C, R, P>
}
/** Either an URL (containing all the parameters) or an object with more granular control. */
export type EndpointHandler<
P extends UrlParams,
C = any,
R = any
> = AdvancedEndpointHandler<P, C, R>
export type AuthorizationEndpointHandler =
EndpointHandler<AuthorizationParameters>
export type TokenEndpointHandler = EndpointHandler<
UrlParams,
{
/**
* Parameters extracted from the request to the `/api/auth/callback/:providerId` endpoint.
* Contains params like `state`.
*/
params: CallbackParamsType
/**
* When using this custom flow, make sure to do all the necessary security checks.
* This object contains parameters you have to match against the request to make sure it is valid.
*/
checks: OAuthChecks
},
{
tokens: TokenSet
}
>
export type UserinfoEndpointHandler = EndpointHandler<
UrlParams,
{ tokens: TokenSet },
Profile
>
export interface OAuthProviderButtonStyles {
logo: string
logoDark?: string
bg: string
bgDark?: string
text: string
textDark?: string
}
export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
/**
* OpenID Connect (OIDC) compliant providers can configure
* this instead of `authorize`/`token`/`userinfo` options
* without further configuration needed in most cases.
* You can still use the `authorize`/`token`/`userinfo`
* options for advanced control.
*
* [Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414#section-3)
*/
wellKnown?: string
jwks_endpoint?: string
/**
* The login process will be initiated by sending the user to this URL.
*
* [Authorization endpoint](https://datatracker.ietf.org/doc/html/rfc6749#section-3.1)
*/
authorization?: string | AuthorizationEndpointHandler
token?: string | TokenEndpointHandler
userinfo?: string | UserinfoEndpointHandler
type: "oauth"
version?: string
profile: (profile: P, tokens: TokenSet) => Awaitable<User>
checks?: ChecksType | ChecksType[]
client?: Partial<ClientMetadata>
jwks?: { keys: JWK[] }
clientId?: string
clientSecret?: string
/**
* If set to `true`, the user information will be extracted
* from the `id_token` claims, instead of
* making a request to the `userinfo` endpoint.
*
* `id_token` is usually present in OpenID Connect (OIDC) compliant providers.
*
* [`id_token` explanation](https://www.oauth.com/oauth2-servers/openid-connect/id-tokens)
*/
idToken?: boolean
// TODO: only allow for BattleNet
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
style?: OAuthProviderButtonStyles
/**
* The options provided by the user.
* We will perform a deep-merge of these values
* with the default configuration.
*/
options?: OAuthUserConfig<P>
// These are kept around for backwards compatibility with OAuth 1.x
accessTokenUrl?: string
requestTokenUrl?: string
profileUrl?: string
encoding?: string
allowDangerousEmailAccountLinking?: boolean
}
export type OAuthUserConfig<P> = Omit<
Partial<OAuthConfig<P>>,
"options" | "type"
> &
Required<Pick<OAuthConfig<P>, "clientId" | "clientSecret">>
export type OAuthProvider = (
options: Partial<OAuthConfig<any>>
) => OAuthConfig<any>

58
node_modules/next-auth/src/providers/okta.ts generated vendored Normal file
View File

@@ -0,0 +1,58 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface OktaProfile extends Record<string, any> {
iss: string
ver: string
sub: string
aud: string
iat: string
exp: string
jti: string
auth_time: string
amr: string
idp: string
nonce: string
name: string
nickname: string
preferred_username: string
given_name: string
middle_name: string
family_name: string
email: string
email_verified: string
profile: string
zoneinfo: string
locale: string
address: string
phone_number: string
picture: string
website: string
gender: string
birthdate: string
updated_at: string
at_hash: string
c_hash: string
}
export default function Okta<P extends OktaProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "okta",
name: "Okta",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid email profile" } },
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.name ?? profile.preferred_username,
email: profile.email,
image: profile.picture,
}
},
style: { logo: "/okta.svg", bg: "#000", text: "#fff" },
options,
}
}

20
node_modules/next-auth/src/providers/onelogin.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import(".").OAuthProvider} */
export default function OneLogin(options) {
return {
id: "onelogin",
name: "OneLogin",
type: "oauth",
wellKnown: `${options.issuer}/oidc/2/.well-known/openid-configuration`,
authorization: { params: { scope: "openid profile email" } },
idToken: true,
profile(profile) {
return {
id: profile.sub,
name: profile.nickname,
email: profile.email,
image: profile.picture,
}
},
options,
}
}

20
node_modules/next-auth/src/providers/osso.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import(".").OAuthProvider} */
export default function Osso(options) {
return {
id: "osso",
name: "Osso",
type: "oauth",
authorization: `${options.issuer}oauth/authorize`,
token: `${options.issuer}oauth/token`,
userinfo: `${options.issuer}oauth/me`,
profile(profile) {
return {
id: profile.id,
name: profile.name,
email: profile.email,
image: null,
}
},
options,
}
}

77
node_modules/next-auth/src/providers/osu.ts generated vendored Normal file
View File

@@ -0,0 +1,77 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface OsuUserCompact {
avatar_url: string
country_code: string
default_group: string
id: string
is_active: boolean
is_bot: boolean
is_deleted: boolean
is_online: boolean
is_supporter: boolean
last_visit: Date | null
pm_friends_only: boolean
profile_colour: string | null
username: string
}
export interface OsuProfile extends OsuUserCompact, Record<string, any> {
discord: string | null
has_supported: boolean
interests: string | null
join_date: Date
kudosu: {
available: number
total: number
}
location: string | null
max_blocks: number
max_friends: number
occupation: string | null
playmode: string
playstyle: string[]
post_count: number
profile_order: string[]
title: string | null
title_url: string | null
twitter: string | null
website: string | null
country: {
code: string
name: string
}
cover: {
custom_url: string | null
url: string
id: number | null
}
is_restricted: boolean
}
export default function Osu<P extends OsuProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "osu",
name: "Osu!",
type: "oauth",
token: "https://osu.ppy.sh/oauth/token",
authorization: {
url: "https://osu.ppy.sh/oauth/authorize",
params: {
scope: "identify",
},
},
userinfo: "https://osu.ppy.sh/api/v2/me",
profile(profile) {
return {
id: profile.id,
email: null,
name: profile.username,
image: profile.avatar_url,
}
},
options,
}
}

56
node_modules/next-auth/src/providers/passage.ts generated vendored Normal file
View File

@@ -0,0 +1,56 @@
import type { OAuthConfig, OAuthUserConfig } from "."
/** @see [Supported Scopes](https://docs.passage.id/hosted-login/oidc-client-configuration#supported-scopes) */
export interface PassageProfile {
iss: string
/** Unique identifer in Passage for the user */
sub: string
aud: string[]
exp: number
iat: number
auth_time: number
azp: string
client_id: string
at_hash: string
c_hash: string
/** The user's email address */
email: string
/** Whether the user has verified their email address */
email_verified: boolean
/** The user's phone number */
phone: string
/** Whether the user has verified their phone number */
phone_number_verified: boolean
}
export default function Passage(
config: OAuthUserConfig<PassageProfile>
): OAuthConfig<PassageProfile> {
config.issuer = config.issuer?.replace(/\/$/, "")
return {
id: "passage",
name: "Passage",
type: "oauth",
wellKnown: `${config.issuer}/.well-known/openid-configuration`,
authorization: { params: { scope: "openid email" } },
client: { token_endpoint_auth_method: "client_secret_basic" },
checks: ["pkce", "state"],
profile(profile) {
return {
id: profile.sub,
name: null,
email: profile.email,
image: null,
}
},
style: {
logo: "/passage.svg",
logoDark: "/passage.svg",
bg: "#fff",
bgDark: "#fff",
text: "#000",
textDark: "#000",
},
options: config,
}
}

35
node_modules/next-auth/src/providers/patreon.ts generated vendored Normal file
View File

@@ -0,0 +1,35 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface PatreonProfile extends Record<string, any> {
sub: string
nickname: string
email: string
picture: string
}
export default function Patreon<P extends PatreonProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "patreon",
name: "Patreon",
type: "oauth",
version: "2.0",
authorization: {
url: "https://www.patreon.com/oauth2/authorize",
params: { scope: "identity identity[email]" },
},
token: "https://www.patreon.com/api/oauth2/token",
userinfo: "https://www.patreon.com/api/oauth2/api/current_user",
profile(profile) {
return {
id: profile.data.id,
name: profile.data.attributes.full_name,
email: profile.data.attributes.email,
image: profile.data.attributes.image_url,
}
},
style: { logo: "/patreon.svg", bg: "#e85b46", text: "#fff" },
options,
}
}

34
node_modules/next-auth/src/providers/pinterest.ts generated vendored Normal file
View File

@@ -0,0 +1,34 @@
import { OAuthConfig, OAuthUserConfig } from "."
export interface PinterestProfile extends Record<string, any> {
account_type: "BUSINESS" | "PINNER"
profile_image: string
website_url: string
username: string
}
export default function PinterestProvider<P extends PinterestProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "pinterest",
name: "Pinterest",
type: "oauth",
authorization: {
url: "https://www.pinterest.com/oauth",
params: { scope: "user_accounts:read" },
},
checks: ["state"],
token: "https://api.pinterest.com/v5/oauth/token",
userinfo: "https://api.pinterest.com/v5/user_account",
profile({ username, profile_image }) {
return {
id: username,
name: username,
image: profile_image,
email: null,
}
},
options,
}
}

59
node_modules/next-auth/src/providers/pipedrive.ts generated vendored Normal file
View File

@@ -0,0 +1,59 @@
import type { OAuthConfig, OAuthUserConfig } from "."
export interface PipedriveProfile extends Record<string, any> {
success: boolean
data: {
id: number
name: string
default_currency?: string
locale?: string
lang?: number
email: string
phone?: string
activated?: boolean
last_login?: Date
created?: Date
modified?: Date
signup_flow_variation?: string
has_created_company?: boolean
is_admin?: number
active_flag?: boolean
timezone_name?: string
timezone_offset?: string
role_id?: number
icon_url?: string
is_you?: boolean
company_id?: number
company_name?: string
company_domain?: string
company_country?: string
company_industry?: string
language?: {
language_code?: string
country_code?: string
}
}
}
export default function Pipedrive<P extends PipedriveProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "pipedrive",
name: "Pipedrive",
type: "oauth",
version: "2.0",
authorization: "https://oauth.pipedrive.com/oauth/authorize",
token: "https://oauth.pipedrive.com/oauth/token",
userinfo: "https://api.pipedrive.com/users/me",
profile: ({ data: profile }) => {
return {
id: String(profile.id),
name: profile.name,
email: profile.email,
image: profile.icon_url,
}
},
options,
}
}

Some files were not shown because too many files have changed in this diff Show More