mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
36 Commits
ndom91/web
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b414bd7b5 | ||
|
|
37bb6ebd2c | ||
|
|
2ecf52c342 | ||
|
|
cda07c239e | ||
|
|
fa60b79abe | ||
|
|
39e1a76e8f | ||
|
|
953ef9d04a | ||
|
|
94f3031765 | ||
|
|
ad7bf07ddf | ||
|
|
f30308ac30 | ||
|
|
6eaaeb15e9 | ||
|
|
8b3f0696a5 | ||
|
|
c69a157832 | ||
|
|
60af446338 | ||
|
|
ce85444760 | ||
|
|
142abe3eea | ||
|
|
da211e6cbe | ||
|
|
79ad6156ed | ||
|
|
28f287d63e | ||
|
|
1ab77d0e11 | ||
|
|
787c1ff7d0 | ||
|
|
208b3b4a43 | ||
|
|
c4f6330f70 | ||
|
|
44127068e1 | ||
|
|
9e3f1aacf7 | ||
|
|
83051c6862 | ||
|
|
f1acab67e6 | ||
|
|
6a31ed3216 | ||
|
|
0998fc0b98 | ||
|
|
bd20d750c2 | ||
|
|
8e29b4df0c | ||
|
|
9632a56d45 | ||
|
|
12161b9613 | ||
|
|
a3b5276a5a | ||
|
|
7c1078b9a9 | ||
|
|
37d3461155 |
2
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
2
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -29,7 +29,7 @@ body:
|
||||
- "@next-auth/mongodb-adapter"
|
||||
- "@next-auth/neo4j-adapter"
|
||||
- "@next-auth/pouchdb-adapter"
|
||||
- "@next-auth/prisma-adapter"
|
||||
- "@auth/prisma-adapter"
|
||||
- "@next-auth/sequelize-adapter"
|
||||
- "@next-auth/supabase-adapter"
|
||||
- "@next-auth/typeorm-legacy-adapter"
|
||||
|
||||
2
.github/issue-labeler.yml
vendored
2
.github/issue-labeler.yml
vendored
@@ -25,7 +25,7 @@ pouchdb:
|
||||
- "@next-auth/pouchdb-adapter"
|
||||
|
||||
prisma:
|
||||
- "@next-auth/prisma-adapter"
|
||||
- "@auth/prisma-adapter"
|
||||
|
||||
sequelize:
|
||||
- "@next-auth/sequelize-adapter"
|
||||
|
||||
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@@ -15,16 +15,46 @@ on:
|
||||
type: choice
|
||||
description: Package name (npm)
|
||||
options:
|
||||
- "@auth/nextjs"
|
||||
- "@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-legacy-adapter"
|
||||
- "@auth/upstash-redis-adapter"
|
||||
- "@auth/xata-adapter"
|
||||
- "next-auth"
|
||||
# TODO: Infer from package name
|
||||
path:
|
||||
type: choice
|
||||
description: Directory name (packages/*)
|
||||
options:
|
||||
- "frameworks-nextjs"
|
||||
- "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-legacy"
|
||||
- "adapter-upstash-redis"
|
||||
- "adapter-xata"
|
||||
- "next-auth"
|
||||
|
||||
jobs:
|
||||
@@ -42,6 +72,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run tests
|
||||
@@ -91,6 +122,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Publish to npm and GitHub
|
||||
@@ -115,6 +147,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Determine version
|
||||
@@ -153,6 +186,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Determine version
|
||||
@@ -162,6 +196,7 @@ 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,7 +15,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@next-auth/fauna-adapter": "workspace:*",
|
||||
"@next-auth/prisma-adapter": "workspace:*",
|
||||
"@auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/supabase-adapter": "workspace:*",
|
||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
||||
"@prisma/client": "^3",
|
||||
|
||||
@@ -37,7 +37,7 @@ import WorkOS from "next-auth/providers/workos"
|
||||
|
||||
// // Prisma
|
||||
// import { PrismaClient } from "@prisma/client"
|
||||
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
// import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
// const client = globalThis.prisma || new PrismaClient()
|
||||
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||
// const adapter = PrismaAdapter(client)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
"@next-auth/fauna-adapter": "workspace:*",
|
||||
"@next-auth/prisma-adapter": "workspace:*",
|
||||
"@auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/supabase-adapter": "workspace:*",
|
||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
||||
"@prisma/client": "^3",
|
||||
|
||||
@@ -6,24 +6,26 @@ 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 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 Okta from "@auth/core/providers/okta"
|
||||
import Osu from "@auth/core/providers/osu"
|
||||
import Patreon from "@auth/core/providers/patreon"
|
||||
@@ -39,7 +41,7 @@ import WorkOS from "@auth/core/providers/workos"
|
||||
|
||||
// // Prisma
|
||||
// import { PrismaClient } from "@prisma/client"
|
||||
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
// import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
// const client = globalThis.prisma || new PrismaClient()
|
||||
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||
// const adapter = PrismaAdapter(client)
|
||||
@@ -75,21 +77,12 @@ 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 }),
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
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://docs-git-misc-docs-nextauthjs.vercel.app/configuration/nextjs#middleware">
|
||||
<a href="https://next-auth.js.org/configuration/nextjs#middleware">
|
||||
the docs
|
||||
</a>
|
||||
.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Session } from "@auth/core"
|
||||
import { Session } from "@auth/core/types"
|
||||
|
||||
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("/api/auth/csrf")
|
||||
const { csrfToken } = await $fetch<{ csrfToken: string }>("/api/auth/csrf")
|
||||
|
||||
console.log(_signInUrl)
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { AuthHandler, AuthOptions, Session } from "@auth/core"
|
||||
import { AuthConfig, Session } from "@auth/core/types"
|
||||
import { Auth } from "@auth/core"
|
||||
import { fromNodeMiddleware, H3Event } from "h3"
|
||||
import getURL from "requrl"
|
||||
import { createMiddleware } from "@hattip/adapter-node"
|
||||
|
||||
export function NuxtAuthHandler(options: AuthOptions) {
|
||||
export function NuxtAuthHandler(options: AuthConfig) {
|
||||
async function handler(ctx: { request: Request }) {
|
||||
options.trustHost ??= true
|
||||
|
||||
return AuthHandler(ctx.request, options)
|
||||
return Auth(ctx.request, options)
|
||||
}
|
||||
|
||||
const middleware = createMiddleware(handler)
|
||||
@@ -17,7 +18,7 @@ export function NuxtAuthHandler(options: AuthOptions) {
|
||||
|
||||
export async function getSession(
|
||||
event: H3Event,
|
||||
options: AuthOptions
|
||||
options: AuthConfig
|
||||
): Promise<Session | null> {
|
||||
options.trustHost ??= true
|
||||
|
||||
@@ -30,7 +31,7 @@ export async function getSession(
|
||||
nodeHeaders.append(key, headers[key] as any)
|
||||
})
|
||||
|
||||
const response = await AuthHandler(
|
||||
const response = await Auth(
|
||||
new Request(url, { headers: nodeHeaders }),
|
||||
options
|
||||
)
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "playground-nuxt",
|
||||
"name": "next-auth-nuxt",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt prepare && nuxt build",
|
||||
"dev": "nuxt prepare && export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt prepare && nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^0.1.1",
|
||||
"eslint": "^8.29.0",
|
||||
"h3": "1.0.2",
|
||||
"nuxt": "3.0.0"
|
||||
"h3": "1.6.6",
|
||||
"nuxt": "3.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
"@hattip/adapter-node": "^0.0.22",
|
||||
"@hattip/adapter-node": "^0.0.34",
|
||||
"requrl": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Session } from "@auth/core"
|
||||
import { Session } from "@auth/core/types"
|
||||
|
||||
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 { AuthOptions } from "@auth/core"
|
||||
import type { AuthConfig } from "@auth/core"
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
export const authOptions: AuthOptions = {
|
||||
export const authOptions = {
|
||||
secret: runtimeConfig.secret,
|
||||
providers: [
|
||||
GithubProvider({
|
||||
@@ -12,6 +12,6 @@ export const authOptions: AuthOptions = {
|
||||
clientSecret: runtimeConfig.github.clientSecret,
|
||||
}),
|
||||
],
|
||||
}
|
||||
} as AuthConfig
|
||||
|
||||
export default NuxtAuthHandler(authOptions)
|
||||
|
||||
@@ -243,10 +243,13 @@ 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 "@next-auth/prisma-adapter"
|
||||
import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
@@ -110,10 +110,6 @@ 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,8 +96,13 @@ 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
|
||||
@@ -137,7 +142,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`, `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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ 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.8.8",
|
||||
"turbo": "1.10.1",
|
||||
"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/@next-auth/prisma-adapter">
|
||||
<a href="https://npm.im/@auth/prisma-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<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 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>
|
||||
<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 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>
|
||||
<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,14 +1,29 @@
|
||||
{
|
||||
"name": "@next-auth/prisma-adapter",
|
||||
"version": "1.0.6",
|
||||
"description": "Prisma adapter for next-auth.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"name": "@auth/prisma-adapter",
|
||||
"version": "1.0.0",
|
||||
"description": "Prisma adapter for Auth.js",
|
||||
"homepage": "https://authjs.dev/reference/adapter/prisma",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "William Luke",
|
||||
"main": "dist/index.js",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
@@ -32,22 +47,19 @@
|
||||
"dev": "prisma generate && tsc -w",
|
||||
"studio": "prisma studio"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@prisma/client": ">=2.26.0 || >=3",
|
||||
"next-auth": "^4"
|
||||
"@prisma/client": ">=2.26.0 || >=3 || >=4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:*",
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"@prisma/client": "^3.10.0",
|
||||
"@prisma/client": "^4.15.0",
|
||||
"jest": "^27.4.3",
|
||||
"mongodb": "^4.4.0",
|
||||
"next-auth": "workspace:*",
|
||||
"prisma": "^3.10.0"
|
||||
"prisma": "^4.15.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install next-auth @prisma/client @next-auth/prisma-adapter
|
||||
* npm install @prisma/client @auth/prisma-adapter
|
||||
* npm install prisma --save-dev
|
||||
* ```
|
||||
*
|
||||
* @module @next-auth/prisma-adapter
|
||||
* @module @auth/prisma-adapter
|
||||
*/
|
||||
import type { PrismaClient, Prisma } from "@prisma/client"
|
||||
import type { Adapter, AdapterAccount } from "next-auth/adapters"
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
|
||||
/**
|
||||
* ## Setup
|
||||
@@ -26,7 +26,7 @@ import type { Adapter, AdapterAccount } from "next-auth/adapters"
|
||||
* ```js title="pages/api/auth/[...nextauth].js"
|
||||
* import NextAuth from "next-auth"
|
||||
* import GoogleProvider from "next-auth/providers/google"
|
||||
* import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
* import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
* import { PrismaClient } from "@prisma/client"
|
||||
*
|
||||
* const prisma = new PrismaClient()
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||
"extends": "@next-auth/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["tests", "dist", "jest.config.js"]
|
||||
}
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"*.js",
|
||||
"*.d.ts",
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Adapter } from "next-auth/adapters"
|
||||
import type { Adapter } from "@auth/core/adapters"
|
||||
import { createHash, randomUUID } from "crypto"
|
||||
|
||||
const requiredMethods = [
|
||||
@@ -58,7 +58,8 @@ export async function runBasicTests(options: TestOptions) {
|
||||
await options.db.connect?.()
|
||||
})
|
||||
|
||||
const { adapter, db, skipTests } = options
|
||||
const { adapter: _adapter, db, skipTests } = options
|
||||
const adapter = _adapter as Required<Adapter>
|
||||
|
||||
afterAll(async () => {
|
||||
// @ts-expect-error This is only used for the TypeORM adapter
|
||||
@@ -88,7 +89,7 @@ export async function runBasicTests(options: TestOptions) {
|
||||
providerAccountId: randomUUID(),
|
||||
type: "oauth",
|
||||
access_token: randomUUID(),
|
||||
expires_at: ONE_MONTH,
|
||||
expires_at: ONE_MONTH / 1000,
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/core",
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.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 "@next-auth/prisma-adapter"
|
||||
* import { PrismaAdapter } from "@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
|
||||
|
||||
@@ -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,6 +177,22 @@ 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 getUserAndProfile(
|
||||
const profileResult = await getUserAndAccount(
|
||||
profile,
|
||||
provider,
|
||||
tokens,
|
||||
logger
|
||||
)
|
||||
|
||||
return { ...profileResult, cookies: resCookies }
|
||||
return { ...profileResult, profile, cookies: resCookies }
|
||||
}
|
||||
|
||||
/** Returns profile, raw profile and auth provider details */
|
||||
async function getUserAndProfile(
|
||||
/** Returns the user and account that is going to be created in the database. */
|
||||
async function getUserAndAccount(
|
||||
OAuthProfile: Profile,
|
||||
provider: OAuthConfigInternal<any>,
|
||||
tokens: TokenSet,
|
||||
@@ -206,7 +206,6 @@ async function getUserAndProfile(
|
||||
providerAccountId: user.id.toString(),
|
||||
...tokens,
|
||||
},
|
||||
OAuthProfile,
|
||||
}
|
||||
} catch (e) {
|
||||
// If we didn't get a response either there was a problem with the provider
|
||||
|
||||
@@ -106,14 +106,16 @@ 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,
|
||||
OAuthProfile,
|
||||
profile: OAuthProfile,
|
||||
} = authorizationResult
|
||||
|
||||
// If we don't have a profile object then either something went wrong
|
||||
@@ -134,6 +134,7 @@ export async function callback(params: {
|
||||
account,
|
||||
profile: OAuthProfile,
|
||||
isNewUser,
|
||||
trigger: isNewUser ? "signUp" : "signIn",
|
||||
})
|
||||
|
||||
// Clear cookies if token is null
|
||||
@@ -244,6 +245,7 @@ export async function callback(params: {
|
||||
user: loggedInUser,
|
||||
account,
|
||||
isNewUser,
|
||||
trigger: isNewUser ? "signUp" : "signIn",
|
||||
})
|
||||
|
||||
// Clear cookies if token is null
|
||||
@@ -340,6 +342,7 @@ export async function callback(params: {
|
||||
// @ts-expect-error
|
||||
account,
|
||||
isNewUser: false,
|
||||
trigger: "signIn",
|
||||
})
|
||||
|
||||
// Clear cookies if token is null
|
||||
|
||||
@@ -6,10 +6,13 @@ 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(
|
||||
sessionStore: SessionStore,
|
||||
export async function session(params: {
|
||||
options: InternalOptions
|
||||
): Promise<ResponseInternal<Session | null>> {
|
||||
sessionStore: SessionStore
|
||||
isUpdate?: boolean
|
||||
newSession?: any
|
||||
}): Promise<ResponseInternal<Session | null>> {
|
||||
const { options, sessionStore, newSession, isUpdate } = params
|
||||
const {
|
||||
adapter,
|
||||
jwt,
|
||||
@@ -33,23 +36,24 @@ export async function session(
|
||||
try {
|
||||
const decodedToken = await jwt.decode({ ...jwt, token: sessionToken })
|
||||
|
||||
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(),
|
||||
}
|
||||
if (!decodedToken) throw new Error("Invalid JWT")
|
||||
|
||||
// @ts-expect-error
|
||||
const token = await callbacks.jwt({ token: decodedToken })
|
||||
const token = await callbacks.jwt({
|
||||
token: decodedToken,
|
||||
...(isUpdate && { trigger: "update" }),
|
||||
session: newSession,
|
||||
})
|
||||
|
||||
const newExpires = fromDate(sessionMaxAge)
|
||||
|
||||
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 })
|
||||
|
||||
@@ -125,14 +129,12 @@ export async function 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,
|
||||
},
|
||||
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,12 +32,14 @@ export interface CredentialsConfig<
|
||||
* @example
|
||||
* ```ts
|
||||
* //...
|
||||
* async authorize(, request) {
|
||||
* async authorize(credentials, request) {
|
||||
* if(!isValidCredentials(credentials)) return null
|
||||
* 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 = (account: TokenSet) => TokenSet
|
||||
export type AccountCallback = (tokens: TokenSet) => TokenSet | undefined | void
|
||||
|
||||
export interface OAuthProviderButtonStyles {
|
||||
logo: string
|
||||
@@ -155,7 +155,31 @@ 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.
|
||||
*
|
||||
* Defaults to: `access_token` and `id_token`
|
||||
* :::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
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @see [Database Adapter: Account model](https://authjs.dev/reference/adapters#account)
|
||||
* @see https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
|
||||
|
||||
@@ -98,7 +98,15 @@ 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
|
||||
@@ -230,40 +238,86 @@ 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.
|
||||
*
|
||||
* [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) |
|
||||
*
|
||||
* @see [`jwt` callback](https://authjs.dev/reference/core/types#jwt)
|
||||
*/
|
||||
session: (params: {
|
||||
session: Session
|
||||
user: User | AdapterUser
|
||||
token: JWT
|
||||
}) => Awaitable<Session>
|
||||
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>
|
||||
/**
|
||||
* 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 inaccessible from the client.
|
||||
* Anything else will be kept from your front-end.
|
||||
*
|
||||
* 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.
|
||||
* The JWT is encrypted by default.
|
||||
*
|
||||
* 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)
|
||||
* [Documentation](https://next-auth.js.org/configuration/callbacks#jwt-callback) |
|
||||
* [`session` callback](https://next-auth.js.org/configuration/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
|
||||
user?: User | AdapterUser
|
||||
account?: A | null
|
||||
/**
|
||||
* 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"`.
|
||||
*/
|
||||
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>
|
||||
}
|
||||
|
||||
@@ -443,7 +497,9 @@ 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"`**: Returns the user's session if it exists, otherwise `null`.
|
||||
* - **`"session"`**:
|
||||
* - **`GET**`: Returns the user's session if it exists, otherwise `null`.
|
||||
* - **`POST**`: Updates the user's session and returns the updated session.
|
||||
* - **`"signin"`**:
|
||||
* - **`GET`**: Renders the built-in sign-in page.
|
||||
* - **`POST`**: Initiates the sign-in flow.
|
||||
|
||||
@@ -224,6 +224,13 @@ 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,10 +65,8 @@
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@panva/hkdf": "^1.0.1",
|
||||
"@simplewebauthn/browser": "^6.2.2",
|
||||
"@simplewebauthn/server": "^6.2.2",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@panva/hkdf": "^1.0.2",
|
||||
"cookie": "^0.5.0",
|
||||
"jose": "^4.11.4",
|
||||
"oauth": "^0.9.15",
|
||||
@@ -98,7 +96,6 @@
|
||||
"@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",
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
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 }
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
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,36 +625,3 @@ 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[]
|
||||
}
|
||||
|
||||
3799
pnpm-lock.yaml
generated
3799
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**/*"]
|
||||
"outputs": ["dist/**/*", "*.js", "*.d.ts", "*.d.ts.map"]
|
||||
},
|
||||
"next-auth#build": {
|
||||
"dependsOn": ["^build"],
|
||||
@@ -53,6 +53,7 @@
|
||||
"docs#dev": {
|
||||
"dependsOn": [
|
||||
"@auth/core#build",
|
||||
"@auth/prisma-adapter#build",
|
||||
"@auth/sveltekit#build",
|
||||
"@next-auth/dgraph-adapter#build",
|
||||
"@next-auth/dynamodb-adapter#build",
|
||||
@@ -62,7 +63,6 @@
|
||||
"@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",
|
||||
"@next-auth/typeorm-legacy-adapter#build",
|
||||
@@ -76,6 +76,7 @@
|
||||
"docs#build": {
|
||||
"dependsOn": [
|
||||
"@auth/core#build",
|
||||
"@auth/prisma-adapter#build",
|
||||
"@auth/sveltekit#build",
|
||||
"@next-auth/dgraph-adapter#build",
|
||||
"@next-auth/dynamodb-adapter#build",
|
||||
@@ -85,7 +86,6 @@
|
||||
"@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",
|
||||
"@next-auth/typeorm-legacy-adapter#build",
|
||||
|
||||
Reference in New Issue
Block a user