Auto-commit 2026-04-29 16:31
This commit is contained in:
125
node_modules/next-auth/src/adapters.ts
generated
vendored
Normal file
125
node_modules/next-auth/src/adapters.ts
generated
vendored
Normal 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>
|
||||
}
|
||||
188
node_modules/next-auth/src/client/__tests__/client-provider.test.js
generated
vendored
Normal file
188
node_modules/next-auth/src/client/__tests__/client-provider.test.js
generated
vendored
Normal 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"))
|
||||
}
|
||||
104
node_modules/next-auth/src/client/__tests__/csrf.test.js
generated
vendored
Normal file
104
node_modules/next-auth/src/client/__tests__/csrf.test.js
generated
vendored
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
90
node_modules/next-auth/src/client/__tests__/helpers/mocks.js
generated
vendored
Normal file
90
node_modules/next-auth/src/client/__tests__/helpers/mocks.js
generated
vendored
Normal 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)))
|
||||
)
|
||||
14
node_modules/next-auth/src/client/__tests__/helpers/utils.js
generated
vendored
Normal file
14
node_modules/next-auth/src/client/__tests__/helpers/utils.js
generated
vendored
Normal 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}`
|
||||
})
|
||||
}
|
||||
84
node_modules/next-auth/src/client/__tests__/providers.test.js
generated
vendored
Normal file
84
node_modules/next-auth/src/client/__tests__/providers.test.js
generated
vendored
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
97
node_modules/next-auth/src/client/__tests__/session.test.js
generated
vendored
Normal file
97
node_modules/next-auth/src/client/__tests__/session.test.js
generated
vendored
Normal 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>
|
||||
}
|
||||
290
node_modules/next-auth/src/client/__tests__/sign-in.test.js
generated
vendored
Normal file
290
node_modules/next-auth/src/client/__tests__/sign-in.test.js
generated
vendored
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
124
node_modules/next-auth/src/client/__tests__/sign-out.test.js
generated
vendored
Normal file
124
node_modules/next-auth/src/client/__tests__/sign-out.test.js
generated
vendored
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
140
node_modules/next-auth/src/client/__tests__/use-session-hook.test.js
generated
vendored
Normal file
140
node_modules/next-auth/src/client/__tests__/use-session-hook.test.js
generated
vendored
Normal 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
120
node_modules/next-auth/src/client/_utils.ts
generated
vendored
Normal 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
127
node_modules/next-auth/src/core/errors.ts
generated
vendored
Normal 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
336
node_modules/next-auth/src/core/index.ts
generated
vendored
Normal 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
155
node_modules/next-auth/src/core/init.ts
generated
vendored
Normal 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
149
node_modules/next-auth/src/core/lib/assert.ts
generated
vendored
Normal 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
233
node_modules/next-auth/src/core/lib/callback-handler.ts
generated
vendored
Normal 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
42
node_modules/next-auth/src/core/lib/callback-url.ts
generated
vendored
Normal 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
250
node_modules/next-auth/src/core/lib/cookie.ts
generated
vendored
Normal 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
55
node_modules/next-auth/src/core/lib/csrf-token.ts
generated
vendored
Normal 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 }
|
||||
}
|
||||
18
node_modules/next-auth/src/core/lib/default-callbacks.ts
generated
vendored
Normal file
18
node_modules/next-auth/src/core/lib/default-callbacks.ts
generated
vendored
Normal 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
|
||||
},
|
||||
}
|
||||
21
node_modules/next-auth/src/core/lib/email/getUserFromEmail.ts
generated
vendored
Normal file
21
node_modules/next-auth/src/core/lib/email/getUserFromEmail.ts
generated
vendored
Normal 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
51
node_modules/next-auth/src/core/lib/email/signin.ts
generated
vendored
Normal 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,
|
||||
})}`
|
||||
}
|
||||
63
node_modules/next-auth/src/core/lib/oauth/authorization-url.ts
generated
vendored
Normal file
63
node_modules/next-auth/src/core/lib/oauth/authorization-url.ts
generated
vendored
Normal 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
183
node_modules/next-auth/src/core/lib/oauth/callback.ts
generated
vendored
Normal 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
199
node_modules/next-auth/src/core/lib/oauth/checks.ts
generated
vendored
Normal 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
|
||||
},
|
||||
}
|
||||
73
node_modules/next-auth/src/core/lib/oauth/client-legacy.ts
generated
vendored
Normal file
73
node_modules/next-auth/src/core/lib/oauth/client-legacy.ts
generated
vendored
Normal 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
48
node_modules/next-auth/src/core/lib/oauth/client.ts
generated
vendored
Normal 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
93
node_modules/next-auth/src/core/lib/providers.ts
generated
vendored
Normal 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
44
node_modules/next-auth/src/core/lib/utils.ts
generated
vendored
Normal 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
112
node_modules/next-auth/src/core/pages/error.tsx
generated
vendored
Normal 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
79
node_modules/next-auth/src/core/pages/index.ts
generated
vendored
Normal 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
279
node_modules/next-auth/src/core/pages/signin.tsx
generated
vendored
Normal 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
48
node_modules/next-auth/src/core/pages/signout.tsx
generated
vendored
Normal 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>
|
||||
)
|
||||
}
|
||||
37
node_modules/next-auth/src/core/pages/verify-request.tsx
generated
vendored
Normal file
37
node_modules/next-auth/src/core/pages/verify-request.tsx
generated
vendored
Normal 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
429
node_modules/next-auth/src/core/routes/callback.ts
generated
vendored
Normal 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
5
node_modules/next-auth/src/core/routes/index.ts
generated
vendored
Normal 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
30
node_modules/next-auth/src/core/routes/providers.ts
generated
vendored
Normal 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
191
node_modules/next-auth/src/core/routes/session.ts
generated
vendored
Normal 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
126
node_modules/next-auth/src/core/routes/signin.ts
generated
vendored
Normal 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
45
node_modules/next-auth/src/core/routes/signout.ts
generated
vendored
Normal 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
626
node_modules/next-auth/src/core/types.ts
generated
vendored
Normal 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
319
node_modules/next-auth/src/css/index.css
generated
vendored
Normal 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
11
node_modules/next-auth/src/css/index.ts
generated
vendored
Normal 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
10
node_modules/next-auth/src/index.ts
generated
vendored
Normal 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
132
node_modules/next-auth/src/jwt/index.ts
generated
vendored
Normal 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
66
node_modules/next-auth/src/jwt/types.ts
generated
vendored
Normal 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
2
node_modules/next-auth/src/middleware.ts
generated
vendored
Normal 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
261
node_modules/next-auth/src/next/index.ts
generated
vendored
Normal 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
243
node_modules/next-auth/src/next/middleware.ts
generated
vendored
Normal 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
60
node_modules/next-auth/src/next/utils.ts
generated
vendored
Normal 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
178
node_modules/next-auth/src/providers/42-school.ts
generated
vendored
Normal 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
123
node_modules/next-auth/src/providers/apple.ts
generated
vendored
Normal 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
37
node_modules/next-auth/src/providers/atlassian.ts
generated
vendored
Normal 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
32
node_modules/next-auth/src/providers/auth0.ts
generated
vendored
Normal 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
44
node_modules/next-auth/src/providers/authentik.ts
generated
vendored
Normal 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
48
node_modules/next-auth/src/providers/azure-ad-b2c.ts
generated
vendored
Normal 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
62
node_modules/next-auth/src/providers/azure-ad.ts
generated
vendored
Normal 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
32
node_modules/next-auth/src/providers/battlenet.ts
generated
vendored
Normal 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
25
node_modules/next-auth/src/providers/box.js
generated
vendored
Normal 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
37
node_modules/next-auth/src/providers/boxyhq-saml.ts
generated
vendored
Normal 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
25
node_modules/next-auth/src/providers/bungie.js
generated
vendored
Normal 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
30
node_modules/next-auth/src/providers/cognito.ts
generated
vendored
Normal 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
21
node_modules/next-auth/src/providers/coinbase.js
generated
vendored
Normal 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
45
node_modules/next-auth/src/providers/credentials.ts
generated
vendored
Normal 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
50
node_modules/next-auth/src/providers/discord.ts
generated
vendored
Normal 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
51
node_modules/next-auth/src/providers/dropbox.js
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
||||
31
node_modules/next-auth/src/providers/duende-identity-server6.ts
generated
vendored
Normal file
31
node_modules/next-auth/src/providers/duende-identity-server6.ts
generated
vendored
Normal 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
209
node_modules/next-auth/src/providers/email.ts
generated
vendored
Normal 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, "​.")
|
||||
|
||||
// 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
33
node_modules/next-auth/src/providers/eveonline.ts
generated
vendored
Normal 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
47
node_modules/next-auth/src/providers/facebook.ts
generated
vendored
Normal 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
25
node_modules/next-auth/src/providers/faceit.js
generated
vendored
Normal 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
60
node_modules/next-auth/src/providers/foursquare.js
generated
vendored
Normal 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
27
node_modules/next-auth/src/providers/freshbooks.js
generated
vendored
Normal 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
50
node_modules/next-auth/src/providers/fusionauth.ts
generated
vendored
Normal 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
105
node_modules/next-auth/src/providers/github.ts
generated
vendored
Normal 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
73
node_modules/next-auth/src/providers/gitlab.ts
generated
vendored
Normal 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
43
node_modules/next-auth/src/providers/google.ts
generated
vendored
Normal 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
70
node_modules/next-auth/src/providers/hubspot.ts
generated
vendored
Normal 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,
|
||||
}
|
||||
}
|
||||
21
node_modules/next-auth/src/providers/identity-server4.js
generated
vendored
Normal file
21
node_modules/next-auth/src/providers/identity-server4.js
generated
vendored
Normal 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
41
node_modules/next-auth/src/providers/index.ts
generated
vendored
Normal 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
56
node_modules/next-auth/src/providers/instagram.js
generated
vendored
Normal 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
92
node_modules/next-auth/src/providers/kakao.ts
generated
vendored
Normal 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
49
node_modules/next-auth/src/providers/keycloak.ts
generated
vendored
Normal 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
39
node_modules/next-auth/src/providers/line.ts
generated
vendored
Normal 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
61
node_modules/next-auth/src/providers/linkedin.ts
generated
vendored
Normal 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
21
node_modules/next-auth/src/providers/mailchimp.js
generated
vendored
Normal 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
20
node_modules/next-auth/src/providers/mailru.js
generated
vendored
Normal 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
20
node_modules/next-auth/src/providers/medium.js
generated
vendored
Normal 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
42
node_modules/next-auth/src/providers/naver.ts
generated
vendored
Normal 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
20
node_modules/next-auth/src/providers/netlify.js
generated
vendored
Normal 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
71
node_modules/next-auth/src/providers/oauth-types.ts
generated
vendored
Normal 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
171
node_modules/next-auth/src/providers/oauth.ts
generated
vendored
Normal 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
58
node_modules/next-auth/src/providers/okta.ts
generated
vendored
Normal 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
20
node_modules/next-auth/src/providers/onelogin.js
generated
vendored
Normal 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
20
node_modules/next-auth/src/providers/osso.js
generated
vendored
Normal 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
77
node_modules/next-auth/src/providers/osu.ts
generated
vendored
Normal 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
56
node_modules/next-auth/src/providers/passage.ts
generated
vendored
Normal 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
35
node_modules/next-auth/src/providers/patreon.ts
generated
vendored
Normal 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
34
node_modules/next-auth/src/providers/pinterest.ts
generated
vendored
Normal 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
59
node_modules/next-auth/src/providers/pipedrive.ts
generated
vendored
Normal 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
Reference in New Issue
Block a user