mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
20 Commits
@auth/core
...
ndom91/web
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
307ca24cb8 | ||
|
|
b96f01319c | ||
|
|
8f416b68ec | ||
|
|
eaf5080721 | ||
|
|
e0b5f18c5b | ||
|
|
99247ce446 | ||
|
|
d6bc65f0d8 | ||
|
|
6f5a50313f | ||
|
|
e3bdb38df2 | ||
|
|
92a0fc42fa | ||
|
|
62e2ad115c | ||
|
|
542c35d729 | ||
|
|
5400645221 | ||
|
|
d739e8e04e | ||
|
|
c2eb9b3ad4 | ||
|
|
5e6b461908 | ||
|
|
2f7f9ccfe3 | ||
|
|
65ee6756bc | ||
|
|
963e407d5f | ||
|
|
4e294edec1 |
4
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
4
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -29,10 +29,10 @@ body:
|
||||
- "@next-auth/mongodb-adapter"
|
||||
- "@next-auth/neo4j-adapter"
|
||||
- "@next-auth/pouchdb-adapter"
|
||||
- "@auth/prisma-adapter"
|
||||
- "@next-auth/prisma-adapter"
|
||||
- "@next-auth/sequelize-adapter"
|
||||
- "@next-auth/supabase-adapter"
|
||||
- "@auth/typeorm-adapter"
|
||||
- "@next-auth/typeorm-legacy-adapter"
|
||||
- "@next-auth/upstash-redis-adapter"
|
||||
- "@next-auth/xata-adapter"
|
||||
validations:
|
||||
|
||||
6
.github/issue-labeler.yml
vendored
6
.github/issue-labeler.yml
vendored
@@ -25,7 +25,7 @@ pouchdb:
|
||||
- "@next-auth/pouchdb-adapter"
|
||||
|
||||
prisma:
|
||||
- "@auth/prisma-adapter"
|
||||
- "@next-auth/prisma-adapter"
|
||||
|
||||
sequelize:
|
||||
- "@next-auth/sequelize-adapter"
|
||||
@@ -33,8 +33,8 @@ sequelize:
|
||||
supabase:
|
||||
- "@next-auth/supabase-adapter"
|
||||
|
||||
typeorm:
|
||||
- "@auth/typeorm-adapter"
|
||||
typeorm-legacy:
|
||||
- "@next-auth/typeorm-legacy-adapter"
|
||||
|
||||
upstash-redis:
|
||||
- "@next-auth/upstash-redis-adapter"
|
||||
|
||||
2
.github/pr-labeler.yml
vendored
2
.github/pr-labeler.yml
vendored
@@ -21,6 +21,6 @@ solidjs: ["packages/frameworks-solid-start/**/*"]
|
||||
supabase: ["packages/adapter-supabase/**/*"]
|
||||
svelte: ["packages/frameworks-sveltekit/**/*"]
|
||||
test: ["**test**/*"]
|
||||
typeorm: ["packages/adapter-typeorm/**/*"]
|
||||
typeorm-legacy: ["packages/adapter-typeorm-legacy/**/*"]
|
||||
upstash-redis: ["packages/adapter-upstash-redis/**/*"]
|
||||
xata: ["packages/adapter-xata/**/*"]
|
||||
|
||||
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@@ -15,46 +15,16 @@ on:
|
||||
type: choice
|
||||
description: Package name (npm)
|
||||
options:
|
||||
- "@auth/core"
|
||||
- "@auth/nextjs"
|
||||
- "@auth/dgraph-adapter"
|
||||
- "@auth/drizzle-adapter"
|
||||
- "@auth/dynamodb-adapter"
|
||||
- "@auth/fauna-adapter"
|
||||
- "@auth/firebase-adapter"
|
||||
- "@auth/mikro-orm-adapter"
|
||||
- "@auth/mongodb-adapter"
|
||||
- "@auth/neo4j-adapter"
|
||||
- "@auth/pouchdb-adapter"
|
||||
- "@auth/prisma-adapter"
|
||||
- "@auth/sequelize-adapter"
|
||||
- "@auth/supabase-adapter"
|
||||
- "@auth/typeorm-adapter"
|
||||
- "@auth/upstash-redis-adapter"
|
||||
- "@auth/xata-adapter"
|
||||
- "@auth/core"
|
||||
- "next-auth"
|
||||
# TODO: Infer from package name
|
||||
path:
|
||||
type: choice
|
||||
description: Directory name (packages/*)
|
||||
options:
|
||||
- "core"
|
||||
- "frameworks-nextjs"
|
||||
- "adapter-dgraph"
|
||||
- "adapter-drizzle"
|
||||
- "adapter-dynamodb"
|
||||
- "adapter-fauna"
|
||||
- "adapter-firebase"
|
||||
- "adapter-mikro-orm"
|
||||
- "adapter-mongodb"
|
||||
- "adapter-neo4j"
|
||||
- "adapter-pouchdb"
|
||||
- "adapter-prisma"
|
||||
- "adapter-sequelize"
|
||||
- "adapter-supabase"
|
||||
- "adapter-typeorm"
|
||||
- "adapter-upstash-redis"
|
||||
- "adapter-xata"
|
||||
- "core"
|
||||
- "next-auth"
|
||||
|
||||
jobs:
|
||||
@@ -72,7 +42,6 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run tests
|
||||
@@ -122,7 +91,6 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Publish to npm and GitHub
|
||||
@@ -147,7 +115,6 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Determine version
|
||||
@@ -186,7 +153,6 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Determine version
|
||||
@@ -196,7 +162,6 @@ jobs:
|
||||
PACKAGE_PATH: ${{ github.event.inputs.path }}
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
pnpm build
|
||||
cd packages/$PACKAGE_PATH
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
|
||||
pnpm publish --no-git-checks --access public --tag experimental
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@next-auth/fauna-adapter": "workspace:*",
|
||||
"@auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/supabase-adapter": "workspace:*",
|
||||
"@auth/typeorm-adapter": "workspace:*",
|
||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
||||
"@prisma/client": "^3",
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"faunadb": "^4",
|
||||
|
||||
@@ -37,7 +37,7 @@ import WorkOS from "next-auth/providers/workos"
|
||||
|
||||
// // Prisma
|
||||
// import { PrismaClient } from "@prisma/client"
|
||||
// import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
// const client = globalThis.prisma || new PrismaClient()
|
||||
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||
// const adapter = PrismaAdapter(client)
|
||||
@@ -51,8 +51,8 @@ import WorkOS from "next-auth/providers/workos"
|
||||
// const adapter = FaunaAdapter(client)
|
||||
|
||||
// // TypeORM
|
||||
// import { TypeORMAdapter } from "@auth/typeorm-adapter"
|
||||
// const adapter = TypeORMAdapter({
|
||||
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||
// const adapter = TypeORMLegacyAdapter({
|
||||
// type: "sqlite",
|
||||
// name: "next-auth-test-memory",
|
||||
// database: "./typeorm/dev.db",
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
"@next-auth/fauna-adapter": "workspace:*",
|
||||
"@auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/supabase-adapter": "workspace:*",
|
||||
"@auth/typeorm-adapter": "workspace:*",
|
||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
||||
"@prisma/client": "^3",
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"faunadb": "^4",
|
||||
|
||||
@@ -6,26 +6,24 @@ import Asgardeo from "@auth/core/providers/asgardeo"
|
||||
import Auth0 from "@auth/core/providers/auth0"
|
||||
import AzureAD from "@auth/core/providers/azure-ad"
|
||||
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
|
||||
import BeyondIdentity from "@auth/core/providers/beyondidentity"
|
||||
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
|
||||
// import Cognito from "@auth/core/providers/cognito"
|
||||
import Credentials from "@auth/core/providers/credentials"
|
||||
import Discord from "@auth/core/providers/discord"
|
||||
import DuendeIDS6 from "@auth/core/providers/duende-identity-server6"
|
||||
// import Discord from "@auth/core/providers/discord"
|
||||
// import DuendeIDS6 from "@auth/core/providers/duende-identity-server6"
|
||||
// import Email from "@auth/core/providers/email"
|
||||
import Facebook from "@auth/core/providers/facebook"
|
||||
import Foursquare from "@auth/core/providers/foursquare"
|
||||
import Freshbooks from "@auth/core/providers/freshbooks"
|
||||
import GitHub from "@auth/core/providers/github"
|
||||
import Gitlab from "@auth/core/providers/gitlab"
|
||||
import Google from "@auth/core/providers/google"
|
||||
// import Facebook from "@auth/core/providers/facebook"
|
||||
// import Foursquare from "@auth/core/providers/foursquare"
|
||||
// import Freshbooks from "@auth/core/providers/freshbooks"
|
||||
// import GitHub from "@auth/core/providers/github"
|
||||
// import Gitlab from "@auth/core/providers/gitlab"
|
||||
// import Google from "@auth/core/providers/google"
|
||||
// import IDS4 from "@auth/core/providers/identity-server4"
|
||||
import Instagram from "@auth/core/providers/instagram"
|
||||
// import Instagram from "@auth/core/providers/instagram"
|
||||
// import Keycloak from "@auth/core/providers/keycloak"
|
||||
import Line from "@auth/core/providers/line"
|
||||
import LinkedIn from "@auth/core/providers/linkedin"
|
||||
import Mailchimp from "@auth/core/providers/mailchimp"
|
||||
import Notion from "@auth/core/providers/notion"
|
||||
// import Line from "@auth/core/providers/line"
|
||||
// import LinkedIn from "@auth/core/providers/linkedin"
|
||||
// import Mailchimp from "@auth/core/providers/mailchimp"
|
||||
// import Okta from "@auth/core/providers/okta"
|
||||
import Osu from "@auth/core/providers/osu"
|
||||
import Patreon from "@auth/core/providers/patreon"
|
||||
@@ -41,7 +39,7 @@ import WorkOS from "@auth/core/providers/workos"
|
||||
|
||||
// // Prisma
|
||||
// import { PrismaClient } from "@prisma/client"
|
||||
// import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
// const client = globalThis.prisma || new PrismaClient()
|
||||
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||
// const adapter = PrismaAdapter(client)
|
||||
@@ -55,8 +53,8 @@ import WorkOS from "@auth/core/providers/workos"
|
||||
// const adapter = FaunaAdapter(client)
|
||||
|
||||
// // TypeORM
|
||||
// import { TypeORMAdapter } from "@auth/typeorm-adapter"
|
||||
// const adapter = TypeORMAdapter({
|
||||
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||
// const adapter = TypeORMLegacyAdapter({
|
||||
// type: "sqlite",
|
||||
// name: "next-auth-test-memory",
|
||||
// database: "./typeorm/dev.db",
|
||||
@@ -77,12 +75,21 @@ export const authConfig: AuthConfig = {
|
||||
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
|
||||
brandColor: "#1786fb",
|
||||
},
|
||||
pages: {
|
||||
signIn: "/auth/signin",
|
||||
},
|
||||
providers: [
|
||||
Credentials({
|
||||
credentials: { password: { label: "Password", type: "password" } },
|
||||
async authorize(credentials) {
|
||||
if (credentials.password !== "pw") return null
|
||||
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" }
|
||||
// if (credentials.password !== "pw") return null
|
||||
return {
|
||||
name: "Fill Murray",
|
||||
email: "bill@fillmurray.com",
|
||||
image: "https://www.fillmurray.com/64/64",
|
||||
id: "1",
|
||||
foo: "",
|
||||
}
|
||||
},
|
||||
}),
|
||||
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
|
||||
|
||||
196
apps/dev/nextjs/pages/auth/signin.js
Normal file
196
apps/dev/nextjs/pages/auth/signin.js
Normal file
@@ -0,0 +1,196 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import "./style.css"
|
||||
|
||||
export default function() {
|
||||
const [email, setEmail] = useState("")
|
||||
const [name, setName] = useState("")
|
||||
const [credentialId, setCredentialId] = useState()
|
||||
const [challenge, setChallenge] = useState(new Uint8Array(32))
|
||||
const [loginResponse, setLoginResponse] = useState()
|
||||
|
||||
const isWebAuthnSupported = () => {
|
||||
return !!window.PublicKeyCredential
|
||||
}
|
||||
|
||||
const isPlatformAuthenticatorSupported = () => {
|
||||
if (!isWebAuthnSupported()) {
|
||||
throw new Error("WebAuthn API is not available")
|
||||
}
|
||||
|
||||
if (!PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable) {
|
||||
return false
|
||||
} else {
|
||||
return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
isPlatformAuthenticatorSupported()
|
||||
}, [])
|
||||
|
||||
const registerDevice = () => {
|
||||
const publicKey = {
|
||||
challenge,
|
||||
rp: {
|
||||
name: ".domino",
|
||||
icon: "https://next-auth.js.org/img/logo/logo-sm.png",
|
||||
},
|
||||
user: {
|
||||
id: new Uint8Array(16),
|
||||
name: email,
|
||||
displayName: name,
|
||||
},
|
||||
pubKeyCredParams: [
|
||||
{ type: "public-key", alg: -7 },
|
||||
{ type: "public-key", alg: -257 },
|
||||
],
|
||||
authenticatorSelection: {
|
||||
userVerification: "required",
|
||||
},
|
||||
status: "ok",
|
||||
}
|
||||
navigator.credentials
|
||||
.create({ publicKey })
|
||||
.then((newCredentialInfo) => {
|
||||
console.log("** REGISTRATION SUCCESS", newCredentialInfo)
|
||||
setCredentialId(newCredentialInfo.id)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("FAIL", error)
|
||||
})
|
||||
}
|
||||
|
||||
const login = () => {
|
||||
const publicKey = {
|
||||
challenge: challenge,
|
||||
allowCredentials: [
|
||||
{ type: "public-key", id: Buffer.from(credentialId, "base64") },
|
||||
],
|
||||
userVerification: "required",
|
||||
status: "ok",
|
||||
}
|
||||
|
||||
navigator.credentials
|
||||
.get({ publicKey })
|
||||
.then((getAssertionResponse) => {
|
||||
console.log("** ASSERTION RECEIVED", getAssertionResponse)
|
||||
console.log(getAssertionResponse.response.authenticatorData.toString('base64'))
|
||||
console.log(getAssertionResponse.response.clientDataJSON.toString('base64'))
|
||||
console.log(getAssertionResponse.response.signature.toString('base64'))
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("FAIL", error)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "2rem",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginTop: "10vh",
|
||||
backgroundColor: "#0d1117",
|
||||
padding: "3rem",
|
||||
borderRadius: "0.5rem",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
<img src="https://next-auth.js.org/img/logo/logo-sm.png" width="64" />
|
||||
<div>WebAuthn Auth.js Demo</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#0d1117",
|
||||
padding: "3rem",
|
||||
borderRadius: "0.5rem",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "1rem",
|
||||
maxWidth: "400px",
|
||||
}}
|
||||
>
|
||||
<h2 style={{ margin: "0" }}>Step 1</h2>
|
||||
|
||||
<label htmlFor="email">
|
||||
Email <span style={{ color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
placeholder="user@company.com"
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
value={email}
|
||||
/>
|
||||
<label htmlFor="displayName">
|
||||
Name <span style={{ color: "red" }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="displayName"
|
||||
placeholder="Joe"
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
value={name}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => registerDevice()}
|
||||
disabled={!email || !name}
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
{credentialId && (
|
||||
<pre
|
||||
style={{
|
||||
wordBreak: "break-all",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "break-spaces",
|
||||
}}
|
||||
>
|
||||
Credential Id: {credentialId}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#0d1117",
|
||||
padding: "3rem",
|
||||
borderRadius: "0.5rem",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
<h2>Step 2</h2>
|
||||
<button type="button" onClick={() => login()} disabled={!credentialId}>
|
||||
Login
|
||||
</button>
|
||||
{loginResponse && (
|
||||
<pre
|
||||
style={{
|
||||
wordBreak: "break-all",
|
||||
wordWrap: "break-word",
|
||||
whiteSpace: "break-spaces",
|
||||
}}
|
||||
>
|
||||
Credential Id: {credentialId}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
50
apps/dev/nextjs/pages/auth/style.css
Normal file
50
apps/dev/nextjs/pages/auth/style.css
Normal file
@@ -0,0 +1,50 @@
|
||||
body {
|
||||
background-color: #161b22;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 0 0.75rem 0;
|
||||
padding: 0.75rem 2rem;
|
||||
color: white;
|
||||
background-color: orange;
|
||||
font-size: 1.1rem;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.1s ease-in-out;
|
||||
font-weight: 900;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
button:active {
|
||||
cursor: pointer;
|
||||
}
|
||||
button:disabled {
|
||||
filter: grayscale(0.9);
|
||||
}
|
||||
button:disabled:hover {
|
||||
cursor: auto !important;
|
||||
}
|
||||
|
||||
label {
|
||||
text-align: left;
|
||||
}
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #555;
|
||||
background: white;
|
||||
font-size: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
color: black;
|
||||
}
|
||||
input:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ export default function Page() {
|
||||
<p>Only admin users can see this page.</p>
|
||||
<p>
|
||||
To learn more about the NextAuth middleware see
|
||||
<a href="https://next-auth.js.org/configuration/nextjs#middleware">
|
||||
<a href="https://docs-git-misc-docs-nextauthjs.vercel.app/configuration/nextjs#middleware">
|
||||
the docs
|
||||
</a>
|
||||
.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Session } from "@auth/core/types"
|
||||
import { Session } from "@auth/core"
|
||||
|
||||
export default function useSession() {
|
||||
return useState<Session | null>("session", () => null)
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function signIn<
|
||||
|
||||
// TODO: Handle custom base path
|
||||
// TODO: Remove this since Sveltekit offers the CSRF protection via origin check
|
||||
const { csrfToken } = await $fetch<{ csrfToken: string }>("/api/auth/csrf")
|
||||
const { csrfToken } = await $fetch("/api/auth/csrf")
|
||||
|
||||
console.log(_signInUrl)
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { AuthConfig, Session } from "@auth/core/types"
|
||||
import { Auth } from "@auth/core"
|
||||
import { AuthHandler, AuthOptions, Session } from "@auth/core"
|
||||
import { fromNodeMiddleware, H3Event } from "h3"
|
||||
import getURL from "requrl"
|
||||
import { createMiddleware } from "@hattip/adapter-node"
|
||||
|
||||
export function NuxtAuthHandler(options: AuthConfig) {
|
||||
export function NuxtAuthHandler(options: AuthOptions) {
|
||||
async function handler(ctx: { request: Request }) {
|
||||
options.trustHost ??= true
|
||||
|
||||
return Auth(ctx.request, options)
|
||||
return AuthHandler(ctx.request, options)
|
||||
}
|
||||
|
||||
const middleware = createMiddleware(handler)
|
||||
@@ -18,7 +17,7 @@ export function NuxtAuthHandler(options: AuthConfig) {
|
||||
|
||||
export async function getSession(
|
||||
event: H3Event,
|
||||
options: AuthConfig
|
||||
options: AuthOptions
|
||||
): Promise<Session | null> {
|
||||
options.trustHost ??= true
|
||||
|
||||
@@ -31,7 +30,7 @@ export async function getSession(
|
||||
nodeHeaders.append(key, headers[key] as any)
|
||||
})
|
||||
|
||||
const response = await Auth(
|
||||
const response = await AuthHandler(
|
||||
new Request(url, { headers: nodeHeaders }),
|
||||
options
|
||||
)
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "next-auth-nuxt",
|
||||
"name": "playground-nuxt",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt prepare && nuxt dev",
|
||||
"build": "nuxt prepare && nuxt build",
|
||||
"dev": "nuxt prepare && export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^0.1.1",
|
||||
"eslint": "^8.29.0",
|
||||
"h3": "1.6.6",
|
||||
"nuxt": "3.5.1"
|
||||
"h3": "1.0.2",
|
||||
"nuxt": "3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
"@hattip/adapter-node": "^0.0.34",
|
||||
"@hattip/adapter-node": "^0.0.22",
|
||||
"requrl": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Session } from "@auth/core/types"
|
||||
import { Session } from "@auth/core"
|
||||
|
||||
export default defineNuxtPlugin(async () => {
|
||||
const session = useSession()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { NuxtAuthHandler } from "@/lib/auth/server"
|
||||
import GithubProvider from "@auth/core/providers/github"
|
||||
import type { AuthConfig } from "@auth/core"
|
||||
import type { AuthOptions } from "@auth/core"
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
export const authOptions = {
|
||||
export const authOptions: AuthOptions = {
|
||||
secret: runtimeConfig.secret,
|
||||
providers: [
|
||||
GithubProvider({
|
||||
@@ -12,6 +12,6 @@ export const authOptions = {
|
||||
clientSecret: runtimeConfig.github.clientSecret,
|
||||
}),
|
||||
],
|
||||
} as AuthConfig
|
||||
}
|
||||
|
||||
export default NuxtAuthHandler(authOptions)
|
||||
|
||||
@@ -243,13 +243,10 @@ http://localhost:5173/auth/callback/github
|
||||
TODO Core
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::info
|
||||
The last part of the URL, `[provider]`, is the ID of the provider you're using. In this case, we're using GitHub, so it's `github`. If you're using Google, it'll be `google`, etc... We keep track of the provider IDs internally.
|
||||
|
||||
The same id is used in the `signIn()` method we saw earlier.
|
||||
:::
|
||||
|
||||
To register, tap on "Register application" button.
|
||||
|
||||
The next screen shows all the configurations for your newly created OAuth app. For now, we need two things from it - the **Client ID** and **Client Secret**:
|
||||
|
||||
@@ -117,7 +117,7 @@ Using the database strategy is very similar, but instead of preserving the `acce
|
||||
import { Auth } from "@auth/core"
|
||||
import { type TokenSet } from "@auth/core/types"
|
||||
import Google from "@auth/core/providers/google"
|
||||
import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
@@ -22,18 +22,11 @@ Next you will have to create some configuration files for Cypress.
|
||||
|
||||
First, the primary cypress config:
|
||||
|
||||
```ts title="cypress.config.ts"
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
chromeWebSecurity: false,
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
})
|
||||
```js title="cypress.json"
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"chromeWebSecurity": false
|
||||
}
|
||||
```
|
||||
|
||||
This initial Cypress config will tell Cypress where to find your site on initial launch as well as allow it to open up URLs at domains that aren't your page, for example to be able to login to a social provider.
|
||||
@@ -53,24 +46,14 @@ You must change the login credentials you want to use, but you can also redefine
|
||||
|
||||
Third, if you're using the `cypress-social-login` plugin, you must add this to your `/cypress/plugins/index.js` file like so:
|
||||
|
||||
```js title="cypress.config.ts" {3-4,10-14}
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { GoogleSocialLogin } = require('cypress-social-logins').plugins
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
chromeWebSecurity: false,
|
||||
setupNodeEvents(on, config) {
|
||||
on('task', {
|
||||
GoogleSocialLogin,
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
```js title="cypress/plugins/index.js"
|
||||
const { GoogleSocialLogin } = require("cypress-social-logins").plugins
|
||||
|
||||
module.exports = (on, config) => {
|
||||
on("task", {
|
||||
GoogleSocialLogin: GoogleSocialLogin,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Finally, you can also add the following npm scripts to your `package.json`:
|
||||
@@ -127,6 +110,10 @@ describe("Login page", () => {
|
||||
secure: cookie.secure,
|
||||
})
|
||||
|
||||
Cypress.Cookies.defaults({
|
||||
preserve: cookieName,
|
||||
})
|
||||
|
||||
// remove the two lines below if you need to stay logged in
|
||||
// for your remaining tests
|
||||
cy.visit("/api/auth/signout")
|
||||
|
||||
@@ -96,13 +96,8 @@ erDiagram
|
||||
string type
|
||||
string provider
|
||||
string providerAccountId
|
||||
string refresh_token
|
||||
string access_token
|
||||
int expires_at
|
||||
string token_type
|
||||
string scope
|
||||
string id_token
|
||||
string session_state
|
||||
}
|
||||
VerificationToken {
|
||||
string identifier
|
||||
@@ -142,7 +137,7 @@ The Account model is for information about OAuth accounts associated with a User
|
||||
|
||||
A single User can have multiple Accounts, but each Account can only have one User.
|
||||
|
||||
Account creation in the database is automatic and happens when the user is logging in for the first time with a provider, or the [`Adapter.linkAccount`](/reference/core/adapters#linkaccount) method is invoked. The default data saved is `access_token`, `expires_at`, `refresh_token`, `id_token`, `token_type`, `scope` and `session_state`. You can save other fields or remove the ones you don't need by returning them in the [OAuth provider](/guides/providers/custom-provider)'s [`account()`](/reference/core/providers#account) callback.
|
||||
Account creation in the database is automatic and happens when the user is logging in for the first time with a provider, or the [`Adapter.linkAccount`](/reference/core/adapters#linkaccount) method is invoked. The default data saved is `access_token`, `refresh_token`, `id_token` and `expires_at`. You can save other fields by returning them in the [OAuth provider](/guides/providers/custom-provider)'s [`account()`](/reference/core/providers#account) callback.
|
||||
|
||||
Linking Accounts to Users happen automatically, only when they have the same e-mail address, and the user is currently signed in. Check the [FAQ](/concepts/faq#security) for more information on why this is a requirement.
|
||||
|
||||
|
||||
@@ -273,7 +273,19 @@ const docusaurusConfig = {
|
||||
typedocAdapter("Neo4j"),
|
||||
typedocAdapter("PouchDB"),
|
||||
typedocAdapter("Prisma"),
|
||||
typedocAdapter("TypeORM"),
|
||||
[
|
||||
"docusaurus-plugin-typedoc",
|
||||
{
|
||||
...typedocConfig,
|
||||
id: "typeorm",
|
||||
plugin: [require.resolve("./typedoc-mdn-links")],
|
||||
watch: process.env.TYPEDOC_WATCH,
|
||||
entryPoints: [`../packages/adapter-typeorm-legacy/src/index.ts`],
|
||||
tsconfig: `../packages/adapter-typeorm-legacy/tsconfig.json`,
|
||||
out: `reference/adapter/typeorm`,
|
||||
sidebar: { indexLabel: "TypeORM" },
|
||||
},
|
||||
],
|
||||
typedocAdapter("Sequelize"),
|
||||
typedocAdapter("Supabase"),
|
||||
typedocAdapter("Upstash Redis"),
|
||||
|
||||
@@ -29,7 +29,6 @@ html[data-theme="dark"] .adapter-card {
|
||||
color: #f5f5f5;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .adapter-card:hover,
|
||||
.adapter-card:hover {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "2.8.1",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"turbo": "1.10.1",
|
||||
"turbo": "1.8.8",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
</a>
|
||||
<h3 align="center"><b>Prisma Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/prisma-adapter">
|
||||
<a href="https://npm.im/@next-auth/prisma-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/prisma-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/prisma-adapter?color=green&label=@auth/prisma-adapter&style=flat-square">
|
||||
<a href="https://npm.im/@next-auth/prisma-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@next-auth/prisma-adapter?color=green&label=@next-auth/prisma-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/prisma-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/prisma-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
<a href="https://www.npmtrends.com/@next-auth/prisma-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@next-auth/prisma-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/nextauthjs/next-auth/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
{
|
||||
"name": "@auth/prisma-adapter",
|
||||
"version": "1.0.0",
|
||||
"description": "Prisma adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev/reference/adapter/prisma",
|
||||
"name": "@next-auth/prisma-adapter",
|
||||
"version": "1.0.6",
|
||||
"description": "Prisma adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "William Luke",
|
||||
"contributors": [
|
||||
"Balázs Orbán <info@balazsorban.com>"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.d.ts*",
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
@@ -47,19 +32,22 @@
|
||||
"dev": "prisma generate && tsc -w",
|
||||
"studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@prisma/client": ">=2.26.0 || >=3 || >=4"
|
||||
"@prisma/client": ">=2.26.0 || >=3",
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:*",
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"@prisma/client": "^3.10.0",
|
||||
"jest": "^27.4.3",
|
||||
"mongodb": "^4.4.0",
|
||||
"prisma": "^4.15.0"
|
||||
"next-auth": "workspace:*",
|
||||
"prisma": "^3.10.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install @prisma/client @auth/prisma-adapter
|
||||
* npm install next-auth @prisma/client @next-auth/prisma-adapter
|
||||
* npm install prisma --save-dev
|
||||
* ```
|
||||
*
|
||||
* @module @auth/prisma-adapter
|
||||
* @module @next-auth/prisma-adapter
|
||||
*/
|
||||
import type { PrismaClient, Prisma } from "@prisma/client"
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
import type { Adapter, AdapterAccount } from "next-auth/adapters"
|
||||
|
||||
/**
|
||||
* ## Setup
|
||||
@@ -26,7 +26,7 @@ import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
* ```js title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
* import GoogleProvider from "next-auth/providers/google"
|
||||
* import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
* import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
* import { PrismaClient } from "@prisma/client"
|
||||
*
|
||||
* const prisma = new PrismaClient()
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.base.json",
|
||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"*.js",
|
||||
"*.d.ts",
|
||||
]
|
||||
}
|
||||
"include": ["."],
|
||||
"exclude": ["tests", "dist", "jest.config.js"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Adapter } from "@auth/core/adapters"
|
||||
import type { Adapter } from "next-auth/adapters"
|
||||
import { createHash, randomUUID } from "crypto"
|
||||
|
||||
const requiredMethods = [
|
||||
@@ -58,8 +58,7 @@ export async function runBasicTests(options: TestOptions) {
|
||||
await options.db.connect?.()
|
||||
})
|
||||
|
||||
const { adapter: _adapter, db, skipTests } = options
|
||||
const adapter = _adapter as Required<Adapter>
|
||||
const { adapter, db, skipTests } = options
|
||||
|
||||
afterAll(async () => {
|
||||
// @ts-expect-error This is only used for the TypeORM adapter
|
||||
@@ -89,7 +88,7 @@ export async function runBasicTests(options: TestOptions) {
|
||||
providerAccountId: randomUUID(),
|
||||
type: "oauth",
|
||||
access_token: randomUUID(),
|
||||
expires_at: ONE_MONTH / 1000,
|
||||
expires_at: ONE_MONTH,
|
||||
id_token: randomUUID(),
|
||||
refresh_token: randomUUID(),
|
||||
token_type: "bearer",
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
"@babel/cli": "^7.14.3",
|
||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"jest": "^27.0.3",
|
||||
"next-auth": "workspace:*",
|
||||
"ts-jest": "^27.0.3",
|
||||
"typescript": "^4.2.4"
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
</a>
|
||||
<h3 align="center"><b>TypeORM Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/typeorm-adapter">
|
||||
<a href="https://npm.im/@next-auth/typeorm-legacy-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/typeorm-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/typeorm-adapter?color=green&label=@auth/typeorm-adapter&style=flat-square">
|
||||
<a href="https://npm.im/@next-auth/typeorm-legacy-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@next-auth/typeorm-legacy-adapter?color=green&label=@next-auth/typeorm-legacy-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/typeorm-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/typeorm-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
<a href="https://www.npmtrends.com/@next-auth/typeorm-legacy-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@next-auth/typeorm-legacy-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/nextauthjs/next-auth/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@auth/typeorm-adapter",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeORM adapter for Auth.js.",
|
||||
"homepage": "https://authjs.dev/reference/adapter/typeorm",
|
||||
"name": "@next-auth/typeorm-legacy-adapter",
|
||||
"version": "2.0.2",
|
||||
"description": "TypeORM (legacy) adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
@@ -11,19 +11,11 @@
|
||||
"contributors": [
|
||||
"Balázs Orbán <info@balazsorban.com>"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.d.ts*",
|
||||
"src"
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
@@ -46,27 +38,26 @@
|
||||
"test:containers": "tests/test.sh",
|
||||
"test": "tests/test.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:*",
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"jest": "^27.4.3",
|
||||
"mssql": "^7.2.1",
|
||||
"mysql": "^2.18.1",
|
||||
"next-auth": "workspace:*",
|
||||
"pg": "^8.7.3",
|
||||
"sqlite3": "^5.0.8",
|
||||
"typeorm": "0.3.15",
|
||||
"typeorm": "0.3.7",
|
||||
"typeorm-naming-strategies": "^4.1.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mssql": "^6.2.1 || 7",
|
||||
"mysql": "^2.18.1",
|
||||
"next-auth": "^4",
|
||||
"pg": "^8.2.1",
|
||||
"sqlite3": "^5.0.2",
|
||||
"typeorm": "^0.3.7"
|
||||
"typeorm": "0.3.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mysql": {
|
||||
@@ -9,17 +9,17 @@
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install @auth/typeorm-adapter typeorm
|
||||
* npm install next-auth @next-auth/typeorm-legacy-adapter typeorm
|
||||
* ```
|
||||
*
|
||||
* @module @auth/typeorm-adapter
|
||||
* @module @next-auth/typeorm-legacy-adapter
|
||||
*/
|
||||
import type {
|
||||
Adapter,
|
||||
AdapterUser,
|
||||
AdapterAccount,
|
||||
AdapterSession,
|
||||
} from "@auth/core/adapters"
|
||||
} from "next-auth/adapters"
|
||||
import { DataSourceOptions, DataSource, EntityManager } from "typeorm"
|
||||
import * as defaultEntities from "./entities"
|
||||
import { parseDataSourceConfig, updateConnectionEntities } from "./utils"
|
||||
@@ -29,7 +29,7 @@ export const entities = defaultEntities
|
||||
export type Entities = typeof entities
|
||||
|
||||
/** This is the interface for the TypeORM adapter options. */
|
||||
export interface TypeORMAdapterOptions {
|
||||
export interface TypeORMLegacyAdapterOptions {
|
||||
/**
|
||||
* The {@link https://orkhan.gitbook.io/typeorm/docs/entities TypeORM entities} to create the database tables from.
|
||||
*/
|
||||
@@ -70,16 +70,16 @@ export async function getManager(options: {
|
||||
*
|
||||
* ```javascript title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
* import { TypeORMAdapter } from "@auth/typeorm-adapter"
|
||||
* import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||
*
|
||||
*
|
||||
* export default NextAuth({
|
||||
* adapter: TypeORMAdapter("yourconnectionstring"),
|
||||
* adapter: TypeORMLegacyAdapter("yourconnectionstring"),
|
||||
* ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* `TypeORMAdapter` takes either a connection string, or a [`ConnectionOptions`](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) object as its first parameter.
|
||||
* `TypeORMLegacyAdapter` takes either a connection string, or a [`ConnectionOptions`](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) object as its first parameter.
|
||||
*
|
||||
* ## Advanced usage
|
||||
*
|
||||
@@ -93,7 +93,7 @@ export async function getManager(options: {
|
||||
*
|
||||
* 1. Create a file containing your modified entities:
|
||||
*
|
||||
* (The file below is based on the [default entities](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-typeorm/src/entities.ts))
|
||||
* (The file below is based on the [default entities](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-typeorm-legacy/src/entities.ts))
|
||||
*
|
||||
* ```diff title="lib/entities.ts"
|
||||
* import {
|
||||
@@ -231,15 +231,15 @@ export async function getManager(options: {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 2. Pass them to `TypeORMAdapter`
|
||||
* 2. Pass them to `TypeORMLegacyAdapter`
|
||||
*
|
||||
* ```javascript title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
* import { TypeORMAdapter } from "@auth/typeorm-adapter"
|
||||
* import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||
* import * as entities from "lib/entities"
|
||||
*
|
||||
* export default NextAuth({
|
||||
* adapter: TypeORMAdapter("yourconnectionstring", { entities }),
|
||||
* adapter: TypeORMLegacyAdapter("yourconnectionstring", { entities }),
|
||||
* ...
|
||||
* })
|
||||
* ```
|
||||
@@ -260,7 +260,7 @@ export async function getManager(options: {
|
||||
*
|
||||
* ```javascript title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
* import { TypeORMAdapter } from "@auth/typeorm-adapter"
|
||||
* import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||
* import { SnakeNamingStrategy } from 'typeorm-naming-strategies'
|
||||
* import { ConnectionOptions } from "typeorm"
|
||||
*
|
||||
@@ -275,14 +275,14 @@ export async function getManager(options: {
|
||||
* }
|
||||
*
|
||||
* export default NextAuth({
|
||||
* adapter: TypeORMAdapter(connection),
|
||||
* adapter: TypeORMLegacyAdapter(connection),
|
||||
* ...
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function TypeORMAdapter(
|
||||
export function TypeORMLegacyAdapter(
|
||||
dataSource: string | DataSourceOptions,
|
||||
options?: TypeORMAdapterOptions
|
||||
options?: TypeORMLegacyAdapterOptions
|
||||
): Adapter {
|
||||
const entities = options?.entities
|
||||
const c = {
|
||||
@@ -328,10 +328,8 @@ export function TypeORMAdapter(
|
||||
},
|
||||
async getUserByAccount(provider_providerAccountId) {
|
||||
const m = await getManager(c)
|
||||
// @ts-expect-error
|
||||
const account = await m.findOne<AdapterAccount & { user: AdapterUser }>(
|
||||
"AccountEntity",
|
||||
// @ts-expect-error
|
||||
{ where: provider_providerAccountId, relations: ["user"] }
|
||||
)
|
||||
if (!account) return null
|
||||
@@ -1,5 +1,5 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { TypeORMAdapter } from "../../src"
|
||||
import { TypeORMLegacyAdapter } from "../../src"
|
||||
import * as entities from "../custom-entities"
|
||||
import { db } from "../helpers"
|
||||
import { SnakeNamingStrategy } from "typeorm-naming-strategies"
|
||||
@@ -18,7 +18,7 @@ const mysqlConfig: ConnectionOptions = {
|
||||
}
|
||||
|
||||
runBasicTests({
|
||||
adapter: TypeORMAdapter(mysqlConfig, {
|
||||
adapter: TypeORMLegacyAdapter(mysqlConfig, {
|
||||
entities,
|
||||
}),
|
||||
db: db(mysqlConfig, entities),
|
||||
@@ -1,5 +1,5 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { TypeORMAdapter } from "../../src"
|
||||
import { TypeORMLegacyAdapter } from "../../src"
|
||||
import { db } from "../helpers"
|
||||
|
||||
const mysqlConfig = {
|
||||
@@ -13,6 +13,6 @@ const mysqlConfig = {
|
||||
}
|
||||
|
||||
runBasicTests({
|
||||
adapter: TypeORMAdapter(mysqlConfig),
|
||||
adapter: TypeORMLegacyAdapter(mysqlConfig),
|
||||
db: db(mysqlConfig),
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { TypeORMAdapter } from "../../src"
|
||||
import { TypeORMLegacyAdapter } from "../../src"
|
||||
import * as entities from "../custom-entities"
|
||||
import { db } from "../helpers"
|
||||
|
||||
@@ -7,7 +7,7 @@ const postgresConfig =
|
||||
"postgres://nextauth:password@localhost:5432/nextauth?synchronize=true"
|
||||
|
||||
runBasicTests({
|
||||
adapter: TypeORMAdapter(postgresConfig, {
|
||||
adapter: TypeORMLegacyAdapter(postgresConfig, {
|
||||
entities,
|
||||
}),
|
||||
db: db(postgresConfig, entities),
|
||||
@@ -1,11 +1,11 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { TypeORMAdapter } from "../../src"
|
||||
import { TypeORMLegacyAdapter } from "../../src"
|
||||
import { db } from "../helpers"
|
||||
|
||||
const postgresConfig =
|
||||
"postgres://nextauth:password@localhost:5432/nextauth?synchronize=true"
|
||||
|
||||
runBasicTests({
|
||||
adapter: TypeORMAdapter(postgresConfig),
|
||||
adapter: TypeORMLegacyAdapter(postgresConfig),
|
||||
db: db(postgresConfig),
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { TypeORMAdapter } from "../../src"
|
||||
import { TypeORMLegacyAdapter } from "../../src"
|
||||
import * as entities from "../custom-entities"
|
||||
import { db } from "../helpers"
|
||||
|
||||
@@ -11,7 +11,7 @@ const sqliteConfig = {
|
||||
}
|
||||
|
||||
runBasicTests({
|
||||
adapter: TypeORMAdapter(sqliteConfig, {
|
||||
adapter: TypeORMLegacyAdapter(sqliteConfig, {
|
||||
entities,
|
||||
}),
|
||||
db: db(sqliteConfig, entities),
|
||||
@@ -1,5 +1,5 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { TypeORMAdapter } from "../../src"
|
||||
import { TypeORMLegacyAdapter } from "../../src"
|
||||
import { db } from "../helpers"
|
||||
import { SnakeNamingStrategy } from "typeorm-naming-strategies"
|
||||
|
||||
@@ -14,6 +14,6 @@ const sqliteConfig: DataSourceOptions = {
|
||||
}
|
||||
|
||||
runBasicTests({
|
||||
adapter: TypeORMAdapter(sqliteConfig),
|
||||
adapter: TypeORMLegacyAdapter(sqliteConfig),
|
||||
db: db(sqliteConfig),
|
||||
})
|
||||
12
packages/adapter-typeorm-legacy/tsconfig.json
Normal file
12
packages/adapter-typeorm-legacy/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"stripInternal": true
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["tests", "dist", "jest.config.js"]
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"*.js",
|
||||
"*.d.ts",
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/core",
|
||||
"version": "0.8.2",
|
||||
"version": "0.7.1",
|
||||
"description": "Authentication for the Web.",
|
||||
"keywords": [
|
||||
"authentication",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
*
|
||||
* ```ts title=my-adapter.ts
|
||||
* import { type Adapter } from "@auth/core/adapters"
|
||||
* import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
* import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
* import { PrismaClient } from "@prisma/client"
|
||||
*
|
||||
* const prisma = new PrismaClient()
|
||||
|
||||
@@ -158,7 +158,7 @@ export async function handleLogin(
|
||||
const { provider: p } = options as InternalOptions<"oauth" | "oidc">
|
||||
const { type, provider, providerAccountId, userId, ...tokenSet } = account
|
||||
const defaults = { providerAccountId, provider, type, userId }
|
||||
account = Object.assign(p.account(tokenSet) ?? {}, defaults)
|
||||
account = Object.assign(p.account(tokenSet), defaults)
|
||||
|
||||
if (user) {
|
||||
// If the user is already signed in and the OAuth account isn't already associated
|
||||
|
||||
@@ -159,17 +159,8 @@ export class SessionStore {
|
||||
* 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(".")[1] || "0");
|
||||
const bSuffix = parseInt(b.split(".")[1] || "0");
|
||||
|
||||
return aSuffix - bSuffix;
|
||||
});
|
||||
|
||||
// Use the sorted keys to join the chunks in the correct order
|
||||
return sortedKeys.map(key => this.#chunks[key]).join("");
|
||||
get value() {
|
||||
return Object.values(this.#chunks)?.join("")
|
||||
}
|
||||
|
||||
/** Given a cookie, return a list of cookies, chunked to fit the allowed cookie size. */
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function AuthInternal<
|
||||
case "providers":
|
||||
return (await routes.providers(options.providers)) as any
|
||||
case "session": {
|
||||
const session = await routes.session({ sessionStore, options })
|
||||
const session = await routes.session(sessionStore, options)
|
||||
if (session.cookies) cookies.push(...session.cookies)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
return { ...session, cookies } as any
|
||||
@@ -177,22 +177,6 @@ export async function AuthInternal<
|
||||
return { ...callback, cookies }
|
||||
}
|
||||
break
|
||||
case "session": {
|
||||
if (options.csrfTokenVerified) {
|
||||
const session = await routes.session({
|
||||
options,
|
||||
sessionStore,
|
||||
newSession: request.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, cookies }
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,18 +171,18 @@ export async function handleOAuth(
|
||||
Math.floor(Date.now() / 1000) + Number(tokens.expires_in)
|
||||
}
|
||||
|
||||
const profileResult = await getUserAndAccount(
|
||||
const profileResult = await getUserAndProfile(
|
||||
profile,
|
||||
provider,
|
||||
tokens,
|
||||
logger
|
||||
)
|
||||
|
||||
return { ...profileResult, profile, cookies: resCookies }
|
||||
return { ...profileResult, cookies: resCookies }
|
||||
}
|
||||
|
||||
/** Returns the user and account that is going to be created in the database. */
|
||||
async function getUserAndAccount(
|
||||
/** Returns profile, raw profile and auth provider details */
|
||||
async function getUserAndProfile(
|
||||
OAuthProfile: Profile,
|
||||
provider: OAuthConfigInternal<any>,
|
||||
tokens: TokenSet,
|
||||
@@ -206,6 +206,7 @@ async function getUserAndAccount(
|
||||
providerAccountId: user.id.toString(),
|
||||
...tokens,
|
||||
},
|
||||
OAuthProfile,
|
||||
}
|
||||
} catch (e) {
|
||||
// If we didn't get a response either there was a problem with the provider
|
||||
|
||||
@@ -106,16 +106,14 @@ const defaultProfile: ProfileCallback<Profile> = (profile) => {
|
||||
* @see https://www.ietf.org/rfc/rfc6749.html#section-5.1
|
||||
* @see https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
|
||||
* @see https://authjs.dev/reference/adapters#account
|
||||
*
|
||||
* @todo Return `refresh_token` and `expires_at` as well when built-in
|
||||
* refresh token support is added. (Can make it opt-in first with a flag).
|
||||
*/
|
||||
const defaultAccount: AccountCallback = (account) => {
|
||||
return stripUndefined({
|
||||
access_token: account.access_token,
|
||||
id_token: account.id_token,
|
||||
refresh_token: account.refresh_token,
|
||||
expires_at: account.expires_at,
|
||||
scope: account.scope,
|
||||
token_type: account.token_type,
|
||||
session_state: account.session_state,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ export async function callback(params: {
|
||||
const {
|
||||
user: userFromProvider,
|
||||
account,
|
||||
profile: OAuthProfile,
|
||||
OAuthProfile,
|
||||
} = authorizationResult
|
||||
|
||||
// If we don't have a profile object then either something went wrong
|
||||
@@ -134,7 +134,6 @@ export async function callback(params: {
|
||||
account,
|
||||
profile: OAuthProfile,
|
||||
isNewUser,
|
||||
trigger: isNewUser ? "signUp" : "signIn",
|
||||
})
|
||||
|
||||
// Clear cookies if token is null
|
||||
@@ -245,7 +244,6 @@ export async function callback(params: {
|
||||
user: loggedInUser,
|
||||
account,
|
||||
isNewUser,
|
||||
trigger: isNewUser ? "signUp" : "signIn",
|
||||
})
|
||||
|
||||
// Clear cookies if token is null
|
||||
@@ -342,7 +340,6 @@ export async function callback(params: {
|
||||
// @ts-expect-error
|
||||
account,
|
||||
isNewUser: false,
|
||||
trigger: "signIn",
|
||||
})
|
||||
|
||||
// Clear cookies if token is null
|
||||
|
||||
@@ -6,13 +6,10 @@ import type { InternalOptions, ResponseInternal, Session } from "../../types.js"
|
||||
import type { SessionStore } from "../cookie.js"
|
||||
|
||||
/** Return a session object filtered via `callbacks.session` */
|
||||
export async function session(params: {
|
||||
export async function session(
|
||||
sessionStore: SessionStore,
|
||||
options: InternalOptions
|
||||
sessionStore: SessionStore
|
||||
isUpdate?: boolean
|
||||
newSession?: any
|
||||
}): Promise<ResponseInternal<Session | null>> {
|
||||
const { options, sessionStore, newSession, isUpdate } = params
|
||||
): Promise<ResponseInternal<Session | null>> {
|
||||
const {
|
||||
adapter,
|
||||
jwt,
|
||||
@@ -36,24 +33,23 @@ export async function session(params: {
|
||||
try {
|
||||
const decodedToken = await jwt.decode({ ...jwt, token: sessionToken })
|
||||
|
||||
if (!decodedToken) throw new Error("Invalid JWT")
|
||||
|
||||
// @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...").
|
||||
const session = {
|
||||
user: {
|
||||
name: decodedToken?.name,
|
||||
email: decodedToken?.email,
|
||||
image: decodedToken?.picture,
|
||||
},
|
||||
expires: newExpires.toISOString(),
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const token = await callbacks.jwt({ token: decodedToken })
|
||||
|
||||
if (token !== null) {
|
||||
// By default, only exposes a limited subset of information to the client
|
||||
// as needed for presentation purposes (e.g. "you are logged in as...").
|
||||
const session = {
|
||||
user: { name: token.name, email: token.email, image: token.picture },
|
||||
expires: newExpires.toISOString(),
|
||||
}
|
||||
// @ts-expect-error
|
||||
const newSession = await callbacks.session({ session, token })
|
||||
|
||||
@@ -129,12 +125,14 @@ export async function session(params: {
|
||||
// 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 },
|
||||
user: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
image: user.image,
|
||||
},
|
||||
expires: session.expires.toISOString(),
|
||||
},
|
||||
user,
|
||||
newSession,
|
||||
...(isUpdate ? { trigger: "update" } : {}),
|
||||
})
|
||||
|
||||
// Return session payload as response
|
||||
|
||||
@@ -32,14 +32,12 @@ export interface CredentialsConfig<
|
||||
* @example
|
||||
* ```ts
|
||||
* //...
|
||||
* async authorize(credentials, request) {
|
||||
* if(!isValidCredentials(credentials)) return null
|
||||
* async authorize(, request) {
|
||||
* const response = await fetch(request)
|
||||
* if(!response.ok) return null
|
||||
* return await response.json() ?? null
|
||||
* }
|
||||
* //...
|
||||
* ```
|
||||
*/
|
||||
authorize: (
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,7 @@ export type ProfileCallback<Profile> = (
|
||||
tokens: TokenSet
|
||||
) => Awaitable<User>
|
||||
|
||||
export type AccountCallback = (tokens: TokenSet) => TokenSet | undefined | void
|
||||
export type AccountCallback = (account: TokenSet) => TokenSet
|
||||
|
||||
export interface OAuthProviderButtonStyles {
|
||||
logo: string
|
||||
@@ -155,31 +155,7 @@ export interface OAuth2Config<Profile>
|
||||
* Receives the full {@link TokenSet} returned by the OAuth provider, and returns a subset.
|
||||
* It is used to create the account associated with a user in the database.
|
||||
*
|
||||
* :::note
|
||||
* You need to adjust your database's [Account model](https://authjs.dev/reference/adapters#account) to match the returned properties.
|
||||
* Check out the documentation of your [database adapter](https://authjs.dev/reference/adapters) for more information.
|
||||
* :::
|
||||
*
|
||||
* Defaults to: `access_token`, `id_token`, `refresh_token`, `expires_at`, `scope`, `token_type`, `session_state`
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import GitHub from "@auth/core/providers/github"
|
||||
* // ...
|
||||
* GitHub({
|
||||
* account(account) {
|
||||
* // https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens#refreshing-a-user-access-token-with-a-refresh-token
|
||||
* const refresh_token_expires_at =
|
||||
* Math.floor(Date.now() / 1000) + Number(account.refresh_token_expires_in)
|
||||
* return {
|
||||
* access_token: account.access_token,
|
||||
* expires_at: account.expires_at,
|
||||
* refresh_token: account.refresh_token,
|
||||
* refresh_token_expires_at
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* Defaults to: `access_token` and `id_token`
|
||||
*
|
||||
* @see [Database Adapter: Account model](https://authjs.dev/reference/adapters#account)
|
||||
* @see https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
|
||||
|
||||
@@ -98,15 +98,7 @@ export interface Theme {
|
||||
*/
|
||||
export type TokenSet = Partial<
|
||||
OAuth2TokenEndpointResponse | OpenIDTokenEndpointResponse
|
||||
> & {
|
||||
/**
|
||||
* Date of when the `access_token` expires in seconds.
|
||||
* This value is calculated from the `expires_in` value.
|
||||
*
|
||||
* @see https://www.ietf.org/rfc/rfc6749.html#section-4.2.2
|
||||
*/
|
||||
expires_at?: number
|
||||
}
|
||||
>
|
||||
|
||||
/**
|
||||
* Usually contains information about the provider being used
|
||||
@@ -238,86 +230,40 @@ export interface CallbacksOptions<P = Profile, A = Account> {
|
||||
* 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.
|
||||
*
|
||||
* @see [`jwt` callback](https://authjs.dev/reference/core/types#jwt)
|
||||
* [Documentation](https://authjs.dev/guides/basics/callbacks#session-callback) |
|
||||
* [`jwt` callback](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
|
||||
* [`useSession`](https://authjs.dev/reference/react/#usesession) |
|
||||
* [`getSession`](https://authjs.dev/reference/utilities/#getsession) |
|
||||
*
|
||||
*/
|
||||
session: (
|
||||
params:
|
||||
| {
|
||||
session: Session
|
||||
/** Available when {@link AuthConfig.session} is set to `strategy: "jwt"` */
|
||||
token: JWT
|
||||
/** Available when {@link AuthConfig.session} is set to `strategy: "database"`. */
|
||||
user: AdapterUser
|
||||
} & {
|
||||
/**
|
||||
* Available when using {@link AuthConfig.session} `strategy: "database"` and an update is triggered for the session.
|
||||
*
|
||||
* :::note
|
||||
* You should validate this data before using it.
|
||||
* :::
|
||||
*/
|
||||
newSession: any
|
||||
trigger: "update"
|
||||
}
|
||||
) => Awaitable<Session | DefaultSession>
|
||||
session: (params: {
|
||||
session: Session
|
||||
user: User | AdapterUser
|
||||
token: JWT
|
||||
}) => Awaitable<Session>
|
||||
/**
|
||||
* 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.
|
||||
* Anything else will be kept inaccessible from the client.
|
||||
*
|
||||
* The JWT is encrypted by default.
|
||||
* Returning `null` will invalidate the JWT session by clearing
|
||||
* the user's cookies. You'll still have to monitor and invalidate
|
||||
* unexpired tokens from future requests yourself to prevent
|
||||
* unauthorized access.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
|
||||
* [`session` callback](https://next-auth.js.org/configuration/callbacks#session-callback)
|
||||
* By default the JWT is encrypted.
|
||||
*
|
||||
* [Documentation](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
|
||||
* [`session` callback](https://authjs.dev/guides/basics/callbacks#session-callback)
|
||||
*/
|
||||
jwt: (params: {
|
||||
/**
|
||||
* When `trigger` is `"signIn"` or `"signUp"`, it will be a subset of {@link JWT},
|
||||
* `name`, `email` and `image` 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://authjs.dev/reference/core/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"`.
|
||||
*/
|
||||
user?: User | AdapterUser
|
||||
account?: A | null
|
||||
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 AuthConfig.session}.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 AuthConfig.session} `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 | null>
|
||||
}
|
||||
|
||||
@@ -497,9 +443,7 @@ export type InternalProvider<T = ProviderType> = (T extends "oauth"
|
||||
* :::
|
||||
* - **`"error"`**: Renders the built-in error page.
|
||||
* - **`"providers"`**: Returns a client-safe list of all configured providers.
|
||||
* - **`"session"`**:
|
||||
* - **`GET**`: Returns the user's session if it exists, otherwise `null`.
|
||||
* - **`POST**`: Updates the user's session and returns the updated session.
|
||||
* - **`"session"`**: Returns the user's session if it exists, otherwise `null`.
|
||||
* - **`"signin"`**:
|
||||
* - **`GET`**: Renders the built-in sign-in page.
|
||||
* - **`POST`**: Initiates the sign-in flow.
|
||||
|
||||
@@ -224,13 +224,6 @@ We're happy to announce we've recently created an [OpenCollective](https://openc
|
||||
<div>WorkOS</div><br />
|
||||
<sub>🥉 Bronze Financial Sponsor</sub>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://www.descope.com" target="_blank">
|
||||
<img width="128px" src="https://avatars.githubusercontent.com/u/97479186?v=4" alt="Descope Logo" />
|
||||
</a><br />
|
||||
<div>Descope</div><br />
|
||||
<sub>🥉 Bronze Financial Sponsor</sub>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<a href="https://checklyhq.com" target="_blank">
|
||||
<img width="128px" src="https://avatars.githubusercontent.com/u/25982255?v=4" alt="Checkly Logo" />
|
||||
|
||||
@@ -65,8 +65,10 @@
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@panva/hkdf": "^1.0.2",
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@panva/hkdf": "^1.0.1",
|
||||
"@simplewebauthn/browser": "^6.2.2",
|
||||
"@simplewebauthn/server": "^6.2.2",
|
||||
"cookie": "^0.5.0",
|
||||
"jose": "^4.11.4",
|
||||
"oauth": "^0.9.15",
|
||||
@@ -96,6 +98,7 @@
|
||||
"@babel/preset-typescript": "^7.17.12",
|
||||
"@edge-runtime/jest-environment": "1.1.0-beta.35",
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"@simplewebauthn/typescript-types": "6.3.0-alpha.1",
|
||||
"@swc/core": "^1.2.198",
|
||||
"@swc/jest": "^0.2.21",
|
||||
"@testing-library/dom": "^8.13.0",
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
generateAuthenticationOptions,
|
||||
verifyAuthenticationResponse,
|
||||
} from '@simplewebauthn/server';
|
||||
import { getUserFromDb } from "./helpers"
|
||||
|
||||
import type { } from '@simplewebauthn/typescript-types'
|
||||
|
||||
/*
|
||||
* @desc GET /api/auth/webauthn/authenticate
|
||||
* @type object
|
||||
**/
|
||||
const generateAuthOptions = (userId) => {
|
||||
// (Pseudocode) Retrieve the logged-in user
|
||||
const user: UserModel = getUserFromDb(userId)
|
||||
// (Pseudocode) Retrieve any of the user's previously-
|
||||
// registered authenticators
|
||||
const userAuthenticators: Authenticator[] = getUserAuthenticators(user)
|
||||
|
||||
const options = generateAuthenticationOptions({
|
||||
// Require users to use a previously-registered authenticator
|
||||
allowCredentials: userAuthenticators.map((authenticator) => ({
|
||||
id: authenticator.credentialID,
|
||||
type: "public-key",
|
||||
// Optional
|
||||
transports: authenticator.transports,
|
||||
})),
|
||||
userVerification: "preferred",
|
||||
})
|
||||
|
||||
// (Pseudocode) Remember this challenge for this user
|
||||
setUserCurrentChallenge(user, options.challenge)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
/*
|
||||
* @desc POST to /api/auth/webauthn/authenticate
|
||||
* @type object
|
||||
* @param body: AuthenticationCredentialJSON
|
||||
**/
|
||||
const verifyAuthResponse = (req) => {
|
||||
const { body } = req;
|
||||
|
||||
// (Pseudocode) Retrieve the logged-in user
|
||||
const user: UserModel = getUserFromDB(loggedInUserId);
|
||||
// (Pseudocode) Get `options.challenge` that was saved above
|
||||
const expectedChallenge: string = getUserCurrentChallenge(user);
|
||||
// (Pseudocode} Retrieve an authenticator from the DB that
|
||||
// should match the `id` in the returned credential
|
||||
const authenticator = getUserAuthenticator(user, body.id);
|
||||
|
||||
if (!authenticator) {
|
||||
throw new Error(`Could not find authenticator ${body.id} for user ${user.id}`);
|
||||
}
|
||||
|
||||
let verification;
|
||||
try {
|
||||
verification = await verifyAuthenticationResponse({
|
||||
credential: body,
|
||||
expectedChallenge,
|
||||
expectedOrigin: origin,
|
||||
expectedRPID: rpID,
|
||||
authenticator,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(400).send({ error: error.message });
|
||||
}
|
||||
|
||||
const { verified } = verification;
|
||||
return verified
|
||||
}
|
||||
27
packages/next-auth/src/core/lib/webauthn/helpers.ts
Normal file
27
packages/next-auth/src/core/lib/webauthn/helpers.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export const saveRegistrationInfo = ({ verification, registrationInfo }) => {
|
||||
const { registrationInfo } = verification
|
||||
const { credentialPublicKey, credentialID, counter } = registrationInfo
|
||||
|
||||
const newAuthenticator: Authenticator = {
|
||||
credentialID,
|
||||
credentialPublicKey,
|
||||
counter,
|
||||
}
|
||||
|
||||
// (Pseudocode) Save the authenticator info so that we can
|
||||
// get it by user ID later
|
||||
saveNewUserAuthenticatorInDB(user, newAuthenticator)
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// (Pseudocode) Fetch User from adapter
|
||||
export const getUserFromDb = (userId) => {
|
||||
return db.query('Users').where({ userId })
|
||||
}
|
||||
|
||||
export const updateRegistrationCounter = ({ verification, authenticationInfo }) = {
|
||||
const { authenticationInfo } = verification;
|
||||
const { newCounter } = authenticationInfo;
|
||||
|
||||
saveUpdatedAuthenticatorCounter(authenticator, newCounter);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import SimpleWebAuthnServer from "@simplewebauthn/server"
|
||||
import { saveRegistrationInfo } from './helpers'
|
||||
|
||||
import type { Authenticator } from '../../types'
|
||||
|
||||
/*
|
||||
* @desc GET - /api/auth/webauthn/register
|
||||
* @type object
|
||||
**/
|
||||
const generateRegistration = () => {
|
||||
// Human-readable title for your website
|
||||
const rpName = "SimpleWebAuthn Example"
|
||||
// A unique identifier for your website
|
||||
const rpID = "localhost"
|
||||
// The URL at which registrations and authentications should occur
|
||||
const origin = `https://${rpID}`
|
||||
|
||||
// (Pseudocode) Retrieve the user from the database
|
||||
// after they've logged in
|
||||
const user: UserModel = getUserFromDB(loggedInUserId)
|
||||
// (Pseudocode) Retrieve any of the user's previously-
|
||||
// registered authenticators
|
||||
const userAuthenticators: Authenticator[] = getUserAuthenticators(user)
|
||||
|
||||
const options = generateRegistrationOptions({
|
||||
rpName,
|
||||
rpID,
|
||||
userID: user.id,
|
||||
userName: user.username,
|
||||
// Don't prompt users for additional information about the authenticator
|
||||
// (Recommended for smoother UX)
|
||||
attestationType: "none",
|
||||
// Prevent users from re-registering existing authenticators
|
||||
excludeCredentials: userAuthenticators.map((authenticator) => ({
|
||||
id: authenticator.credentialID,
|
||||
type: "public-key",
|
||||
// Optional
|
||||
transports: authenticator.transports,
|
||||
})),
|
||||
})
|
||||
|
||||
// (Pseudocode) Remember the challenge for this user
|
||||
setUserCurrentChallenge(user, options.challenge)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
/*
|
||||
* @desc POST /api/auth/webauthn/register
|
||||
* @type object
|
||||
**/
|
||||
const verifyRegistration = (req) => {
|
||||
const { body } = req
|
||||
|
||||
// (Pseudocode) Retrieve the logged-in user
|
||||
const user: UserModel = getUserFromDB(loggedInUserId)
|
||||
// (Pseudocode) Get `options.challenge` that was saved above
|
||||
const expectedChallenge: string = getUserCurrentChallenge(user)
|
||||
|
||||
let verification
|
||||
try {
|
||||
verification = await verifyRegistrationResponse({
|
||||
credential: body,
|
||||
expectedChallenge,
|
||||
expectedOrigin: origin,
|
||||
expectedRPID: rpID,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return res.status(400).send({ error: error.message })
|
||||
}
|
||||
|
||||
const { verified } = verification
|
||||
if (verified) {
|
||||
saveRegistrationInfo({ verification, registrationInfo })
|
||||
}
|
||||
return { verified }
|
||||
}
|
||||
7
packages/next-auth/src/core/routes/webauthn.ts
Normal file
7
packages/next-auth/src/core/routes/webauthn.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ResponseInternal } from ".."
|
||||
|
||||
export default function webauthn(): ResponseInternal<> {
|
||||
// TODO:
|
||||
// Assign `/core/lib/webauthn/{authentication-handler,registration-handler}.ts`
|
||||
// handler functions to the endpoint/http verb combo listed in their @desc
|
||||
}
|
||||
@@ -625,3 +625,36 @@ export type NextAuthApiHandler<Result = void, Response = any> = (
|
||||
req: NextAuthRequest,
|
||||
res: NextAuthResponse<Response>
|
||||
) => Awaitable<Result>
|
||||
|
||||
/** @internal */
|
||||
export type UserModel = {
|
||||
id: string
|
||||
username: string
|
||||
currentChallenge?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* It is strongly advised that authenticators get their own DB
|
||||
* table, ideally with a foreign key to a specific UserModel.
|
||||
*
|
||||
* "SQL" tags below are suggestions for column data types and
|
||||
* how best to store data received during registration for use
|
||||
* in subsequent authentications.
|
||||
* @internal
|
||||
*/
|
||||
export type Authenticator = {
|
||||
// SQL: Encode to base64url then store as `TEXT`. Index this column
|
||||
credentialID: Buffer
|
||||
// SQL: Store raw bytes as `BYTEA`/`BLOB`/etc...
|
||||
credentialPublicKey: Buffer
|
||||
// SQL: Consider `BIGINT` since some authenticators return atomic timestamps as counters
|
||||
counter: number
|
||||
// SQL: `VARCHAR(32)` or similar, longest possible value is currently 12 characters
|
||||
// Ex: 'singleDevice' | 'multiDevice'
|
||||
credentialDeviceType: CredentialDeviceType
|
||||
// SQL: `BOOL` or whatever similar type is supported
|
||||
credentialBackedUp: boolean
|
||||
// SQL: `VARCHAR(255)` and store string array as a CSV string
|
||||
// Ex: ['usb' | 'ble' | 'nfc' | 'internal']
|
||||
transports?: AuthenticatorTransport[]
|
||||
}
|
||||
|
||||
4190
pnpm-lock.yaml
generated
4190
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
10
turbo.json
10
turbo.json
@@ -3,7 +3,7 @@
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**/*", "*.js", "*.d.ts", "*.d.ts.map"]
|
||||
"outputs": ["dist/**/*"]
|
||||
},
|
||||
"next-auth#build": {
|
||||
"dependsOn": ["^build"],
|
||||
@@ -53,7 +53,6 @@
|
||||
"docs#dev": {
|
||||
"dependsOn": [
|
||||
"@auth/core#build",
|
||||
"@auth/prisma-adapter#build",
|
||||
"@auth/sveltekit#build",
|
||||
"@next-auth/dgraph-adapter#build",
|
||||
"@next-auth/dynamodb-adapter#build",
|
||||
@@ -63,9 +62,10 @@
|
||||
"@next-auth/mongodb-adapter#build",
|
||||
"@next-auth/neo4j-adapter#build",
|
||||
"@next-auth/pouchdb-adapter#build",
|
||||
"@next-auth/prisma-adapter#build",
|
||||
"@next-auth/sequelize-adapter#build",
|
||||
"@next-auth/supabase-adapter#build",
|
||||
"@auth/typeorm-adapter#build",
|
||||
"@next-auth/typeorm-legacy-adapter#build",
|
||||
"@next-auth/upstash-redis-adapter#build",
|
||||
"@next-auth/xata-adapter#build",
|
||||
"^build",
|
||||
@@ -76,7 +76,6 @@
|
||||
"docs#build": {
|
||||
"dependsOn": [
|
||||
"@auth/core#build",
|
||||
"@auth/prisma-adapter#build",
|
||||
"@auth/sveltekit#build",
|
||||
"@next-auth/dgraph-adapter#build",
|
||||
"@next-auth/dynamodb-adapter#build",
|
||||
@@ -86,9 +85,10 @@
|
||||
"@next-auth/mongodb-adapter#build",
|
||||
"@next-auth/neo4j-adapter#build",
|
||||
"@next-auth/pouchdb-adapter#build",
|
||||
"@next-auth/prisma-adapter#build",
|
||||
"@next-auth/sequelize-adapter#build",
|
||||
"@next-auth/supabase-adapter#build",
|
||||
"@auth/typeorm-adapter#build",
|
||||
"@next-auth/typeorm-legacy-adapter#build",
|
||||
"@next-auth/upstash-redis-adapter#build",
|
||||
"@next-auth/xata-adapter#build",
|
||||
"^build",
|
||||
|
||||
Reference in New Issue
Block a user