From fd4af6512ed0a67a33a0e7d6dcd119a8e6e12e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sat, 17 Dec 2022 20:42:10 +0100 Subject: [PATCH] chore: remove new stuff from v4 branch --- .github/sync.yml | 8 +- .github/workflows/sync-examples.yml | 2 +- apps/dev/package.json | 1 - apps/dev/pages/api/auth/[...nextauth].ts | 94 +- apps/playground-sveltekit/.env.example | 4 - apps/playground-sveltekit/.eslintignore | 13 - apps/playground-sveltekit/.eslintrc.cjs | 20 - apps/playground-sveltekit/.gitignore | 12 - apps/playground-sveltekit/.prettierignore | 13 - apps/playground-sveltekit/.prettierrc | 9 - apps/playground-sveltekit/README.md | 76 - apps/playground-sveltekit/package.json | 37 - apps/playground-sveltekit/src/app.d.ts | 32 - apps/playground-sveltekit/src/app.html | 15 - apps/playground-sveltekit/src/hooks.server.ts | 25 - .../src/lib/SignInButton.svelte | 12 - .../src/routes/+layout.server.ts | 14 - .../src/routes/+layout.svelte | 144 -- .../src/routes/+page.svelte | 25 - .../src/routes/protected/+page.svelte | 10 - .../src/routes/protected/+page.ts | 10 - apps/playground-sveltekit/static/favicon.ico | Bin 1571 -> 0 bytes apps/playground-sveltekit/svelte.config.js | 15 - apps/playground-sveltekit/tsconfig.json | 17 - apps/playground-sveltekit/vite.config.js | 8 - packages/core/README.md | 3 - packages/core/package.json | 75 - packages/core/scripts/generate-css.js | 22 - packages/core/src/adapters.ts | 130 -- packages/core/src/index.ts | 287 --- packages/core/src/jwt/index.ts | 127 -- packages/core/src/jwt/types.ts | 54 - packages/core/src/lib/assert.ts | 157 -- packages/core/src/lib/callback-handler.ts | 228 --- packages/core/src/lib/callback-url.ts | 42 - packages/core/src/lib/cookie.ts | 236 --- packages/core/src/lib/csrf-token.ts | 54 - packages/core/src/lib/default-callbacks.ts | 18 - .../core/src/lib/email/getUserFromEmail.ts | 20 - packages/core/src/lib/email/signin.ts | 49 - packages/core/src/lib/errors.ts | 141 -- packages/core/src/lib/init.ts | 152 -- .../core/src/lib/oauth/authorization-url.ts | 147 -- packages/core/src/lib/oauth/callback.ts | 221 --- packages/core/src/lib/oauth/nonce-handler.ts | 76 - packages/core/src/lib/oauth/pkce-handler.ts | 87 - packages/core/src/lib/oauth/state-handler.ts | 63 - packages/core/src/lib/pages/error.tsx | 113 -- packages/core/src/lib/pages/index.ts | 87 - packages/core/src/lib/pages/signin.tsx | 184 -- packages/core/src/lib/pages/signout.tsx | 36 - .../core/src/lib/pages/verify-request.tsx | 36 - packages/core/src/lib/providers.ts | 101 -- packages/core/src/lib/routes/callback.ts | 420 ----- packages/core/src/lib/routes/index.ts | 5 - packages/core/src/lib/routes/providers.ts | 29 - packages/core/src/lib/routes/session.ts | 165 -- packages/core/src/lib/routes/signin.ts | 103 -- packages/core/src/lib/routes/signout.ts | 44 - packages/core/src/lib/styles/index.css | 290 --- packages/core/src/lib/styles/index.ts | 2 - packages/core/src/lib/types.ts | 578 ------ packages/core/src/lib/utils/date.ts | 8 - packages/core/src/lib/utils/logger.ts | 112 -- packages/core/src/lib/utils/merge.ts | 25 - packages/core/src/lib/utils/parse-url.ts | 36 - packages/core/src/lib/web.ts | 107 -- packages/core/src/providers/42-school.ts | 178 -- packages/core/src/providers/apple.ts | 128 -- packages/core/src/providers/atlassian.ts | 44 - packages/core/src/providers/auth0.ts | 27 - packages/core/src/providers/authentik.ts | 33 - packages/core/src/providers/azure-ad-b2c.ts | 50 - packages/core/src/providers/azure-ad.ts | 64 - packages/core/src/providers/battlenet.ts | 38 - packages/core/src/providers/box.js | 28 - packages/core/src/providers/boxyhq-saml.ts | 33 - packages/core/src/providers/bungie.js | 25 - packages/core/src/providers/cognito.ts | 27 - packages/core/src/providers/coinbase.js | 21 - packages/core/src/providers/credentials.ts | 44 - packages/core/src/providers/discord.ts | 57 - packages/core/src/providers/dropbox.js | 50 - .../src/providers/duende-identity-server6.ts | 19 - packages/core/src/providers/email.ts | 166 -- packages/core/src/providers/eveonline.ts | 33 - packages/core/src/providers/facebook.ts | 51 - packages/core/src/providers/faceit.js | 25 - packages/core/src/providers/foursquare.js | 38 - packages/core/src/providers/freshbooks.js | 28 - packages/core/src/providers/fusionauth.ts | 50 - packages/core/src/providers/github.ts | 112 -- packages/core/src/providers/gitlab.ts | 76 - packages/core/src/providers/google.ts | 39 - packages/core/src/providers/hubspot.ts | 58 - .../core/src/providers/identity-server4.js | 9 - packages/core/src/providers/index.ts | 39 - packages/core/src/providers/instagram.js | 59 - packages/core/src/providers/kakao.ts | 92 - packages/core/src/providers/keycloak.ts | 44 - packages/core/src/providers/line.ts | 36 - packages/core/src/providers/linkedin.ts | 68 - packages/core/src/providers/mailchimp.js | 28 - packages/core/src/providers/mailru.js | 12 - packages/core/src/providers/medium.js | 20 - packages/core/src/providers/naver.ts | 41 - packages/core/src/providers/netlify.js | 20 - packages/core/src/providers/oauth-types.ts | 70 - packages/core/src/providers/oauth.ts | 185 -- packages/core/src/providers/okta.ts | 54 - packages/core/src/providers/onelogin.js | 11 - packages/core/src/providers/osso.js | 12 - packages/core/src/providers/osu.ts | 72 - packages/core/src/providers/patreon.ts | 41 - packages/core/src/providers/pinterest.ts | 33 - packages/core/src/providers/pipedrive.ts | 58 - packages/core/src/providers/reddit.js | 12 - packages/core/src/providers/salesforce.ts | 31 - packages/core/src/providers/slack.ts | 53 - packages/core/src/providers/spotify.ts | 42 - packages/core/src/providers/strava.ts | 42 - packages/core/src/providers/todoist.ts | 66 - packages/core/src/providers/trakt.ts | 58 - packages/core/src/providers/twitch.ts | 40 - packages/core/src/providers/twitter.ts | 189 -- packages/core/src/providers/united-effects.ts | 20 - packages/core/src/providers/vk.ts | 318 ---- packages/core/src/providers/wikimedia.ts | 193 -- packages/core/src/providers/wordpress.js | 21 - packages/core/src/providers/workos.ts | 55 - packages/core/src/providers/yandex.js | 23 - packages/core/src/providers/zitadel.ts | 36 - packages/core/src/providers/zoho.js | 21 - packages/core/src/providers/zoom.ts | 52 - packages/core/tsconfig.dev.json | 18 - packages/core/tsconfig.eslint.json | 7 - packages/core/tsconfig.json | 35 - packages/frameworks-sveltekit/.eslintrc.cjs | 24 - packages/frameworks-sveltekit/.prettierignore | 13 - packages/frameworks-sveltekit/README.md | 3 - packages/frameworks-sveltekit/package.json | 64 - .../frameworks-sveltekit/playwright.config.ts | 11 - .../frameworks-sveltekit/scripts/postbuild.js | 13 - packages/frameworks-sveltekit/src/app.d.ts | 20 - .../frameworks-sveltekit/src/lib/client.ts | 106 -- .../frameworks-sveltekit/src/lib/index.ts | 71 - .../frameworks-sveltekit/svelte.config.js | 15 - packages/frameworks-sveltekit/tests/test.ts | 6 - packages/frameworks-sveltekit/tsconfig.json | 19 - packages/frameworks-sveltekit/vite.config.js | 11 - pnpm-lock.yaml | 1603 ----------------- pnpm-workspace.yaml | 2 - 152 files changed, 38 insertions(+), 11307 deletions(-) delete mode 100644 apps/playground-sveltekit/.env.example delete mode 100644 apps/playground-sveltekit/.eslintignore delete mode 100644 apps/playground-sveltekit/.eslintrc.cjs delete mode 100644 apps/playground-sveltekit/.gitignore delete mode 100644 apps/playground-sveltekit/.prettierignore delete mode 100644 apps/playground-sveltekit/.prettierrc delete mode 100644 apps/playground-sveltekit/README.md delete mode 100644 apps/playground-sveltekit/package.json delete mode 100644 apps/playground-sveltekit/src/app.d.ts delete mode 100644 apps/playground-sveltekit/src/app.html delete mode 100644 apps/playground-sveltekit/src/hooks.server.ts delete mode 100644 apps/playground-sveltekit/src/lib/SignInButton.svelte delete mode 100644 apps/playground-sveltekit/src/routes/+layout.server.ts delete mode 100644 apps/playground-sveltekit/src/routes/+layout.svelte delete mode 100644 apps/playground-sveltekit/src/routes/+page.svelte delete mode 100644 apps/playground-sveltekit/src/routes/protected/+page.svelte delete mode 100644 apps/playground-sveltekit/src/routes/protected/+page.ts delete mode 100644 apps/playground-sveltekit/static/favicon.ico delete mode 100644 apps/playground-sveltekit/svelte.config.js delete mode 100644 apps/playground-sveltekit/tsconfig.json delete mode 100644 apps/playground-sveltekit/vite.config.js delete mode 100644 packages/core/README.md delete mode 100644 packages/core/package.json delete mode 100644 packages/core/scripts/generate-css.js delete mode 100644 packages/core/src/adapters.ts delete mode 100644 packages/core/src/index.ts delete mode 100644 packages/core/src/jwt/index.ts delete mode 100644 packages/core/src/jwt/types.ts delete mode 100644 packages/core/src/lib/assert.ts delete mode 100644 packages/core/src/lib/callback-handler.ts delete mode 100644 packages/core/src/lib/callback-url.ts delete mode 100644 packages/core/src/lib/cookie.ts delete mode 100644 packages/core/src/lib/csrf-token.ts delete mode 100644 packages/core/src/lib/default-callbacks.ts delete mode 100644 packages/core/src/lib/email/getUserFromEmail.ts delete mode 100644 packages/core/src/lib/email/signin.ts delete mode 100644 packages/core/src/lib/errors.ts delete mode 100644 packages/core/src/lib/init.ts delete mode 100644 packages/core/src/lib/oauth/authorization-url.ts delete mode 100644 packages/core/src/lib/oauth/callback.ts delete mode 100644 packages/core/src/lib/oauth/nonce-handler.ts delete mode 100644 packages/core/src/lib/oauth/pkce-handler.ts delete mode 100644 packages/core/src/lib/oauth/state-handler.ts delete mode 100644 packages/core/src/lib/pages/error.tsx delete mode 100644 packages/core/src/lib/pages/index.ts delete mode 100644 packages/core/src/lib/pages/signin.tsx delete mode 100644 packages/core/src/lib/pages/signout.tsx delete mode 100644 packages/core/src/lib/pages/verify-request.tsx delete mode 100644 packages/core/src/lib/providers.ts delete mode 100644 packages/core/src/lib/routes/callback.ts delete mode 100644 packages/core/src/lib/routes/index.ts delete mode 100644 packages/core/src/lib/routes/providers.ts delete mode 100644 packages/core/src/lib/routes/session.ts delete mode 100644 packages/core/src/lib/routes/signin.ts delete mode 100644 packages/core/src/lib/routes/signout.ts delete mode 100644 packages/core/src/lib/styles/index.css delete mode 100644 packages/core/src/lib/styles/index.ts delete mode 100644 packages/core/src/lib/types.ts delete mode 100644 packages/core/src/lib/utils/date.ts delete mode 100644 packages/core/src/lib/utils/logger.ts delete mode 100644 packages/core/src/lib/utils/merge.ts delete mode 100644 packages/core/src/lib/utils/parse-url.ts delete mode 100644 packages/core/src/lib/web.ts delete mode 100644 packages/core/src/providers/42-school.ts delete mode 100644 packages/core/src/providers/apple.ts delete mode 100644 packages/core/src/providers/atlassian.ts delete mode 100644 packages/core/src/providers/auth0.ts delete mode 100644 packages/core/src/providers/authentik.ts delete mode 100644 packages/core/src/providers/azure-ad-b2c.ts delete mode 100644 packages/core/src/providers/azure-ad.ts delete mode 100644 packages/core/src/providers/battlenet.ts delete mode 100644 packages/core/src/providers/box.js delete mode 100644 packages/core/src/providers/boxyhq-saml.ts delete mode 100644 packages/core/src/providers/bungie.js delete mode 100644 packages/core/src/providers/cognito.ts delete mode 100644 packages/core/src/providers/coinbase.js delete mode 100644 packages/core/src/providers/credentials.ts delete mode 100644 packages/core/src/providers/discord.ts delete mode 100644 packages/core/src/providers/dropbox.js delete mode 100644 packages/core/src/providers/duende-identity-server6.ts delete mode 100644 packages/core/src/providers/email.ts delete mode 100644 packages/core/src/providers/eveonline.ts delete mode 100644 packages/core/src/providers/facebook.ts delete mode 100644 packages/core/src/providers/faceit.js delete mode 100644 packages/core/src/providers/foursquare.js delete mode 100644 packages/core/src/providers/freshbooks.js delete mode 100644 packages/core/src/providers/fusionauth.ts delete mode 100644 packages/core/src/providers/github.ts delete mode 100644 packages/core/src/providers/gitlab.ts delete mode 100644 packages/core/src/providers/google.ts delete mode 100644 packages/core/src/providers/hubspot.ts delete mode 100644 packages/core/src/providers/identity-server4.js delete mode 100644 packages/core/src/providers/index.ts delete mode 100644 packages/core/src/providers/instagram.js delete mode 100644 packages/core/src/providers/kakao.ts delete mode 100644 packages/core/src/providers/keycloak.ts delete mode 100644 packages/core/src/providers/line.ts delete mode 100644 packages/core/src/providers/linkedin.ts delete mode 100644 packages/core/src/providers/mailchimp.js delete mode 100644 packages/core/src/providers/mailru.js delete mode 100644 packages/core/src/providers/medium.js delete mode 100644 packages/core/src/providers/naver.ts delete mode 100644 packages/core/src/providers/netlify.js delete mode 100644 packages/core/src/providers/oauth-types.ts delete mode 100644 packages/core/src/providers/oauth.ts delete mode 100644 packages/core/src/providers/okta.ts delete mode 100644 packages/core/src/providers/onelogin.js delete mode 100644 packages/core/src/providers/osso.js delete mode 100644 packages/core/src/providers/osu.ts delete mode 100644 packages/core/src/providers/patreon.ts delete mode 100644 packages/core/src/providers/pinterest.ts delete mode 100644 packages/core/src/providers/pipedrive.ts delete mode 100644 packages/core/src/providers/reddit.js delete mode 100644 packages/core/src/providers/salesforce.ts delete mode 100644 packages/core/src/providers/slack.ts delete mode 100644 packages/core/src/providers/spotify.ts delete mode 100644 packages/core/src/providers/strava.ts delete mode 100644 packages/core/src/providers/todoist.ts delete mode 100644 packages/core/src/providers/trakt.ts delete mode 100644 packages/core/src/providers/twitch.ts delete mode 100644 packages/core/src/providers/twitter.ts delete mode 100644 packages/core/src/providers/united-effects.ts delete mode 100644 packages/core/src/providers/vk.ts delete mode 100644 packages/core/src/providers/wikimedia.ts delete mode 100644 packages/core/src/providers/wordpress.js delete mode 100644 packages/core/src/providers/workos.ts delete mode 100644 packages/core/src/providers/yandex.js delete mode 100644 packages/core/src/providers/zitadel.ts delete mode 100644 packages/core/src/providers/zoho.js delete mode 100644 packages/core/src/providers/zoom.ts delete mode 100644 packages/core/tsconfig.dev.json delete mode 100644 packages/core/tsconfig.eslint.json delete mode 100644 packages/core/tsconfig.json delete mode 100644 packages/frameworks-sveltekit/.eslintrc.cjs delete mode 100644 packages/frameworks-sveltekit/.prettierignore delete mode 100644 packages/frameworks-sveltekit/README.md delete mode 100644 packages/frameworks-sveltekit/package.json delete mode 100644 packages/frameworks-sveltekit/playwright.config.ts delete mode 100644 packages/frameworks-sveltekit/scripts/postbuild.js delete mode 100644 packages/frameworks-sveltekit/src/app.d.ts delete mode 100644 packages/frameworks-sveltekit/src/lib/client.ts delete mode 100644 packages/frameworks-sveltekit/src/lib/index.ts delete mode 100644 packages/frameworks-sveltekit/svelte.config.js delete mode 100644 packages/frameworks-sveltekit/tests/test.ts delete mode 100644 packages/frameworks-sveltekit/tsconfig.json delete mode 100644 packages/frameworks-sveltekit/vite.config.js diff --git a/.github/sync.yml b/.github/sync.yml index bdac9a1d..0826e8f0 100644 --- a/.github/sync.yml +++ b/.github/sync.yml @@ -1,13 +1,7 @@ +# This is a legacy example pushed from the v4 branch nextauthjs/next-auth-example: - source: apps/example-nextjs dest: . deleteOrphaned: true - .github/FUNDING.yml - LICENSE - -nextauthjs/next-auth-gatsby-example: - - source: apps/example-gatsby - dest: . - deleteOrphaned: true - - .github/FUNDING.yml - - LICENSE diff --git a/.github/workflows/sync-examples.yml b/.github/workflows/sync-examples.yml index 1960df5e..7d3cd63f 100644 --- a/.github/workflows/sync-examples.yml +++ b/.github/workflows/sync-examples.yml @@ -2,7 +2,7 @@ name: Sync Example Repositories on: push: branches: - - main + - v4 workflow_dispatch: jobs: sync: diff --git a/apps/dev/package.json b/apps/dev/package.json index 24b51e7f..5b5cf51e 100644 --- a/apps/dev/package.json +++ b/apps/dev/package.json @@ -23,7 +23,6 @@ "faunadb": "^4", "next": "13.0.6", "next-auth": "workspace:*", - "@auth/core": "workspace:*", "nodemailer": "^6", "react": "^18", "react-dom": "^18" diff --git a/apps/dev/pages/api/auth/[...nextauth].ts b/apps/dev/pages/api/auth/[...nextauth].ts index ccdcc23e..7654451a 100644 --- a/apps/dev/pages/api/auth/[...nextauth].ts +++ b/apps/dev/pages/api/auth/[...nextauth].ts @@ -1,39 +1,39 @@ -import { AuthHandler, type AuthOptions } from "@auth/core" +import NextAuth, { NextAuthOptions } from "next-auth" // Providers -import Apple from "@auth/core/providers/apple" -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 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 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 IDS4 from "@auth/core/providers/identity-server4" -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 Okta from "@auth/core/providers/okta" -import Osu from "@auth/core/providers/osu" -import Patreon from "@auth/core/providers/patreon" -import Slack from "@auth/core/providers/slack" -import Spotify from "@auth/core/providers/spotify" -import Trakt from "@auth/core/providers/trakt" -import Twitch from "@auth/core/providers/twitch" -import Twitter from "@auth/core/providers/twitter" -import Vk from "@auth/core/providers/vk" -import Wikimedia from "@auth/core/providers/wikimedia" -import WorkOS from "@auth/core/providers/workos" +import Apple from "next-auth/providers/apple" +import Auth0 from "next-auth/providers/auth0" +import AzureAD from "next-auth/providers/azure-ad" +import AzureB2C from "next-auth/providers/azure-ad-b2c" +import BoxyHQSAML from "next-auth/providers/boxyhq-saml" +// import Cognito from "next-auth/providers/cognito" +import Credentials from "next-auth/providers/credentials" +import Discord from "next-auth/providers/discord" +import DuendeIDS6 from "next-auth/providers/duende-identity-server6" +// import Email from "next-auth/providers/email" +import Facebook from "next-auth/providers/facebook" +import Foursquare from "next-auth/providers/foursquare" +import Freshbooks from "next-auth/providers/freshbooks" +import GitHub from "next-auth/providers/github" +import Gitlab from "next-auth/providers/gitlab" +import Google from "next-auth/providers/google" +// import IDS4 from "next-auth/providers/identity-server4" +import Instagram from "next-auth/providers/instagram" +// import Keycloak from "next-auth/providers/keycloak" +import Line from "next-auth/providers/line" +import LinkedIn from "next-auth/providers/linkedin" +import Mailchimp from "next-auth/providers/mailchimp" +// import Okta from "next-auth/providers/okta" +import Osu from "next-auth/providers/osu" +import Patreon from "next-auth/providers/patreon" +import Slack from "next-auth/providers/slack" +import Spotify from "next-auth/providers/spotify" +import Trakt from "next-auth/providers/trakt" +import Twitch from "next-auth/providers/twitch" +import Twitter from "next-auth/providers/twitter" +import Vk from "next-auth/providers/vk" +import Wikimedia from "next-auth/providers/wikimedia" +import WorkOS from "next-auth/providers/workos" // // Prisma // import { PrismaClient } from "@prisma/client" @@ -66,7 +66,7 @@ import WorkOS from "@auth/core/providers/workos" // secret: process.env.SUPABASE_SERVICE_ROLE_KEY, // }) -export const authOptions: AuthOptions = { +export const authOptions: NextAuthOptions = { // adapter, // debug: process.env.NODE_ENV !== "production", theme: { @@ -129,26 +129,4 @@ if (authOptions.adapter) { // ) } -// TODO: move to next-auth/edge -function Auth(...args: any[]) { - const envSecret = process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET - const envTrustHost = !!(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL ?? process.env.NODE_ENV !== "production") - if (args.length === 1) { - return async (req: Request) => { - args[0].secret ??= envSecret - args[0].trustHost ??= envTrustHost - return await AuthHandler(req, args[0]) - } - } - args[1].secret ??= envSecret - args[1].trustHost ??= envTrustHost - return AuthHandler(args[0], args[1]) -} - -// export default Auth(authOptions) - -export default function handle(request: Request) { - return Auth(request, authOptions) -} - -export const config = { runtime: "experimental-edge" } +export default NextAuth(authOptions) diff --git a/apps/playground-sveltekit/.env.example b/apps/playground-sveltekit/.env.example deleted file mode 100644 index 6597ada2..00000000 --- a/apps/playground-sveltekit/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -GITHUB_CLIENT_ID= -GITHUB_CLIENT_SECRET= -NEXTAUTH_SECRET= -PUBLIC_NEXTAUTH_URL=http://localhost:5173 \ No newline at end of file diff --git a/apps/playground-sveltekit/.eslintignore b/apps/playground-sveltekit/.eslintignore deleted file mode 100644 index 38972655..00000000 --- a/apps/playground-sveltekit/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/apps/playground-sveltekit/.eslintrc.cjs b/apps/playground-sveltekit/.eslintrc.cjs deleted file mode 100644 index 3ccf435f..00000000 --- a/apps/playground-sveltekit/.eslintrc.cjs +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], - plugins: ['svelte3', '@typescript-eslint'], - ignorePatterns: ['*.cjs'], - overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], - settings: { - 'svelte3/typescript': () => require('typescript') - }, - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020 - }, - env: { - browser: true, - es2017: true, - node: true - } -}; diff --git a/apps/playground-sveltekit/.gitignore b/apps/playground-sveltekit/.gitignore deleted file mode 100644 index 8f6c617e..00000000 --- a/apps/playground-sveltekit/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example -.vercel -.output -vite.config.js.timestamp-* -vite.config.ts.timestamp-* diff --git a/apps/playground-sveltekit/.prettierignore b/apps/playground-sveltekit/.prettierignore deleted file mode 100644 index 38972655..00000000 --- a/apps/playground-sveltekit/.prettierignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/apps/playground-sveltekit/.prettierrc b/apps/playground-sveltekit/.prettierrc deleted file mode 100644 index a77fddea..00000000 --- a/apps/playground-sveltekit/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte"], - "pluginSearchDirs": ["."], - "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] -} diff --git a/apps/playground-sveltekit/README.md b/apps/playground-sveltekit/README.md deleted file mode 100644 index 8de73df7..00000000 --- a/apps/playground-sveltekit/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# SvelteKit + NextAuth.js Playground - -NextAuth.js is committed to bringing easy authentication to other frameworks. https://github.com/nextauthjs/next-auth/issues/2294 - -SvelteKit support with NextAuth.js is currently experimental. This directory contains a minimal, proof-of-concept application. Parts of this is expected to be abstracted away into a package like `@next-auth/sveltekit` - -## Running this Demo - -- Copy `.env.example` to `.env` -- In `.env`, set `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` - - See [https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app)) - - When creating the OAuth app, set "Homepage URL" to `http://localhost:5173` and Authorization callack URL to `http://localhost:5173/api/auth/callback/github` -- In `.env`, set `NEXTAUTH_SECRET` to any random string -- Build and run the application: `yarn build && yarn start` - -## Existing Project - -### Add API Route - -To add NextAuth.js to a project create a file called `[...nextauth]/+server.js` in routes/api/auth. This contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations. - -```ts -import { NextAuth, options } from "$lib/next-auth" - -export const { GET, POST } = NextAuth(options) -``` - -### Add [hook](https://kit.svelte.dev/docs/hooks) - -```ts -import type { Handle } from "@sveltejs/kit" -import { getServerSession, options as nextAuthOptions } from "$lib/next-auth" - -export const handle: Handle = async function handle({ - event, - resolve, -}): Promise { - const session = await getServerSession(event.request, nextAuthOptions) - event.locals.session = session - - return resolve(event) -} -``` - -### Load Session from Primary Layout - -```ts -// src/lib/routes/+layout.server.ts -import type { LayoutServerLoad } from "./$types" - -export const load: LayoutServerLoad = ({ locals }) => { - return { - session: locals.session, - } -} -``` - -### Protecting a Route - -```ts -// src/lib/routes/protected/+page.ts -import { redirect } from "@sveltejs/kit" -import type { PageLoad } from "./$types" - -export const load: PageLoad = async ({ parent }) => { - const { session } = await parent() - if (!session?.user) { - throw redirect(302, "/") - } - return {} -} -``` - -## Packaging lib - -Refer to https://kit.svelte.dev/docs/packaging diff --git a/apps/playground-sveltekit/package.json b/apps/playground-sveltekit/package.json deleted file mode 100644 index f4101223..00000000 --- a/apps/playground-sveltekit/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "sveltekit-nextauth", - "private": true, - "version": "0.0.1", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" - }, - "devDependencies": { - "@fontsource/fira-mono": "^4.5.10", - "@neoconfetti/svelte": "^1.0.0", - "@sveltejs/adapter-auto": "next", - "@sveltejs/kit": "next", - "@types/cookie": "^0.5.1", - "@typescript-eslint/eslint-plugin": "^5.45.0", - "@typescript-eslint/parser": "^5.45.0", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-svelte3": "^4.0.0", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.8.1", - "svelte": "^3.54.0", - "svelte-check": "^2.9.2", - "tslib": "^2.4.1", - "typescript": "^4.9.3", - "vite": "^4.0.0" - }, - "dependencies": { - "cookie": "0.5.0", - "@auth/core": "workspace:*", - "@auth/sveltekit": "workspace:^" - }, - "type": "module" -} diff --git a/apps/playground-sveltekit/src/app.d.ts b/apps/playground-sveltekit/src/app.d.ts deleted file mode 100644 index 3a5ba126..00000000 --- a/apps/playground-sveltekit/src/app.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -/// -/// -import type { - User as NextAuthUser, - Session as NextAuthSession, -} from "next-auth" - -// optionally extend the `user` -interface User extends NextAuthUser { - // add custom fields here -} - -interface AppSession extends NextAuthSession { - user: User -} - -// See https://kit.svelte.dev/docs/typescript -// for information about these interfaces -declare global { - declare namespace App { - interface Locals { - // session: AppSession - getSession: () => Promise - } - - interface Platform {} - - interface Session extends AppSession {} - - interface Stuff {} - } -} diff --git a/apps/playground-sveltekit/src/app.html b/apps/playground-sveltekit/src/app.html deleted file mode 100644 index b41e51ce..00000000 --- a/apps/playground-sveltekit/src/app.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - %sveltekit.head% - - - -
%sveltekit.body%
- - - \ No newline at end of file diff --git a/apps/playground-sveltekit/src/hooks.server.ts b/apps/playground-sveltekit/src/hooks.server.ts deleted file mode 100644 index de22c97e..00000000 --- a/apps/playground-sveltekit/src/hooks.server.ts +++ /dev/null @@ -1,25 +0,0 @@ -import SvelteKitAuth from "@auth/sveltekit" -import GitHub from '@auth/core/providers/github'; -import Google from '@auth/core/providers/google'; -import Credentials from '@auth/core/providers/credentials'; -import { - GITHUB_CLIENT_ID, - GITHUB_CLIENT_SECRET, - GOOGLE_CLIENT_ID, - GOOGLE_CLIENT_SECRET, -} from "$env/static/private" - -export const handle = SvelteKitAuth({ - providers: [ - GitHub({ clientId: GITHUB_CLIENT_ID, clientSecret: GITHUB_CLIENT_SECRET }), - Google({ clientId: GOOGLE_CLIENT_ID, clientSecret: GOOGLE_CLIENT_SECRET }), - 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: "" } - }, - }), - ], - debug: true, -}); diff --git a/apps/playground-sveltekit/src/lib/SignInButton.svelte b/apps/playground-sveltekit/src/lib/SignInButton.svelte deleted file mode 100644 index d5013c48..00000000 --- a/apps/playground-sveltekit/src/lib/SignInButton.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - -
- {#if provider.callbackUrl} - - {/if} - -
diff --git a/apps/playground-sveltekit/src/routes/+layout.server.ts b/apps/playground-sveltekit/src/routes/+layout.server.ts deleted file mode 100644 index 63491256..00000000 --- a/apps/playground-sveltekit/src/routes/+layout.server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { LayoutServerLoad } from "./$types" - -export const load: LayoutServerLoad = (event) => { - console.log('layout server load', event.locals.getSession) - let session - if (event.locals.getSession) - { - session = event.locals.getSession() - - } - return { - session, - } -} diff --git a/apps/playground-sveltekit/src/routes/+layout.svelte b/apps/playground-sveltekit/src/routes/+layout.svelte deleted file mode 100644 index e05bf9b0..00000000 --- a/apps/playground-sveltekit/src/routes/+layout.svelte +++ /dev/null @@ -1,144 +0,0 @@ - - -
-
-
-

- {#if Object.keys($page.data.session || {}).length} - {#if $page.data.session.user.image} - - {/if} - - Signed in as
- {$page.data.session.user.email || $page.data.session.user.name} -
- Sign out - {:else} - You are not signed in - Sign in - {/if} -

-
- -
- -
- - diff --git a/apps/playground-sveltekit/src/routes/+page.svelte b/apps/playground-sveltekit/src/routes/+page.svelte deleted file mode 100644 index c3677e7b..00000000 --- a/apps/playground-sveltekit/src/routes/+page.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - -

SvelteKit + NextAuth.js Example

-

- This is an example site to demonstrate how to use SvelteKit - with NextAuth.js for authentication. - - {#if Object.keys($page.data.session || {}).length} - {#if $page.data.session.user.image} - - {/if} - - Signed in as
- {$page.data.session.user.email || $page.data.session.user.name} -
- - {:else} - You are not signed in - - - {/if} -

diff --git a/apps/playground-sveltekit/src/routes/protected/+page.svelte b/apps/playground-sveltekit/src/routes/protected/+page.svelte deleted file mode 100644 index 55397466..00000000 --- a/apps/playground-sveltekit/src/routes/protected/+page.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - -

Protected page

-

- This is a protected content. You can access this content because you are - signed in. -

-

Session expiry: {$page.data.session.expires}

diff --git a/apps/playground-sveltekit/src/routes/protected/+page.ts b/apps/playground-sveltekit/src/routes/protected/+page.ts deleted file mode 100644 index 966bf5e0..00000000 --- a/apps/playground-sveltekit/src/routes/protected/+page.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { redirect } from "@sveltejs/kit" -import type { PageLoad } from "./$types" - -export const load: PageLoad = async ({ parent }) => { - const { session } = await parent() - if (!session?.user) { - throw redirect(302, "/") - } - return {} -} diff --git a/apps/playground-sveltekit/static/favicon.ico b/apps/playground-sveltekit/static/favicon.ico deleted file mode 100644 index 825b9e65af7c104cfb07089bb28659393b4f2097..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH", - "contributors": [ - "Balázs Orbán ", - "Nico Domino ", - "Lluis Agusti ", - "Thang Huu Vu ", - "Iain Collins = DefaultAdapter & - (WithVerificationToken extends true - ? { - createVerificationToken: ( - verificationToken: VerificationToken - ) => Awaitable - /** - * Return verification token from the database - * and delete it so it cannot be used again. - */ - useVerificationToken: (params: { - identifier: string - token: string - }) => Awaitable - } - : {}) - -export interface DefaultAdapter { - createUser: (user: Omit) => Awaitable - getUser: (id: string) => Awaitable - getUserByEmail: (email: string) => Awaitable - /** Using the provider id and the id of the user for a specific account, get the user. */ - getUserByAccount: ( - providerAccountId: Pick - ) => Awaitable - updateUser: (user: Partial) => Awaitable - /** @todo Implement */ - deleteUser?: ( - userId: string - ) => Promise | Awaitable - linkAccount: ( - account: AdapterAccount - ) => Promise | Awaitable - /** @todo Implement */ - unlinkAccount?: ( - providerAccountId: Pick - ) => Promise | Awaitable - /** Creates a session for the user and returns it. */ - createSession: (session: { - sessionToken: string - userId: string - expires: Date - }) => Awaitable - getSessionAndUser: ( - sessionToken: string - ) => Awaitable<{ session: AdapterSession; user: AdapterUser } | null> - updateSession: ( - session: Partial & Pick - ) => Awaitable - /** - * Deletes a session from the database. - * It is preferred that this method also returns the session - * that is being deleted for logging purposes. - */ - deleteSession: ( - sessionToken: string - ) => Promise | Awaitable - createVerificationToken?: ( - verificationToken: VerificationToken - ) => Awaitable - /** - * Return verification token from the database - * and delete it so it cannot be used again. - */ - useVerificationToken?: (params: { - identifier: string - token: string - }) => Awaitable -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts deleted file mode 100644 index 39886a15..00000000 --- a/packages/core/src/index.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { init } from "./lib/init" -import { assertConfig } from "./lib/assert" -import { SessionStore } from "./lib/cookie" -import { toInternalRequest, toResponse } from "./lib/web" -import renderPage from "./lib/pages" -import * as routes from "./lib/routes" -import logger, { setLogger } from "./lib/utils/logger" - -import type { ErrorType } from "./lib/pages/error" -import type { - AuthOptions, - RequestInternal, - ResponseInternal, -} from "./lib/types" -import { UntrustedHost } from "./lib/errors" - -export * from "./lib/types" - -const configErrorMessage = - "There is a problem with the server configuration. Check the server logs for more information." - -async function AuthHandlerInternal< - Body extends string | Record | any[] ->(params: { - req: RequestInternal - options: AuthOptions - /** REVIEW: Is this the best way to skip parsing the body in Node.js? */ - parsedBody?: any -}): Promise> { - const { options: authOptions, req } = params - - const assertionResult = assertConfig({ options: authOptions, req }) - - if (Array.isArray(assertionResult)) { - assertionResult.forEach(logger.warn) - } else if (assertionResult instanceof Error) { - // Bail out early if there's an error in the user config - logger.error(assertionResult.code, assertionResult) - - const htmlPages = ["signin", "signout", "error", "verify-request"] - if (!htmlPages.includes(req.action) || req.method !== "GET") { - return { - status: 500, - headers: { "Content-Type": "application/json" }, - body: { message: configErrorMessage } as any, - } - } - const { pages, theme } = authOptions - - const authOnErrorPage = - pages?.error && req.query?.callbackUrl?.startsWith(pages.error) - - if (!pages?.error || authOnErrorPage) { - if (authOnErrorPage) { - logger.error( - "AUTH_ON_ERROR_PAGE_ERROR", - new Error( - `The error page ${pages?.error} should not require authentication` - ) - ) - } - const render = renderPage({ theme }) - return render.error({ error: "configuration" }) - } - - return { - redirect: `${pages.error}?error=Configuration`, - } - } - - const { action, providerId, error, method } = req - - const { options, cookies } = await init({ - authOptions, - action, - providerId, - url: req.url, - callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl, - csrfToken: req.body?.csrfToken, - cookies: req.cookies, - isPost: method === "POST", - }) - - const sessionStore = new SessionStore( - options.cookies.sessionToken, - req, - options.logger - ) - - if (method === "GET") { - const render = renderPage({ ...options, query: req.query, cookies }) - const { pages } = options - switch (action) { - case "providers": - return (await routes.providers(options.providers)) as any - case "session": { - const session = await routes.session({ options, sessionStore }) - if (session.cookies) cookies.push(...session.cookies) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - return { ...session, cookies } as any - } - case "csrf": - return { - headers: { "Content-Type": "application/json" }, - body: { csrfToken: options.csrfToken } as any, - cookies, - } - case "signin": - if (pages.signIn) { - let signinUrl = `${pages.signIn}${ - pages.signIn.includes("?") ? "&" : "?" - }callbackUrl=${encodeURIComponent(options.callbackUrl)}` - if (error) - signinUrl = `${signinUrl}&error=${encodeURIComponent(error)}` - return { redirect: signinUrl, cookies } - } - - return render.signin() - case "signout": - if (pages.signOut) return { redirect: pages.signOut, cookies } - - return render.signout() - case "callback": - if (options.provider) { - const callback = await routes.callback({ - body: req.body, - query: req.query, - headers: req.headers, - cookies: req.cookies, - method, - options, - sessionStore, - }) - if (callback.cookies) cookies.push(...callback.cookies) - return { ...callback, cookies } - } - break - case "verify-request": - if (pages.verifyRequest) { - return { redirect: pages.verifyRequest, cookies } - } - return render.verifyRequest() - case "error": - // These error messages are displayed in line on the sign in page - if ( - [ - "Signin", - "OAuthSignin", - "OAuthCallback", - "OAuthCreateAccount", - "EmailCreateAccount", - "Callback", - "OAuthAccountNotLinked", - "EmailSignin", - "CredentialsSignin", - "SessionRequired", - ].includes(error as string) - ) { - return { redirect: `${options.url}/signin?error=${error}`, cookies } - } - - if (pages.error) { - return { - redirect: `${pages.error}${ - pages.error.includes("?") ? "&" : "?" - }error=${error}`, - cookies, - } - } - - return render.error({ error: error as ErrorType }) - default: - } - } else if (method === "POST") { - switch (action) { - case "signin": - // Verified CSRF Token required for all sign in routes - if (options.csrfTokenVerified && options.provider) { - const signin = await routes.signin({ - query: req.query, - body: req.body, - options, - }) - if (signin.cookies) cookies.push(...signin.cookies) - return { ...signin, cookies } - } - - return { redirect: `${options.url}/signin?csrf=true`, cookies } - case "signout": - // Verified CSRF Token required for signout - if (options.csrfTokenVerified) { - const signout = await routes.signout({ options, sessionStore }) - if (signout.cookies) cookies.push(...signout.cookies) - return { ...signout, cookies } - } - return { redirect: `${options.url}/signout?csrf=true`, cookies } - case "callback": - if (options.provider) { - // Verified CSRF Token required for credentials providers only - if ( - options.provider.type === "credentials" && - !options.csrfTokenVerified - ) { - return { redirect: `${options.url}/signin?csrf=true`, cookies } - } - - const callback = await routes.callback({ - body: req.body, - query: req.query, - headers: req.headers, - cookies: req.cookies, - method, - options, - sessionStore, - }) - if (callback.cookies) cookies.push(...callback.cookies) - return { ...callback, cookies } - } - break - case "_log": - if (authOptions.logger) { - try { - const { code, level, ...metadata } = req.body ?? {} - logger[level](code, metadata) - } catch (error) { - // If logging itself failed... - logger.error("LOGGER_ERROR", error as Error) - } - } - return {} - default: - } - } - - return { - status: 400, - body: `Error: This action with HTTP ${method} is not supported by NextAuth.js` as any, - } -} - -/** - * The core functionality of Auth.js. - * It receives a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) - * and returns a standard [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response). - */ -export async function AuthHandler( - request: Request, - options: AuthOptions -): Promise { - setLogger(options.logger, options.debug) - - if (!options.trustHost) { - const error = new UntrustedHost( - `Host must be trusted. URL was: ${request.url}` - ) - logger.error(error.code, error) - - return new Response(JSON.stringify({ message: configErrorMessage }), { - status: 500, - headers: { "Content-Type": "application/json" }, - }) - } - - const req = await toInternalRequest(request) - if (req instanceof Error) { - logger.error((req as any).code, req) - return new Response( - `Error: This action with HTTP ${request.method} is not supported.`, - { status: 400 } - ) - } - const internalResponse = await AuthHandlerInternal({ req, options }) - - const response = await toResponse(internalResponse) - - // If the request expects a return URL, send it as JSON - // instead of doing an actual redirect. - const redirect = response.headers.get("Location") - if (request.headers.has("X-Auth-Return-Redirect") && redirect) { - response.headers.delete("Location") - response.headers.set("Content-Type", "application/json") - return new Response(JSON.stringify({ url: redirect }), { - headers: response.headers, - }) - } - return response -} diff --git a/packages/core/src/jwt/index.ts b/packages/core/src/jwt/index.ts deleted file mode 100644 index 323d8eee..00000000 --- a/packages/core/src/jwt/index.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { EncryptJWT, jwtDecrypt } from "jose" -import hkdf from "@panva/hkdf" -import { SessionStore } from "../lib/cookie" -import type { JWT, JWTDecodeParams, JWTEncodeParams, JWTOptions } from "./types" -import type { LoggerInstance } from ".." - -export * from "./types" - -const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days - -const now = () => (Date.now() / 1000) | 0 - -/** Issues a JWT. By default, the JWT is encrypted using "A256GCM". */ -export async function encode(params: JWTEncodeParams) { - const { token = {}, secret, maxAge = DEFAULT_MAX_AGE } = params - const encryptionSecret = await getDerivedEncryptionKey(secret) - return await new EncryptJWT(token) - .setProtectedHeader({ alg: "dir", enc: "A256GCM" }) - .setIssuedAt() - .setExpirationTime(now() + maxAge) - .setJti(crypto.randomUUID()) - .encrypt(encryptionSecret) -} - -/** Decodes a NextAuth.js issued JWT. */ -export async function decode(params: JWTDecodeParams): Promise { - const { token, secret } = params - if (!token) return null - const encryptionSecret = await getDerivedEncryptionKey(secret) - const { payload } = await jwtDecrypt(token, encryptionSecret, { - clockTolerance: 15, - }) - return payload -} - -export interface GetTokenParams { - /** The request containing the JWT either in the cookies or in the `Authorization` header. */ - req: - | Request - | { cookies: Record; headers: Record } - /** - * Use secure prefix for cookie name, unless URL in `NEXTAUTH_URL` is http:// - * or not set (e.g. development or test instance) case use unprefixed name - */ - secureCookie?: boolean - /** If the JWT is in the cookie, what name `getToken()` should look for. */ - cookieName?: string - /** - * `getToken()` will return the raw JWT if this is set to `true` - * @default false - */ - raw?: R - /** - * The same `secret` used in the `NextAuth` configuration. - * Defaults to the `NEXTAUTH_SECRET` environment variable. - */ - secret?: string - decode?: JWTOptions["decode"] - logger?: LoggerInstance | Console -} - -/** - * Takes a NextAuth.js request (`req`) and returns either the NextAuth.js issued JWT's payload, - * or the raw JWT string. We look for the JWT in the either the cookies, or the `Authorization` header. - * [Documentation](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken) - */ -export async function getToken( - params: GetTokenParams -): Promise { - const { - req, - secureCookie = process.env.NEXTAUTH_URL?.startsWith("https://") ?? - !!process.env.VERCEL, - cookieName = secureCookie - ? "__Secure-next-auth.session-token" - : "next-auth.session-token", - raw, - decode: _decode = decode, - logger = console, - secret = process.env.NEXTAUTH_SECRET, - } = params - - if (!req) throw new Error("Must pass `req` to JWT getToken()") - - const sessionStore = new SessionStore( - { name: cookieName, options: { secure: secureCookie } }, - // @ts-expect-error - { cookies: req.cookies, headers: req.headers }, - logger - ) - - let token = sessionStore.value - - const authorizationHeader = - req.headers instanceof Headers - ? req.headers.get("authorization") - : req.headers.authorization - - if (!token && authorizationHeader?.split(" ")[0] === "Bearer") { - const urlEncodedToken = authorizationHeader.split(" ")[1] - token = decodeURIComponent(urlEncodedToken) - } - - // @ts-expect-error - if (!token) return null - - // @ts-expect-error - if (raw) return token - - try { - // @ts-expect-error - return await _decode({ token, secret }) - } catch { - // @ts-expect-error - return null - } -} - -async function getDerivedEncryptionKey(secret: string | Buffer) { - return await hkdf( - "sha256", - secret, - "", - "NextAuth.js Generated Encryption Key", - 32 - ) -} diff --git a/packages/core/src/jwt/types.ts b/packages/core/src/jwt/types.ts deleted file mode 100644 index ecd0e2a9..00000000 --- a/packages/core/src/jwt/types.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Awaitable } from ".." - -export interface DefaultJWT extends Record { - name?: string | null - email?: string | null - picture?: string | null - sub?: string -} - -/** - * Returned by the `jwt` callback and `getToken`, when using JWT sessions - * - * [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | [`getToken`](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken) - */ -export interface JWT extends Record, DefaultJWT {} - -export interface JWTEncodeParams { - /** The JWT payload. */ - token?: JWT - /** The secret used to encode the NextAuth.js issued JWT. */ - secret: string | Buffer - /** - * The maximum age of the NextAuth.js issued JWT in seconds. - * @default 30 * 24 * 30 * 60 // 30 days - */ - maxAge?: number -} - -export interface JWTDecodeParams { - /** The NextAuth.js issued JWT to be decoded */ - token?: string - /** The secret used to decode the NextAuth.js issued JWT. */ - secret: string | Buffer -} - -export interface JWTOptions { - /** - * The secret used to encode/decode the NextAuth.js issued JWT. - * @deprecated Set the `NEXTAUTH_SECRET` environment vairable or - * use the top-level `secret` option instead - */ - secret: string - /** - * The maximum age of the NextAuth.js issued JWT in seconds. - * @default 30 * 24 * 30 * 60 // 30 days - */ - maxAge: number - /** Override this method to control the NextAuth.js issued JWT encoding. */ - encode: (params: JWTEncodeParams) => Awaitable - /** Override this method to control the NextAuth.js issued JWT decoding. */ - decode: (params: JWTDecodeParams) => Awaitable -} - -export type Secret = string | Buffer diff --git a/packages/core/src/lib/assert.ts b/packages/core/src/lib/assert.ts deleted file mode 100644 index 16c75eb6..00000000 --- a/packages/core/src/lib/assert.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { - InvalidCallbackUrl, - InvalidEndpoints, - MissingAdapter, - MissingAdapterMethods, - MissingAPIRoute, - MissingAuthorize, - MissingSecret, - UnsupportedStrategy, -} from "./errors" -import { defaultCookies } from "./cookie" - -import type { AuthOptions, RequestInternal } from ".." -import type { WarningCode } from "./utils/logger" - -type ConfigError = - | MissingAdapter - | MissingAdapterMethods - | MissingAPIRoute - | MissingAuthorize - | MissingSecret - | InvalidCallbackUrl - | UnsupportedStrategy - | InvalidEndpoints - | UnsupportedStrategy - -let warned = false - -function isValidHttpUrl(url: string, baseUrl: string) { - try { - return /^https?:/.test( - new URL(url, url.startsWith("/") ? baseUrl : undefined).protocol - ) - } catch { - return false - } -} - -/** - * Verify that the user configured Auth.js correctly. - * Good place to mention deprecations as well. - * - * REVIEW: Make some of these and corresponding docs less Next.js specific? - */ -export function assertConfig(params: { - options: AuthOptions - req: RequestInternal -}): ConfigError | WarningCode[] { - const { options, req } = params - const { url } = req - const warnings: WarningCode[] = [] - - if (!warned) { - if (!url.origin) warnings.push("NEXTAUTH_URL") - if (options.debug) warnings.push("DEBUG_ENABLED") - } - - if (!options.secret) { - return new MissingSecret("Please define a `secret` in production.") - } - - // req.query isn't defined when asserting `unstable_getServerSession` for example - if (!req.query?.nextauth && !req.action) { - return new MissingAPIRoute( - "Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly." - ) - } - - const callbackUrlParam = req.query?.callbackUrl as string | undefined - - if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, url.origin)) { - return new InvalidCallbackUrl( - `Invalid callback URL. Received: ${callbackUrlParam}` - ) - } - - const { callbackUrl: defaultCallbackUrl } = defaultCookies( - options.useSecureCookies ?? url.protocol === "https://" - ) - const callbackUrlCookie = - req.cookies?.[options.cookies?.callbackUrl?.name ?? defaultCallbackUrl.name] - - if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, url.origin)) { - return new InvalidCallbackUrl( - `Invalid callback URL. Received: ${callbackUrlCookie}` - ) - } - - let hasCredentials, hasEmail - - for (const provider of options.providers) { - if ( - (provider.type === "oauth" || provider.type === "oidc") && - !(provider.issuer ?? provider.options?.issuer) - ) { - const { authorization: a, token: t, userinfo: u } = provider - - let key - if (typeof a !== "string" && !a?.url) key = "authorization" - else if (typeof t !== "string" && !t?.url) key = "token" - else if (typeof u !== "string" && !u?.url) key = "userinfo" - - if (key) { - return new InvalidEndpoints( - `Provider "${provider.id}" is missing both \`issuer\` and \`${key}\` endpoint config. At least one of them is required.` - ) - } - } - - if (provider.type === "credentials") hasCredentials = true - else if (provider.type === "email") hasEmail = true - } - - if (hasCredentials) { - const dbStrategy = options.session?.strategy === "database" - const onlyCredentials = !options.providers.some( - (p) => p.type !== "credentials" - ) - if (dbStrategy && onlyCredentials) { - return new UnsupportedStrategy( - "Signin in with credentials only supported if JWT strategy is enabled" - ) - } - - const credentialsNoAuthorize = options.providers.some( - (p) => p.type === "credentials" && !p.authorize - ) - if (credentialsNoAuthorize) { - return new MissingAuthorize( - "Must define an authorize() handler to use credentials authentication provider" - ) - } - } - - if (hasEmail) { - const { adapter } = options - if (!adapter) { - return new MissingAdapter("E-mail login requires an adapter.") - } - - const missingMethods = [ - "createVerificationToken", - "useVerificationToken", - "getUserByEmail", - ].filter((method) => !adapter[method]) - - if (missingMethods.length) { - return new MissingAdapterMethods( - `Required adapter methods were missing: ${missingMethods.join(", ")}` - ) - } - } - - if (!warned) warned = true - - return warnings -} diff --git a/packages/core/src/lib/callback-handler.ts b/packages/core/src/lib/callback-handler.ts deleted file mode 100644 index 76a4b037..00000000 --- a/packages/core/src/lib/callback-handler.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { AccountNotLinkedError } from "./errors" -import { fromDate } from "./utils/date" - -import type { Account, InternalOptions, User } from ".." -import type { AdapterSession, AdapterUser } from "../adapters" -import type { JWT } from "../jwt" -import type { OAuthConfig } from "../providers" -import type { SessionToken } from "./cookie" - -/** - * This function handles the complex flow of signing users in, and either creating, - * linking (or not linking) accounts depending on if the user is currently logged - * in, if they have account already and the authentication mechanism they are using. - * - * It prevents insecure behaviour, such as linking OAuth accounts unless a user is - * signed in and authenticated with an existing valid account. - * - * All verification (e.g. OAuth flows or email address verificaiton flows) are - * done prior to this handler being called to avoid additonal complexity in this - * handler. - */ -export default async function callbackHandler(params: { - sessionToken?: SessionToken - profile: User | AdapterUser | { email: string } - account: Account | null - options: InternalOptions -}) { - const { sessionToken, profile: _profile, account, options } = params - // Input validation - if (!account?.providerAccountId || !account.type) - throw new Error("Missing or invalid provider account") - if (!["email", "oauth", "oidc"].includes(account.type)) - throw new Error("Provider not supported") - - const { - adapter, - jwt, - events, - session: { strategy: sessionStrategy, generateSessionToken }, - } = options - - // If no adapter is configured then we don't have a database and cannot - // persist data; in this mode we just return a dummy session object. - if (!adapter) { - return { user: _profile as User, account } - } - - const profile = _profile as AdapterUser - - const { - createUser, - updateUser, - getUser, - getUserByAccount, - getUserByEmail, - linkAccount, - createSession, - getSessionAndUser, - deleteSession, - } = adapter - - let session: AdapterSession | JWT | null = null - let user: AdapterUser | null = null - let isNewUser = false - - const useJwtSession = sessionStrategy === "jwt" - - if (sessionToken) { - if (useJwtSession) { - try { - session = await jwt.decode({ ...jwt, token: sessionToken }) - if (session && "sub" in session && session.sub) { - user = await getUser(session.sub) - } - } catch { - // If session can't be verified, treat as no session - } - } else { - const userAndSession = await getSessionAndUser(sessionToken) - if (userAndSession) { - session = userAndSession.session - user = userAndSession.user - } - } - } - - if (account.type === "email") { - // If signing in with an email, check if an account with the same email address exists already - const userByEmail = await getUserByEmail(profile.email) - if (userByEmail) { - // If they are not already signed in as the same user, this flow will - // sign them out of the current session and sign them in as the new user - if (user?.id !== userByEmail.id && !useJwtSession && sessionToken) { - // Delete existing session if they are currently signed in as another user. - // This will switch user accounts for the session in cases where the user was - // already logged in with a different account. - await deleteSession(sessionToken) - } - - // Update emailVerified property on the user object - user = await updateUser({ id: userByEmail.id, emailVerified: new Date() }) - await events.updateUser?.({ user }) - } else { - const { id: _, ...newUser } = { ...profile, emailVerified: new Date() } - // Create user account if there isn't one for the email address already - user = await createUser(newUser) - await events.createUser?.({ user }) - isNewUser = true - } - - // Create new session - session = useJwtSession - ? {} - : await createSession({ - sessionToken: generateSessionToken(), - userId: user.id, - expires: fromDate(options.session.maxAge), - }) - - return { session, user, isNewUser } - } else if (account.type === "oauth") { - // If signing in with OAuth account, check to see if the account exists already - const userByAccount = await getUserByAccount({ - providerAccountId: account.providerAccountId, - provider: account.provider, - }) - if (userByAccount) { - if (user) { - // If the user is already signed in with this account, we don't need to do anything - if (userByAccount.id === user.id) { - return { session, user, isNewUser } - } - // If the user is currently signed in, but the new account they are signing in - // with is already associated with another user, then we cannot link them - // and need to return an error. - throw new AccountNotLinkedError( - "The account is already associated with another user" - ) - } - // If there is no active session, but the account being signed in with is already - // associated with a valid user then create session to sign the user in. - session = useJwtSession - ? {} - : await createSession({ - sessionToken: generateSessionToken(), - userId: userByAccount.id, - expires: fromDate(options.session.maxAge), - }) - - return { session, user: userByAccount, isNewUser } - } else { - if (user) { - // If the user is already signed in and the OAuth account isn't already associated - // with another user account then we can go ahead and link the accounts safely. - await linkAccount({ ...account, userId: user.id }) - await events.linkAccount?.({ user, account, profile }) - - // As they are already signed in, we don't need to do anything after linking them - return { session, user, isNewUser } - } - - // If the user is not signed in and it looks like a new OAuth account then we - // check there also isn't an user account already associated with the same - // email address as the one in the OAuth profile. - // - // This step is often overlooked in OAuth implementations, but covers the following cases: - // - // 1. It makes it harder for someone to accidentally create two accounts. - // e.g. by signin in with email, then again with an oauth account connected to the same email. - // 2. It makes it harder to hijack a user account using a 3rd party OAuth account. - // e.g. by creating an oauth account then changing the email address associated with it. - // - // It's quite common for services to automatically link accounts in this case, but it's - // better practice to require the user to sign in *then* link accounts to be sure - // someone is not exploiting a problem with a third party OAuth service. - // - // OAuth providers should require email address verification to prevent this, but in - // practice that is not always the case; this helps protect against that. - const userByEmail = profile.email - ? await getUserByEmail(profile.email) - : null - if (userByEmail) { - const provider = options.provider as OAuthConfig - if (provider?.allowDangerousEmailAccountLinking) { - // If you trust the oauth provider to correctly verify email addresses, you can opt-in to - // account linking even when the user is not signed-in. - user = userByEmail - } else { - // We end up here when we don't have an account with the same [provider].id *BUT* - // we do already have an account with the same email address as the one in the - // OAuth profile the user has just tried to sign in with. - // - // We don't want to have two accounts with the same email address, and we don't - // want to link them in case it's not safe to do so, so instead we prompt the user - // to sign in via email to verify their identity and then link the accounts. - throw new AccountNotLinkedError( - "Another account already exists with the same e-mail address" - ) - } - } else { - // If the current user is not logged in and the profile isn't linked to any user - // accounts (by email or provider account id)... - // - // If no account matching the same [provider].id or .email exists, we can - // create a new account for the user, link it to the OAuth acccount and - // create a new session for them so they are signed in with it. - const { id: _, ...newUser } = { ...profile, emailVerified: null } - user = await createUser(newUser) - } - await events.createUser?.({ user }) - - await linkAccount({ ...account, userId: user.id }) - await events.linkAccount?.({ user, account, profile }) - - session = useJwtSession - ? {} - : await createSession({ - sessionToken: generateSessionToken(), - userId: user.id, - expires: fromDate(options.session.maxAge), - }) - - return { session, user, isNewUser: true } - } - } - - throw new Error("Unsupported account type") -} diff --git a/packages/core/src/lib/callback-url.ts b/packages/core/src/lib/callback-url.ts deleted file mode 100644 index 39c0692d..00000000 --- a/packages/core/src/lib/callback-url.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { InternalOptions } from ".." - -interface CreateCallbackUrlParams { - options: InternalOptions - /** Try reading value from request body (POST) then from query param (GET) */ - paramValue?: string - cookieValue?: string -} - -/** - * Get callback URL based on query param / cookie + validation, - * and add it to `req.options.callbackUrl`. - */ -export async function createCallbackUrl({ - options, - paramValue, - cookieValue, -}: CreateCallbackUrlParams) { - const { url, callbacks } = options - - let callbackUrl = url.origin - - if (paramValue) { - // If callbackUrl form field or query parameter is passed try to use it if allowed - callbackUrl = await callbacks.redirect({ - url: paramValue, - baseUrl: url.origin, - }) - } else if (cookieValue) { - // If no callbackUrl specified, try using the value from the cookie if allowed - callbackUrl = await callbacks.redirect({ - url: cookieValue, - baseUrl: url.origin, - }) - } - - return { - callbackUrl, - // Save callback URL in a cookie so that it can be used for subsequent requests in signin/signout/callback flow - callbackUrlCookie: callbackUrl !== cookieValue ? callbackUrl : undefined, - } -} diff --git a/packages/core/src/lib/cookie.ts b/packages/core/src/lib/cookie.ts deleted file mode 100644 index e038a9e3..00000000 --- a/packages/core/src/lib/cookie.ts +++ /dev/null @@ -1,236 +0,0 @@ -import type { - CookieOption, - CookiesOptions, - LoggerInstance, - SessionStrategy, -} from ".." - -// Uncomment to recalculate the estimated size -// of an empty session cookie -// import { serialize } from "cookie" -// console.log( -// "Cookie estimated to be ", -// serialize(`__Secure.next-auth.session-token.0`, "", { -// expires: new Date(), -// httpOnly: true, -// maxAge: Number.MAX_SAFE_INTEGER, -// path: "/", -// sameSite: "strict", -// secure: true, -// domain: "example.com", -// }).length, -// " bytes" -// ) - -const ALLOWED_COOKIE_SIZE = 4096 -// Based on commented out section above -const ESTIMATED_EMPTY_COOKIE_SIZE = 163 -const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE - -// REVIEW: Is there any way to defer two types of strings? - -/** Stringified form of `JWT`. Extract the content with `jwt.decode` */ -export type JWTString = string - -export type SetCookieOptions = Partial & { - expires?: Date | string - encode?: (val: unknown) => string -} - -/** - * If `options.session.strategy` is set to `jwt`, this is a stringified `JWT`. - * In case of `strategy: "database"`, this is the `sessionToken` of the session in the database. - */ -export type SessionToken = T extends "jwt" - ? JWTString - : string - -/** - * Use secure cookies if the site uses HTTPS - * This being conditional allows cookies to work non-HTTPS development URLs - * Honour secure cookie option, which sets 'secure' and also adds '__Secure-' - * prefix, but enable them by default if the site URL is HTTPS; but not for - * non-HTTPS URLs like http://localhost which are used in development). - * For more on prefixes see https://googlechrome.github.io/samples/cookie-prefixes/ - * - * @TODO Review cookie settings (names, options) - */ -export function defaultCookies(useSecureCookies: boolean): CookiesOptions { - const cookiePrefix = useSecureCookies ? "__Secure-" : "" - return { - // default cookie options - sessionToken: { - name: `${cookiePrefix}next-auth.session-token`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: useSecureCookies, - }, - }, - callbackUrl: { - name: `${cookiePrefix}next-auth.callback-url`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: useSecureCookies, - }, - }, - csrfToken: { - // Default to __Host- for CSRF token for additional protection if using useSecureCookies - // NB: The `__Host-` prefix is stricter than the `__Secure-` prefix. - name: `${useSecureCookies ? "__Host-" : ""}next-auth.csrf-token`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: useSecureCookies, - }, - }, - pkceCodeVerifier: { - name: `${cookiePrefix}next-auth.pkce.code_verifier`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: useSecureCookies, - maxAge: 60 * 15, // 15 minutes in seconds - }, - }, - state: { - name: `${cookiePrefix}next-auth.state`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: useSecureCookies, - maxAge: 60 * 15, // 15 minutes in seconds - }, - }, - nonce: { - name: `${cookiePrefix}next-auth.nonce`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: useSecureCookies, - }, - }, - } -} - -export interface Cookie extends CookieOption { - value: string -} - -type Chunks = Record - -export class SessionStore { - #chunks: Chunks = {} - #option: CookieOption - #logger: LoggerInstance | Console - - constructor( - option: CookieOption, - req: Partial<{ cookies: any; headers: any }>, - logger: LoggerInstance | Console - ) { - this.#logger = logger - this.#option = option - - const { cookies } = req - const { name: cookieName } = option - - if (typeof cookies?.getAll === "function") { - // Next.js ^v13.0.1 (Edge Env) - for (const { name, value } of cookies.getAll()) { - if (name.startsWith(cookieName)) { - this.#chunks[name] = value - } - } - } else if (cookies instanceof Map) { - for (const name of cookies.keys()) { - if (name.startsWith(cookieName)) this.#chunks[name] = cookies.get(name) - } - } else { - for (const name in cookies) { - if (name.startsWith(cookieName)) this.#chunks[name] = cookies[name] - } - } - } - - get value() { - return Object.values(this.#chunks)?.join("") - } - - /** Given a cookie, return a list of cookies, chunked to fit the allowed cookie size. */ - #chunk(cookie: Cookie): Cookie[] { - const chunkCount = Math.ceil(cookie.value.length / CHUNK_SIZE) - - if (chunkCount === 1) { - this.#chunks[cookie.name] = cookie.value - return [cookie] - } - - const cookies: Cookie[] = [] - for (let i = 0; i < chunkCount; i++) { - const name = `${cookie.name}.${i}` - const value = cookie.value.substr(i * CHUNK_SIZE, CHUNK_SIZE) - cookies.push({ ...cookie, name, value }) - this.#chunks[name] = value - } - - this.#logger.debug("CHUNKING_SESSION_COOKIE", { - message: `Session cookie exceeds allowed ${ALLOWED_COOKIE_SIZE} bytes.`, - emptyCookieSize: ESTIMATED_EMPTY_COOKIE_SIZE, - valueSize: cookie.value.length, - chunks: cookies.map((c) => c.value.length + ESTIMATED_EMPTY_COOKIE_SIZE), - }) - - return cookies - } - - /** Returns cleaned cookie chunks. */ - #clean(): Record { - const cleanedChunks: Record = {} - for (const name in this.#chunks) { - delete this.#chunks?.[name] - cleanedChunks[name] = { - name, - value: "", - options: { ...this.#option.options, maxAge: 0 }, - } - } - return cleanedChunks - } - - /** - * Given a cookie value, return new cookies, chunked, to fit the allowed cookie size. - * If the cookie has changed from chunked to unchunked or vice versa, - * it deletes the old cookies as well. - */ - chunk(value: string, options: Partial): Cookie[] { - // Assume all cookies should be cleaned by default - const cookies: Record = this.#clean() - - // Calculate new chunks - const chunked = this.#chunk({ - name: this.#option.name, - value, - options: { ...this.#option.options, ...options }, - }) - - // Update stored chunks / cookies - for (const chunk of chunked) { - cookies[chunk.name] = chunk - } - - return Object.values(cookies) - } - - /** Returns a list of cookies that should be cleaned. */ - clean(): Cookie[] { - return Object.values(this.#clean()) - } -} diff --git a/packages/core/src/lib/csrf-token.ts b/packages/core/src/lib/csrf-token.ts deleted file mode 100644 index 5864bd6e..00000000 --- a/packages/core/src/lib/csrf-token.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createHash, randomString } from "./web" -import type { InternalOptions } from "./types" - -interface CreateCSRFTokenParams { - options: InternalOptions - cookieValue?: string - isPost: boolean - bodyValue?: string -} - -/** - * Ensure CSRF Token cookie is set for any subsequent requests. - * Used as part of the strategy for mitigation for CSRF tokens. - * - * Creates a cookie like 'next-auth.csrf-token' with the value 'token|hash', - * where 'token' is the CSRF token and 'hash' is a hash made of the token and - * the secret, and the two values are joined by a pipe '|'. By storing the - * value and the hash of the value (with the secret used as a salt) we can - * verify the cookie was set by the server and not by a malicous attacker. - * - * For more details, see the following OWASP links: - * https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie - * https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf - */ -export async function createCSRFToken({ - options, - cookieValue, - isPost, - bodyValue, -}: CreateCSRFTokenParams) { - if (cookieValue) { - const [csrfToken, csrfTokenHash] = cookieValue.split("|") - - const expectedCsrfTokenHash = await createHash( - `${csrfToken}${options.secret}` - ) - - if (csrfTokenHash === expectedCsrfTokenHash) { - // If hash matches then we trust the CSRF token value - // If this is a POST request and the CSRF Token in the POST request matches - // the cookie we have already verified is the one we have set, then the token is verified! - const csrfTokenVerified = isPost && csrfToken === bodyValue - - return { csrfTokenVerified, csrfToken } - } - } - - // New CSRF token - const csrfToken = randomString(32) - const csrfTokenHash = await createHash(`${csrfToken}${options.secret}`) - const cookie = `${csrfToken}|${csrfTokenHash}` - - return { cookie, csrfToken } -} diff --git a/packages/core/src/lib/default-callbacks.ts b/packages/core/src/lib/default-callbacks.ts deleted file mode 100644 index ed7e3d51..00000000 --- a/packages/core/src/lib/default-callbacks.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CallbacksOptions } from ".." - -export const defaultCallbacks: CallbacksOptions = { - signIn() { - return true - }, - redirect({ url, baseUrl }) { - if (url.startsWith("/")) return `${baseUrl}${url}` - else if (new URL(url).origin === baseUrl) return url - return baseUrl - }, - session({ session }) { - return session - }, - jwt({ token }) { - return token - }, -} diff --git a/packages/core/src/lib/email/getUserFromEmail.ts b/packages/core/src/lib/email/getUserFromEmail.ts deleted file mode 100644 index c0b0b56d..00000000 --- a/packages/core/src/lib/email/getUserFromEmail.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { AdapterUser } from "../../adapters" -import type { InternalOptions } from "../.." - -/** - * Query the database for a user by email address. - * If is an existing user return a user object (otherwise use placeholder). - */ -export default async function getAdapterUserFromEmail({ - email, - adapter, -}: { - email: string - adapter: InternalOptions<"email">["adapter"] -}): Promise { - const { getUserByEmail } = adapter - const adapterUser = email ? await getUserByEmail(email) : null - if (adapterUser) return adapterUser - - return { id: email, email, emailVerified: null } -} diff --git a/packages/core/src/lib/email/signin.ts b/packages/core/src/lib/email/signin.ts deleted file mode 100644 index 5ba00043..00000000 --- a/packages/core/src/lib/email/signin.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { randomString, createHash } from "../web" -import type { InternalOptions } from "../.." - -/** - * Starts an e-mail login flow, by generating a token, - * and sending it to the user's e-mail (with the help of a DB adapter) - */ -export default async function email( - identifier: string, - options: InternalOptions<"email"> -): Promise { - const { url, adapter, provider, callbackUrl, theme } = options - // Generate token - const token = - (await provider.generateVerificationToken?.()) ?? randomString(32) - - const ONE_DAY_IN_SECONDS = 86400 - const expires = new Date( - Date.now() + (provider.maxAge ?? ONE_DAY_IN_SECONDS) * 1000 - ) - - // Generate a link with email, unhashed token and callback url - const params = new URLSearchParams({ callbackUrl, token, email: identifier }) - const _url = `${url}/callback/${provider.id}?${params}` - - const secret = provider.secret ?? options.secret - await Promise.all([ - // Send to user - provider.sendVerificationRequest({ - identifier, - token, - expires, - url: _url, - provider, - theme, - }), - // Save in database - adapter.createVerificationToken({ - identifier, - token: await createHash(`${token}${secret}`), - expires, - }), - ]) - - return `${url}/verify-request?${new URLSearchParams({ - provider: provider.id, - type: provider.type, - })}` -} diff --git a/packages/core/src/lib/errors.ts b/packages/core/src/lib/errors.ts deleted file mode 100644 index 1f569103..00000000 --- a/packages/core/src/lib/errors.ts +++ /dev/null @@ -1,141 +0,0 @@ -import type { EventCallbacks, LoggerInstance } from "./types" - -/** - * Same as the default `Error`, but it is JSON serializable. - * @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af - */ -export class UnknownError extends Error { - code: string - constructor(error: Error | string) { - // Support passing error or string - super((error as Error)?.message ?? error) - this.name = "UnknownError" - this.code = (error as any).code - if (error instanceof Error) { - this.stack = error.stack - } - } - - toJSON() { - return { - name: this.name, - message: this.message, - stack: this.stack, - } - } -} - -export class OAuthCallbackError extends UnknownError { - name = "OAuthCallbackError" -} - -/** - * Thrown when an Email address is already associated with an account - * but the user is trying an OAuth account that is not linked to it. - */ -export class AccountNotLinkedError extends UnknownError { - name = "AccountNotLinkedError" -} - -export class MissingAPIRoute extends UnknownError { - name = "MissingAPIRouteError" - code = "MISSING_NEXTAUTH_API_ROUTE_ERROR" -} - -export class MissingSecret extends UnknownError { - name = "MissingSecretError" - code = "NO_SECRET" -} - -export class MissingAuthorize extends UnknownError { - name = "MissingAuthorizeError" - code = "CALLBACK_CREDENTIALS_HANDLER_ERROR" -} - -export class MissingAdapter extends UnknownError { - name = "MissingAdapterError" - code = "EMAIL_REQUIRES_ADAPTER_ERROR" -} - -export class MissingAdapterMethods extends UnknownError { - name = "MissingAdapterMethodsError" - code = "MISSING_ADAPTER_METHODS_ERROR" -} - -export class UnsupportedStrategy extends UnknownError { - name = "UnsupportedStrategyError" - code = "CALLBACK_CREDENTIALS_JWT_ERROR" -} - -export class InvalidCallbackUrl extends UnknownError { - name = "InvalidCallbackUrlError" - code = "INVALID_CALLBACK_URL_ERROR" -} - -export class InvalidEndpoints extends UnknownError { - name = "InvalidEndpoints" - code = "INVALID_ENDPOINTS_ERROR" -} -export class UnknownAction extends UnknownError { - name = "UnknownAction" - code = "UNKNOWN_ACTION_ERROR" -} - -export class UntrustedHost extends UnknownError { - name = "UntrustedHost" - code = "UNTRUST_HOST_ERROR" -} - -type Method = (...args: any[]) => Promise - -export function upperSnake(s: string) { - return s.replace(/([A-Z])/g, "_$1").toUpperCase() -} - -export function capitalize(s: string) { - return `${s[0].toUpperCase()}${s.slice(1)}` -} - -/** - * Wraps an object of methods and adds error handling. - */ -export function eventsErrorHandler( - methods: Partial, - logger: LoggerInstance -): Partial { - return Object.keys(methods).reduce((acc, name) => { - acc[name] = async (...args: any[]) => { - try { - const method: Method = methods[name as keyof Method] - return await method(...args) - } catch (e) { - logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error) - } - } - return acc - }, {}) -} - -/** Handles adapter induced errors. */ -export function adapterErrorHandler( - adapter: TAdapter | undefined, - logger: LoggerInstance -): TAdapter | undefined { - if (!adapter) return - - return Object.keys(adapter).reduce((acc, name) => { - acc[name] = async (...args: any[]) => { - try { - logger.debug(`adapter_${name}`, { args }) - const method: Method = adapter[name as keyof Method] - return await method(...args) - } catch (error) { - logger.error(`adapter_error_${name}`, error as Error) - const e = new UnknownError(error as Error) - e.name = `${capitalize(name)}Error` - throw e - } - } - return acc - }, {}) -} diff --git a/packages/core/src/lib/init.ts b/packages/core/src/lib/init.ts deleted file mode 100644 index 2d211013..00000000 --- a/packages/core/src/lib/init.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { adapterErrorHandler, eventsErrorHandler } from "./errors" -import * as jwt from "../jwt" -import { createCallbackUrl } from "./callback-url" -import * as cookie from "./cookie" -import { createCSRFToken } from "./csrf-token" -import { defaultCallbacks } from "./default-callbacks" -import parseProviders from "./providers" -import logger from "./utils/logger" -import parseUrl from "./utils/parse-url" - -import type { AuthOptions, InternalOptions, RequestInternal } from ".." - -interface InitParams { - url: URL - authOptions: AuthOptions - providerId?: string - action: InternalOptions["action"] - /** Callback URL value extracted from the incoming request. */ - callbackUrl?: string - /** CSRF token value extracted from the incoming request. From body if POST, from query if GET */ - csrfToken?: string - /** Is the incoming request a POST request? */ - isPost: boolean - cookies: RequestInternal["cookies"] -} - -/** Initialize all internal options and cookies. */ -export async function init({ - authOptions, - providerId, - action, - url: reqUrl, - cookies: reqCookies, - callbackUrl: reqCallbackUrl, - csrfToken: reqCsrfToken, - isPost, -}: InitParams): Promise<{ - options: InternalOptions - cookies: cookie.Cookie[] -}> { - // TODO: move this to web.ts - const parsed = parseUrl( - reqUrl.origin + - reqUrl.pathname.replace(`/${action}`, "").replace(`/${providerId}`, "") - ) - const url = new URL(parsed.toString()) - - const { providers, provider } = parseProviders({ - providers: authOptions.providers, - url, - providerId, - }) - - const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle by default - - // User provided options are overriden by other options, - // except for the options with special handling above - const options: InternalOptions = { - debug: false, - pages: {}, - theme: { - colorScheme: "auto", - logo: "", - brandColor: "", - buttonText: "", - }, - // Custom options override defaults - ...authOptions, - // These computed settings can have values in userOptions but we override them - // and are request-specific. - url, - action, - // @ts-expect-errors - provider, - cookies: { - ...cookie.defaultCookies( - authOptions.useSecureCookies ?? url.protocol === "https:" - ), - // Allow user cookie options to override any cookie settings above - ...authOptions.cookies, - }, - providers, - // Session options - session: { - // If no adapter specified, force use of JSON Web Tokens (stateless) - strategy: authOptions.adapter ? "database" : "jwt", - maxAge, - updateAge: 24 * 60 * 60, - generateSessionToken: crypto.randomUUID, - ...authOptions.session, - }, - // JWT options - jwt: { - // Asserted in assert.ts - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - secret: authOptions.secret!, - maxAge, // same as session maxAge, - encode: jwt.encode, - decode: jwt.decode, - ...authOptions.jwt, - }, - // Event messages - events: eventsErrorHandler(authOptions.events ?? {}, logger), - adapter: adapterErrorHandler(authOptions.adapter, logger), - // Callback functions - callbacks: { ...defaultCallbacks, ...authOptions.callbacks }, - logger, - callbackUrl: url.origin, - } - - // Init cookies - - const cookies: cookie.Cookie[] = [] - - const { - csrfToken, - cookie: csrfCookie, - csrfTokenVerified, - } = await createCSRFToken({ - options, - cookieValue: reqCookies?.[options.cookies.csrfToken.name], - isPost, - bodyValue: reqCsrfToken, - }) - - options.csrfToken = csrfToken - options.csrfTokenVerified = csrfTokenVerified - - if (csrfCookie) { - cookies.push({ - name: options.cookies.csrfToken.name, - value: csrfCookie, - options: options.cookies.csrfToken.options, - }) - } - - const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({ - options, - cookieValue: reqCookies?.[options.cookies.callbackUrl.name], - paramValue: reqCallbackUrl, - }) - options.callbackUrl = callbackUrl - if (callbackUrlCookie) { - cookies.push({ - name: options.cookies.callbackUrl.name, - value: callbackUrlCookie, - options: options.cookies.callbackUrl.options, - }) - } - - return { options, cookies } -} diff --git a/packages/core/src/lib/oauth/authorization-url.ts b/packages/core/src/lib/oauth/authorization-url.ts deleted file mode 100644 index 275d04c7..00000000 --- a/packages/core/src/lib/oauth/authorization-url.ts +++ /dev/null @@ -1,147 +0,0 @@ -import * as o from "oauth4webapi" - -import type { - CookiesOptions, - InternalOptions, - RequestInternal, - ResponseInternal, -} from "../.." -import type { Cookie } from "../cookie" - -/** - * Generates an authorization/request token URL. - * - * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) - */ -export async function getAuthorizationUrl({ - options, - query, -}: { - options: InternalOptions<"oauth"> - query: RequestInternal["query"] -}): Promise { - const { logger, provider } = options - - let url = provider.authorization?.url - let as: o.AuthorizationServer | undefined - - if (!url) { - // If url is undefined, we assume that issuer is always defined - // We check this in assert.ts - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const issuer = new URL(provider.issuer!) - const discoveryResponse = await o.discoveryRequest(issuer) - const as = await o.processDiscoveryResponse(issuer, discoveryResponse) - - if (!as.authorization_endpoint) { - throw new TypeError( - "Authorization server did not provide an authorization endpoint." - ) - } - - url = new URL(as.authorization_endpoint) - } - - const authParams = url.searchParams - const params = Object.assign( - { - response_type: "code", - // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it? - client_id: provider.clientId, - redirect_uri: provider.callbackUrl, - // @ts-expect-error TODO: - ...provider.authorization?.params, - }, // Defaults - Object.fromEntries(authParams), // From provider config - query // From `signIn` call - ) - - for (const k in params) authParams.set(k, params[k]) - - const cookies: Cookie[] = [] - - if (provider.checks?.includes("state")) { - const { value, raw } = await createState(options) - authParams.set("state", raw) - cookies.push(value) - } - - if (provider.checks?.includes("pkce")) { - if (as && !as.code_challenge_methods_supported?.includes("S256")) { - // We assume S256 PKCE support, if the server does not advertise that, - // a random `nonce` must be used for CSRF protection. - provider.checks = ["nonce"] - } else { - const { code_challenge, pkce } = await createPKCE(options) - authParams.set("code_challenge", code_challenge) - authParams.set("code_challenge_method", "S256") - cookies.push(pkce) - } - } - - if (provider.checks?.includes("nonce")) { - const nonce = await createNonce(options) - authParams.set("nonce", nonce.value) - cookies.push(nonce) - } - - url.searchParams.delete("nextauth") - - // TODO: This does not work in normalizeOAuth because authorization endpoint can come from discovery - // Need to make normalizeOAuth async - if (provider.type === "oidc" && !url.searchParams.has("scope")) { - url.searchParams.set("scope", "openid profile email") - } - - logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider }) - return { redirect: url, cookies } -} - -/** Returns a signed cookie. */ -export async function signCookie( - type: keyof CookiesOptions, - value: string, - maxAge: number, - options: InternalOptions<"oauth"> -): Promise { - const { cookies, jwt, logger } = options - - logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge }) - - const expires = new Date() - expires.setTime(expires.getTime() + maxAge * 1000) - return { - name: cookies[type].name, - value: await jwt.encode({ ...jwt, maxAge, token: { value } }), - options: { ...cookies[type].options, expires }, - } -} - -const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds -async function createState(options: InternalOptions<"oauth">) { - const raw = o.generateRandomState() - const maxAge = STATE_MAX_AGE - const value = await signCookie("state", raw, maxAge, options) - return { value, raw } -} - -const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds -async function createPKCE(options: InternalOptions<"oauth">) { - const code_verifier = o.generateRandomCodeVerifier() - const code_challenge = await o.calculatePKCECodeChallenge(code_verifier) - const maxAge = PKCE_MAX_AGE - const pkce = await signCookie( - "pkceCodeVerifier", - code_verifier, - maxAge, - options - ) - return { code_challenge, pkce } -} - -const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds -async function createNonce(options: InternalOptions<"oauth">) { - const raw = o.generateRandomNonce() - const maxAge = NONCE_MAX_AGE - return await signCookie("nonce", raw, maxAge, options) -} diff --git a/packages/core/src/lib/oauth/callback.ts b/packages/core/src/lib/oauth/callback.ts deleted file mode 100644 index e31df488..00000000 --- a/packages/core/src/lib/oauth/callback.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { OAuthCallbackError } from "../errors" -import { useNonce } from "./nonce-handler" -import { usePKCECodeVerifier } from "./pkce-handler" -import { useState } from "./state-handler" -import * as o from "oauth4webapi" - -import type { - InternalOptions, - LoggerInstance, - Profile, - RequestInternal, - TokenSet, -} from "../.." -import type { OAuthConfigInternal } from "../../providers" -import type { Cookie } from "../cookie" - -export async function handleOAuthCallback(params: { - options: InternalOptions<"oauth"> - query: RequestInternal["query"] - body: RequestInternal["body"] - cookies: RequestInternal["cookies"] -}) { - const { options, query, body, cookies } = params - const { logger, provider } = options - - const errorMessage = body?.error ?? query?.error - if (errorMessage) { - const error = new Error(errorMessage) - logger.error("OAUTH_CALLBACK_HANDLER_ERROR", { - error, - error_description: query?.error_description, - providerId: provider.id, - }) - logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", { body }) - throw error - } - - try { - let as: o.AuthorizationServer - - if (!provider.token?.url && !provider.userinfo?.url) { - // We assume that issuer is always defined as this has been asserted earlier - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const issuer = new URL(provider.issuer!) - const discoveryResponse = await o.discoveryRequest(issuer) - const discoveredAs = await o.processDiscoveryResponse( - issuer, - discoveryResponse - ) - - if (!discoveredAs.token_endpoint) - throw new TypeError( - "TODO: Authorization server did not provide a token endpoint." - ) - - if (!discoveredAs.userinfo_endpoint) - throw new TypeError( - "TODO: Authorization server did not provide a userinfo endpoint." - ) - - as = discoveredAs - } else { - as = { - issuer: provider.issuer ?? "https://a", // TODO: review fallback issuer - token_endpoint: provider.token?.url.toString(), - userinfo_endpoint: provider.userinfo?.url.toString(), - } - } - - const client: o.Client = { - client_id: provider.clientId, - client_secret: provider.clientSecret, - ...provider.client, - } - - const resCookies: Cookie[] = [] - - const state = await useState(cookies?.[options.cookies.state.name], options) - if (state) resCookies.push(state.cookie) - - const codeVerifier = await usePKCECodeVerifier( - cookies?.[options.cookies.pkceCodeVerifier.name], - options - ) - if (codeVerifier) resCookies.push(codeVerifier.cookie) - - // TODO: - const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options) - if (nonce && provider.type === "oidc") { - resCookies.push(nonce.cookie) - } - - const parameters = o.validateAuthResponse( - as, - client, - new URLSearchParams(query), - provider.checks.includes("state") ? state?.value : o.skipStateCheck - ) - - if (o.isOAuth2Error(parameters)) { - console.log("error", parameters) - throw new Error("TODO: Handle OAuth 2.0 redirect error") - } - - const codeGrantResponse = await o.authorizationCodeGrantRequest( - as, - client, - parameters, - provider.callbackUrl, - codeVerifier?.codeVerifier ?? "auth" // TODO: review fallback code verifier - ) - - let challenges: o.WWWAuthenticateChallenge[] | undefined - if ((challenges = o.parseWwwAuthenticateChallenges(codeGrantResponse))) { - for (const challenge of challenges) { - console.log("challenge", challenge) - } - throw new Error("TODO: Handle www-authenticate challenges as needed") - } - - let profile: Profile = {} - let tokens: TokenSet - - if (provider.type === "oidc") { - const result = await o.processAuthorizationCodeOpenIDResponse( - as, - client, - codeGrantResponse - ) - - if (o.isOAuth2Error(result)) { - console.log("error", result) - throw new Error("TODO: Handle OIDC response body error") - } - - profile = o.getValidatedIdTokenClaims(result) - tokens = result - } else { - tokens = await o.processAuthorizationCodeOAuth2Response( - as, - client, - codeGrantResponse - ) - if (o.isOAuth2Error(tokens as any)) { - console.log("error", tokens) - throw new Error("TODO: Handle OAuth 2.0 response body error") - } - - if (provider.userinfo?.request) { - profile = await provider.userinfo.request({ tokens, provider }) - } else if (provider.userinfo?.url) { - const userinfoResponse = await o.userInfoRequest( - as, - client, - (tokens as any).access_token - ) - profile = await userinfoResponse.json() - } - } - - const profileResult = await getProfile({ - profile, - provider, - tokens, - logger, - }) - - return { ...profileResult, cookies: resCookies } - } catch (error) { - throw new OAuthCallbackError(error as Error) - } -} - -interface GetProfileParams { - profile: Profile - tokens: TokenSet - provider: OAuthConfigInternal - logger: LoggerInstance -} - -/** Returns profile, raw profile and auth provider details */ -async function getProfile({ - profile: OAuthProfile, - tokens, - provider, - logger, -}: GetProfileParams) { - try { - logger.debug("PROFILE_DATA", { OAuthProfile }) - const profile = await provider.profile(OAuthProfile, tokens) - profile.email = profile.email?.toLowerCase() - if (!profile.id) - throw new TypeError( - `Profile id is missing in ${provider.name} OAuth profile response` - ) - - // Return profile, raw profile and auth provider details - return { - profile, - account: { - provider: provider.id, - type: provider.type, - providerAccountId: profile.id.toString(), - ...tokens, - }, - OAuthProfile, - } - } catch (error) { - // If we didn't get a response either there was a problem with the provider - // response *or* the user cancelled the action with the provider. - // - // Unfortuately, we can't tell which - at least not in a way that works for - // all providers, so we return an empty object; the user should then be - // redirected back to the sign up page. We log the error to help developers - // who might be trying to debug this when configuring a new provider. - logger.error("OAUTH_PARSE_PROFILE_ERROR", { - error: error as Error, - OAuthProfile, - }) - } -} diff --git a/packages/core/src/lib/oauth/nonce-handler.ts b/packages/core/src/lib/oauth/nonce-handler.ts deleted file mode 100644 index 1096ae2b..00000000 --- a/packages/core/src/lib/oauth/nonce-handler.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as o from "oauth4webapi" -import * as jwt from "../../jwt" - -import type { InternalOptions } from "../.." -import type { Cookie } from "../cookie" - -const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds - -/** - * Returns nonce if the provider supports it - * and saves it in a cookie */ -export async function createNonce(options: InternalOptions<"oauth">): Promise< - | undefined - | { - value: string - cookie: Cookie - } -> { - const { cookies, logger, provider } = options - if (!provider.checks?.includes("nonce")) { - // Provider does not support nonce, return nothing. - return - } - - const nonce = o.generateRandomNonce() - - const expires = new Date() - expires.setTime(expires.getTime() + NONCE_MAX_AGE * 1000) - - // Encrypt nonce and save it to an encrypted cookie - const encryptedNonce = await jwt.encode({ - ...options.jwt, - maxAge: NONCE_MAX_AGE, - token: { nonce }, - }) - - logger.debug("CREATE_ENCRYPTED_NONCE", { - nonce, - maxAge: NONCE_MAX_AGE, - }) - - return { - cookie: { - name: cookies.nonce.name, - value: encryptedNonce, - options: { ...cookies.nonce.options, expires }, - }, - value: nonce, - } -} - -/** - * Returns nonce from if the provider supports nonce, - * and clears the container cookie afterwards. - */ -export async function useNonce( - nonce: string | undefined, - options: InternalOptions<"oauth"> -): Promise<{ value: string; cookie: Cookie } | undefined> { - const { cookies, provider } = options - - if (!provider?.checks?.includes("nonce") || !nonce) { - return - } - - const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any - - return { - value: value?.value ?? undefined, - cookie: { - name: cookies.nonce.name, - value: "", - options: { ...cookies.nonce.options, maxAge: 0 }, - }, - } -} diff --git a/packages/core/src/lib/oauth/pkce-handler.ts b/packages/core/src/lib/oauth/pkce-handler.ts deleted file mode 100644 index ab003b84..00000000 --- a/packages/core/src/lib/oauth/pkce-handler.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as o from "oauth4webapi" -import * as jwt from "../../jwt" - -import type { InternalOptions } from "../.." -import type { Cookie } from "../cookie" - -const PKCE_CODE_CHALLENGE_METHOD = "S256" -const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds - -/** - * Returns `code_challenge` and `code_challenge_method` - * and saves them in a cookie. - */ -export async function createPKCE(options: InternalOptions<"oauth">): Promise< - | undefined - | { - code_challenge: string - code_challenge_method: "S256" - cookie: Cookie - } -> { - const { cookies, logger, provider } = options - if (!provider.checks?.includes("pkce")) { - // Provider does not support PKCE, return nothing. - return - } - const code_verifier = o.generateRandomCodeVerifier() - const code_challenge = await o.calculatePKCECodeChallenge(code_verifier) - - const maxAge = cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE - - const expires = new Date() - expires.setTime(expires.getTime() + maxAge * 1000) - - // Encrypt code_verifier and save it to an encrypted cookie - const encryptedCodeVerifier = await jwt.encode({ - ...options.jwt, - maxAge, - token: { code_verifier }, - }) - - logger.debug("CREATE_PKCE_CHALLENGE_VERIFIER", { - code_challenge, - code_challenge_method: PKCE_CODE_CHALLENGE_METHOD, - code_verifier, - maxAge, - }) - - return { - code_challenge, - code_challenge_method: PKCE_CODE_CHALLENGE_METHOD, - cookie: { - name: cookies.pkceCodeVerifier.name, - value: encryptedCodeVerifier, - options: { ...cookies.pkceCodeVerifier.options, expires }, - }, - } -} - -/** - * Returns code_verifier if provider uses PKCE, - * and clears the container cookie afterwards. - */ -export async function usePKCECodeVerifier( - codeVerifier: string | undefined, - options: InternalOptions<"oauth"> -): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> { - const { cookies, provider } = options - - if (!provider?.checks?.includes("pkce") || !codeVerifier) { - return - } - - const pkce = (await jwt.decode({ - ...options.jwt, - token: codeVerifier, - })) as any - - return { - codeVerifier: pkce?.value ?? undefined, - cookie: { - name: cookies.pkceCodeVerifier.name, - value: "", - options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 }, - }, - } -} diff --git a/packages/core/src/lib/oauth/state-handler.ts b/packages/core/src/lib/oauth/state-handler.ts deleted file mode 100644 index 548970cd..00000000 --- a/packages/core/src/lib/oauth/state-handler.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { InternalOptions } from "../.." -import type { Cookie } from "../cookie" -import * as o from "oauth4webapi" - -const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds - -/** Returns state if the provider supports it */ -export async function createState( - options: InternalOptions<"oauth"> -): Promise<{ cookie: Cookie; value: string } | undefined> { - const { logger, provider, jwt, cookies } = options - - if (!provider.checks?.includes("state")) { - // Provider does not support state, return nothing - return - } - - const state = o.generateRandomState() - const maxAge = cookies.state.options.maxAge ?? STATE_MAX_AGE - - const encodedState = await jwt.encode({ - ...jwt, - maxAge, - token: { state }, - }) - - logger.debug("CREATE_STATE", { state, maxAge }) - - const expires = new Date() - expires.setTime(expires.getTime() + maxAge * 1000) - return { - value: state, - cookie: { - name: cookies.state.name, - value: encodedState, - options: { ...cookies.state.options, expires }, - }, - } -} - -/** - * Returns state from if the provider supports states, - * and clears the container cookie afterwards. - */ -export async function useState( - state: string | undefined, - options: InternalOptions<"oauth"> -): Promise<{ value: string; cookie: Cookie } | undefined> { - const { cookies, provider, jwt } = options - - if (!provider.checks?.includes("state") || !state) return - - const value = (await jwt.decode({ ...options.jwt, token: state })) as any - - return { - value: value?.value ?? undefined, - cookie: { - name: cookies.state.name, - value: "", - options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 }, - }, - } -} diff --git a/packages/core/src/lib/pages/error.tsx b/packages/core/src/lib/pages/error.tsx deleted file mode 100644 index a2ea8e5f..00000000 --- a/packages/core/src/lib/pages/error.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import type { Theme } from "../.." - -/** - * The following errors are passed as error query parameters to the default or overridden error page. - * - * [Documentation](https://next-auth.js.org/configuration/pages#error-page) */ -export type ErrorType = - | "default" - | "configuration" - | "accessdenied" - | "verification" - -export interface ErrorProps { - url?: URL - theme?: Theme - error?: ErrorType -} - -interface ErrorView { - status: number - heading: string - message: JSX.Element - signin?: JSX.Element -} - -/** Renders an error page. */ -export default function ErrorPage(props: ErrorProps) { - const { url, error = "default", theme } = props - const signinPageUrl = `${url}/signin` - - const errors: Record = { - default: { - status: 200, - heading: "Error", - message: ( -

- - {url?.host} - -

- ), - }, - configuration: { - status: 500, - heading: "Server error", - message: ( -
-

There is a problem with the server configuration.

-

Check the server logs for more information.

-
- ), - }, - accessdenied: { - status: 403, - heading: "Access Denied", - message: ( -
-

You do not have permission to sign in.

-

- - Sign in - -

-
- ), - }, - verification: { - status: 403, - heading: "Unable to sign in", - message: ( -
-

The sign in link is no longer valid.

-

It may have been used already or it may have expired.

-
- ), - signin: ( -

- - Sign in - -

- ), - }, - } - - const { status, heading, message, signin } = - errors[error.toLowerCase()] ?? errors.default - - return { - status, - html: ( -
- {theme?.brandColor && ( - ${title}
${renderToString(html)}
`, - } - } - - return { - signin(props?: any) { - return send({ - html: SigninPage({ - csrfToken: params.csrfToken, - // We only want to render providers - providers: params.providers?.filter( - (provider) => - // Always render oauth and email type providers - ["email", "oauth", "oidc"].includes(provider.type) || - // Only render credentials type provider if credentials are defined - (provider.type === "credentials" && provider.credentials) || - // Don't render other provider types - false - ), - callbackUrl: params.callbackUrl, - theme, - ...query, - ...props, - }), - title: "Sign In", - }) - }, - signout(props?: any) { - return send({ - html: SignoutPage({ - csrfToken: params.csrfToken, - url, - theme, - ...props, - }), - title: "Sign Out", - }) - }, - verifyRequest(props?: any) { - return send({ - html: VerifyRequestPage({ url, theme, ...props }), - title: "Verify Request", - }) - }, - error(props?: { error?: ErrorType }) { - return send({ - ...ErrorPage({ url, theme, ...props }), - title: "Error", - }) - }, - } -} diff --git a/packages/core/src/lib/pages/signin.tsx b/packages/core/src/lib/pages/signin.tsx deleted file mode 100644 index 0543ea51..00000000 --- a/packages/core/src/lib/pages/signin.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import type { InternalProvider, Theme } from "../.." -import type { CSSProperties } from "react" - -/** - * The following errors are passed as error query parameters to the default or overridden sign-in page. - * - * [Documentation](https://next-auth.js.org/configuration/pages#sign-in-page) */ -export type SignInErrorTypes = - | "Signin" - | "OAuthSignin" - | "OAuthCallback" - | "OAuthCreateAccount" - | "EmailCreateAccount" - | "Callback" - | "OAuthAccountNotLinked" - | "EmailSignin" - | "CredentialsSignin" - | "SessionRequired" - | "default" - -export interface SignInServerPageParams { - csrfToken: string - providers: InternalProvider[] - callbackUrl: string - email: string - error: SignInErrorTypes - theme: Theme -} - -export default function SigninPage(props: SignInServerPageParams) { - const { - csrfToken, - providers = [], - callbackUrl, - theme, - email, - error: errorType, - } = props - - if (typeof document !== "undefined" && theme.brandColor) { - document.documentElement.style.setProperty( - "--brand-color", - theme.brandColor - ) - } - - const errors: Record = { - Signin: "Try signing in with a different account.", - OAuthSignin: "Try signing in with a different account.", - OAuthCallback: "Try signing in with a different account.", - OAuthCreateAccount: "Try signing in with a different account.", - EmailCreateAccount: "Try signing in with a different account.", - Callback: "Try signing in with a different account.", - OAuthAccountNotLinked: - "To confirm your identity, sign in with the same account you used originally.", - EmailSignin: "The e-mail could not be sent.", - CredentialsSignin: - "Sign in failed. Check the details you provided are correct.", - SessionRequired: "Please sign in to access this page.", - default: "Unable to sign in.", - } - - const error = errorType && (errors[errorType] ?? errors.default) - - // TODO: move logos - const logos = - "https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/next-auth/provider-logos" - return ( -
- {theme.brandColor && ( -