mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
28 Commits
@auth/core
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c385cf8c7c | ||
|
|
53fa46744c | ||
|
|
451eaaabd2 | ||
|
|
f54424c216 | ||
|
|
09bcc1d504 | ||
|
|
6ecf9cb93d | ||
|
|
ba2711d279 | ||
|
|
03881bf98f | ||
|
|
230164f751 | ||
|
|
fecf5e0a1c | ||
|
|
400d0f1842 | ||
|
|
39657bf06c | ||
|
|
d1dd8d95c4 | ||
|
|
554ec439c9 | ||
|
|
8e4db3899a | ||
|
|
444b99ee96 | ||
|
|
f12b527300 | ||
|
|
ac48211967 | ||
|
|
2bd60f6626 | ||
|
|
a83573ed2f | ||
|
|
6242aa7ecb | ||
|
|
54cbbadc8f | ||
|
|
fd4af6512e | ||
|
|
6482e359b7 | ||
|
|
64aac2efc0 | ||
|
|
df37a24c23 | ||
|
|
8bcdf8e818 | ||
|
|
dd765a1b45 |
70
.eslintrc.js
70
.eslintrc.js
@@ -1,21 +1,20 @@
|
|||||||
// @ts-check
|
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
|
|
||||||
/** @type {import("eslint").ESLint.ConfigData} */
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
extends: ["standard-with-typescript", "prettier"],
|
|
||||||
rules: {
|
|
||||||
camelcase: "off",
|
|
||||||
"@typescript-eslint/naming-convention": "off",
|
|
||||||
"@typescript-eslint/strict-boolean-expressions": "off",
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
|
||||||
},
|
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["*.ts", "*.tsx"],
|
files: ["*.ts", "*.tsx"],
|
||||||
|
extends: ["standard-with-typescript", "prettier"],
|
||||||
|
rules: {
|
||||||
|
camelcase: "off",
|
||||||
|
"@typescript-eslint/naming-convention": "off",
|
||||||
|
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/restrict-template-expressions": "off",
|
||||||
|
},
|
||||||
|
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: [
|
project: [
|
||||||
path.resolve(__dirname, "./packages/**/tsconfig.eslint.json"),
|
path.resolve(__dirname, "./packages/**/tsconfig.eslint.json"),
|
||||||
@@ -24,44 +23,19 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
files: ["*.test.ts", "*.test.js"],
|
|
||||||
env: { jest: true },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["docs/**"],
|
|
||||||
plugins: ["@docusaurus"],
|
|
||||||
extends: ["plugin:@docusaurus/recommended"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["packages/core/src/**/*"],
|
|
||||||
plugins: ["jsdoc"],
|
|
||||||
extends: ["plugin:jsdoc/recommended"],
|
|
||||||
rules: {
|
|
||||||
"jsdoc/require-param": "off",
|
|
||||||
"jsdoc/require-returns": "off",
|
|
||||||
"jsdoc/require-jsdoc": [
|
|
||||||
"warn",
|
|
||||||
{ publicOnly: true, enableFixer: false },
|
|
||||||
],
|
|
||||||
"jsdoc/no-multi-asterisks": ["warn", { allowWhitespace: true }],
|
|
||||||
"jsdoc/tag-lines": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["packages/core/src/adapters.ts"],
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/method-signature-style": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
extends: ["prettier"],
|
||||||
|
globals: {
|
||||||
|
localStorage: "readonly",
|
||||||
|
location: "readonly",
|
||||||
|
fetch: "readonly",
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
camelcase: "off",
|
||||||
|
},
|
||||||
plugins: ["jest"],
|
plugins: ["jest"],
|
||||||
ignorePatterns: [
|
env: {
|
||||||
"**/dist/**",
|
"jest/globals": true,
|
||||||
"**/node_modules/**",
|
},
|
||||||
".eslintrc.js",
|
ignorePatterns: [".eslintrc.js"],
|
||||||
"**/.turbo/**",
|
|
||||||
"**/coverage/**",
|
|
||||||
"**/build/**",
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import * as github from "@actions/github"
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
import * as core from "@actions/core"
|
import * as core from "@actions/core"
|
||||||
import { readFileSync } from "node:fs"
|
import { readFileSync } from "node:fs"
|
||||||
|
import { join } from "node:path"
|
||||||
|
|
||||||
const addReproductionLabel = "incomplete"
|
const addReproductionLabel = "incomplete"
|
||||||
|
const __dirname =
|
||||||
|
"/home/runner/work/nextauthjs/next-auth/.github/actions/issue-validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@@ -70,7 +73,7 @@ async function run() {
|
|||||||
}),
|
}),
|
||||||
client.issues.createComment({
|
client.issues.createComment({
|
||||||
...issueCommon,
|
...issueCommon,
|
||||||
body: readFileSync("repro.md", "utf8"),
|
body: readFileSync(join(__dirname, "repro.md"), "utf8"),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
return core.info(
|
return core.info(
|
||||||
|
|||||||
14
.github/sync.yml
vendored
14
.github/sync.yml
vendored
@@ -1,14 +1,6 @@
|
|||||||
# Note that nextauthjs/next-auth-example syncs from the v4 branch
|
# This is a legacy example pushed from the v4 branch
|
||||||
|
nextauthjs/next-auth-example:
|
||||||
nextauthjs/sveltekit-auth-example:
|
- source: apps/example-nextjs
|
||||||
- source: apps/example-sveltekit
|
|
||||||
dest: .
|
|
||||||
deleteOrphaned: true
|
|
||||||
- .github/FUNDING.yml
|
|
||||||
- LICENSE
|
|
||||||
|
|
||||||
nextauthjs/next-auth-gatsby-example:
|
|
||||||
- source: apps/playground-gatsby
|
|
||||||
dest: .
|
dest: .
|
||||||
deleteOrphaned: true
|
deleteOrphaned: true
|
||||||
- .github/FUNDING.yml
|
- .github/FUNDING.yml
|
||||||
|
|||||||
6
.github/workflows/issue-validator.yml
vendored
6
.github/workflows/issue-validator.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- name: "Run issue validator"
|
- name: 'Run issue validator'
|
||||||
run: node /home/runner/work/next-auth/next-auth/.github/actions/issue-validator/index.mjs
|
run: node ./.github/actions/issue-validator/index.mjs
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
@@ -7,6 +7,7 @@ on:
|
|||||||
- "beta"
|
- "beta"
|
||||||
- "next"
|
- "next"
|
||||||
- "3.x"
|
- "3.x"
|
||||||
|
- "v4"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -29,6 +30,8 @@ jobs:
|
|||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
env:
|
env:
|
||||||
@@ -62,11 +65,15 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
- name: Publish to npm and GitHub
|
- name: Publish to npm and GitHub
|
||||||
run: pnpm release
|
run: |
|
||||||
|
git config --global user.email "balazsorban44@users.noreply.github.com"
|
||||||
|
git config --global user.name "Balázs Orbán"
|
||||||
|
pnpm release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
NO_VERIFY: 1
|
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
|
||||||
|
NPM_TOKEN_ORG: ${{ secrets.NPM_TOKEN_ORG }}
|
||||||
release-pr:
|
release-pr:
|
||||||
name: Publish PR
|
name: Publish PR
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -94,20 +101,19 @@ jobs:
|
|||||||
PR_NUMBER: ${{ github.event.number }}
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
- name: Publish to npm
|
- name: Publish to npm
|
||||||
run: |
|
run: |
|
||||||
cd packages/core
|
cd packages/next-auth
|
||||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
|
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
|
||||||
pnpm publish --no-git-checks --access public --tag experimental
|
pnpm publish --no-git-checks --access public --tag experimental
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN_PKG }}
|
||||||
- name: Comment version on PR
|
- name: Comment version on PR
|
||||||
uses: NejcZdovc/comment-pr@v2
|
uses: NejcZdovc/comment-pr@v2
|
||||||
with:
|
with:
|
||||||
message:
|
message:
|
||||||
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/@auth/core/v/${{ env.VERSION }})!\n \
|
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n \
|
||||||
```sh\npnpm add @auth/core@${{ env.VERSION }}\n```\n \
|
```sh\npnpm add next-auth@${{ env.VERSION }}\n```\n \
|
||||||
```sh\nyarn add @auth/core@${{ env.VERSION }}\n```\n \
|
```sh\nyarn add next-auth@${{ env.VERSION }}\n```\n \
|
||||||
```sh\nnpm i @auth/core@${{ env.VERSION }}\n```"
|
```sh\nnpm i next-auth@${{ env.VERSION }}\n```"
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ steps.determine-version.outputs.version }}
|
VERSION: ${{ steps.determine-version.outputs.version }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
5
.github/workflows/sync-examples.yml
vendored
5
.github/workflows/sync-examples.yml
vendored
@@ -2,7 +2,7 @@ name: Sync Example Repositories
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- v4
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
@@ -14,6 +14,5 @@ jobs:
|
|||||||
# Can update to v1 when https://github.com/BetaHuhn/repo-file-sync-action/issues/168 is resolved
|
# Can update to v1 when https://github.com/BetaHuhn/repo-file-sync-action/issues/168 is resolved
|
||||||
uses: BetaHuhn/repo-file-sync-action@v1.16.5
|
uses: BetaHuhn/repo-file-sync-action@v1.16.5
|
||||||
with:
|
with:
|
||||||
GH_PAT: ${{ secrets.SYNC_EXAMPLE_PAT }}
|
GH_PAT: ${{ secrets.GH_PAT_CLASSIC }}
|
||||||
SKIP_PR: true
|
SKIP_PR: true
|
||||||
ORIGINAL_MESSAGE: true
|
|
||||||
|
|||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -51,7 +51,7 @@ apps/dev/typeorm
|
|||||||
/.vs/slnx.sqlite-journal
|
/.vs/slnx.sqlite-journal
|
||||||
/.vs/slnx.sqlite
|
/.vs/slnx.sqlite
|
||||||
/.vs
|
/.vs
|
||||||
.vscode/generated*
|
.vscode
|
||||||
|
|
||||||
# Jetbrains
|
# Jetbrains
|
||||||
.idea
|
.idea
|
||||||
@@ -81,13 +81,11 @@ docs/.docusaurus
|
|||||||
docs/providers.json
|
docs/providers.json
|
||||||
|
|
||||||
# Core
|
# Core
|
||||||
packages/core/*.js
|
packages/core/adapters.*
|
||||||
packages/core/*.d.ts
|
packages/core/index.*
|
||||||
packages/core/*.d.ts.map
|
packages/core/jwt
|
||||||
packages/core/lib
|
packages/core/lib
|
||||||
packages/core/providers
|
packages/core/providers
|
||||||
docs/docs/reference/03-core
|
|
||||||
docs/docs/reference/04-sveltekit
|
|
||||||
|
|
||||||
|
|
||||||
# SvelteKit
|
# SvelteKit
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
/** @type {import("prettier").Config} */
|
|
||||||
module.exports = {
|
|
||||||
semi: false,
|
|
||||||
singleQuote: false,
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
"apps/dev/pages/api/auth/[...nextauth].ts",
|
|
||||||
"docs/{sidebars,docusaurus.config}.js",
|
|
||||||
],
|
|
||||||
options: { printWidth: 150 },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"files.exclude": {
|
|
||||||
"packages/core/{lib,providers,*.js,*.d.ts,*.d.ts.map}": true,
|
|
||||||
"packages/next-auth/{client,core,css,jwt,next,providers,react,utils,*.js,*.d.ts}": true
|
|
||||||
},
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
|
||||||
"openInGitHub.remote.branch": "main",
|
|
||||||
}
|
|
||||||
18
.vscode/snippets.code-snippets
vendored
18
.vscode/snippets.code-snippets
vendored
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"oauth2-spec": {
|
|
||||||
"description": "Markdown link to OAuth 2 specification",
|
|
||||||
"scope": "typescript",
|
|
||||||
"prefix": "oauth2",
|
|
||||||
"body": [
|
|
||||||
"[OAuth 2](https://datatracker.ietf.org/doc/html/rfc6749)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"oidc-spec": {
|
|
||||||
"description": "Markdown link to OpenID Connect specification",
|
|
||||||
"scope": "typescript",
|
|
||||||
"prefix": "oidc",
|
|
||||||
"body": [
|
|
||||||
"[OIDC](https://openid.net/specs/openid-connect-core-1_0.html)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
ISC License
|
ISC License
|
||||||
|
|
||||||
Copyright (c) 2022-2023, Balázs Orbán
|
Copyright (c) 2018-2021, Iain Collins
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
|||||||
4
apps/dev/.vscode/settings.json
vendored
4
apps/dev/.vscode/settings.json
vendored
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"typescript.tsdk": "../../node_modules/.pnpm/typescript@4.8.4/node_modules/typescript/lib",
|
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
|
||||||
}
|
|
||||||
@@ -8,10 +8,10 @@ export default function Footer() {
|
|||||||
<hr />
|
<hr />
|
||||||
<ul className={styles.navItems}>
|
<ul className={styles.navItems}>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<a href="https://authjs.dev">Documentation</a>
|
<a href="https://next-auth.js.org">Documentation</a>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<a href="https://www.npmjs.com/package/@auth/core">NPM</a>
|
<a href="https://www.npmjs.com/package/next-auth">NPM</a>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf .next",
|
"clean": "rm -rf .next",
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
"lint": "next lint",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"email": "fake-smtp-server",
|
"email": "fake-smtp-server",
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
"faunadb": "^4",
|
"faunadb": "^4",
|
||||||
"next": "13.0.6",
|
"next": "13.0.6",
|
||||||
"next-auth": "workspace:*",
|
"next-auth": "workspace:*",
|
||||||
"@auth/core": "workspace:*",
|
|
||||||
"nodemailer": "^6",
|
"nodemailer": "^6",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18"
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
import { Auth, type AuthConfig } from "@auth/core"
|
import NextAuth, { NextAuthOptions } from "next-auth"
|
||||||
|
|
||||||
// Providers
|
// Providers
|
||||||
import Apple from "@auth/core/providers/apple"
|
import Apple from "next-auth/providers/apple"
|
||||||
import Auth0 from "@auth/core/providers/auth0"
|
import Auth0 from "next-auth/providers/auth0"
|
||||||
import AzureAD from "@auth/core/providers/azure-ad"
|
import AzureAD from "next-auth/providers/azure-ad"
|
||||||
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
|
import AzureB2C from "next-auth/providers/azure-ad-b2c"
|
||||||
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
|
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
|
||||||
// import Cognito from "@auth/core/providers/cognito"
|
// import Cognito from "next-auth/providers/cognito"
|
||||||
import Credentials from "@auth/core/providers/credentials"
|
import Credentials from "next-auth/providers/credentials"
|
||||||
import Discord from "@auth/core/providers/discord"
|
import Discord from "next-auth/providers/discord"
|
||||||
import DuendeIDS6 from "@auth/core/providers/duende-identity-server6"
|
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
|
||||||
// import Email from "@auth/core/providers/email"
|
// import Email from "next-auth/providers/email"
|
||||||
import Facebook from "@auth/core/providers/facebook"
|
import Facebook from "next-auth/providers/facebook"
|
||||||
import Foursquare from "@auth/core/providers/foursquare"
|
import Foursquare from "next-auth/providers/foursquare"
|
||||||
import Freshbooks from "@auth/core/providers/freshbooks"
|
import Freshbooks from "next-auth/providers/freshbooks"
|
||||||
import GitHub from "@auth/core/providers/github"
|
import GitHub from "next-auth/providers/github"
|
||||||
import Gitlab from "@auth/core/providers/gitlab"
|
import Gitlab from "next-auth/providers/gitlab"
|
||||||
import Google from "@auth/core/providers/google"
|
import Google from "next-auth/providers/google"
|
||||||
// import IDS4 from "@auth/core/providers/identity-server4"
|
// import IDS4 from "next-auth/providers/identity-server4"
|
||||||
import Instagram from "@auth/core/providers/instagram"
|
import Instagram from "next-auth/providers/instagram"
|
||||||
// import Keycloak from "@auth/core/providers/keycloak"
|
// import Keycloak from "next-auth/providers/keycloak"
|
||||||
import Line from "@auth/core/providers/line"
|
import Line from "next-auth/providers/line"
|
||||||
import LinkedIn from "@auth/core/providers/linkedin"
|
import LinkedIn from "next-auth/providers/linkedin"
|
||||||
import Mailchimp from "@auth/core/providers/mailchimp"
|
import Mailchimp from "next-auth/providers/mailchimp"
|
||||||
// import Okta from "@auth/core/providers/okta"
|
// import Okta from "next-auth/providers/okta"
|
||||||
import Osu from "@auth/core/providers/osu"
|
import Osu from "next-auth/providers/osu"
|
||||||
import Patreon from "@auth/core/providers/patreon"
|
import Patreon from "next-auth/providers/patreon"
|
||||||
import Slack from "@auth/core/providers/slack"
|
import Slack from "next-auth/providers/slack"
|
||||||
import Spotify from "@auth/core/providers/spotify"
|
import Spotify from "next-auth/providers/spotify"
|
||||||
import Trakt from "@auth/core/providers/trakt"
|
import Trakt from "next-auth/providers/trakt"
|
||||||
import Twitch from "@auth/core/providers/twitch"
|
import Twitch from "next-auth/providers/twitch"
|
||||||
import Twitter from "@auth/core/providers/twitter"
|
import Twitter from "next-auth/providers/twitter"
|
||||||
import Vk from "@auth/core/providers/vk"
|
import Vk from "next-auth/providers/vk"
|
||||||
import Wikimedia from "@auth/core/providers/wikimedia"
|
import Wikimedia from "next-auth/providers/wikimedia"
|
||||||
import WorkOS from "@auth/core/providers/workos"
|
import WorkOS from "next-auth/providers/workos"
|
||||||
|
|
||||||
// // Prisma
|
// // Prisma
|
||||||
// import { PrismaClient } from "@prisma/client"
|
// import { PrismaClient } from "@prisma/client"
|
||||||
@@ -66,7 +66,7 @@ import WorkOS from "@auth/core/providers/workos"
|
|||||||
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||||
// })
|
// })
|
||||||
|
|
||||||
export const authConfig: AuthConfig = {
|
export const authOptions: NextAuthOptions = {
|
||||||
// adapter,
|
// adapter,
|
||||||
// debug: process.env.NODE_ENV !== "production",
|
// debug: process.env.NODE_ENV !== "production",
|
||||||
theme: {
|
theme: {
|
||||||
@@ -118,10 +118,9 @@ export const authConfig: AuthConfig = {
|
|||||||
Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }),
|
Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }),
|
||||||
WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }),
|
WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }),
|
||||||
],
|
],
|
||||||
// debug: process.env.NODE_ENV !== "production",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authConfig.adapter) {
|
if (authOptions.adapter) {
|
||||||
// TODO:
|
// TODO:
|
||||||
// authOptions.providers.unshift(
|
// authOptions.providers.unshift(
|
||||||
// // NOTE: You can start a fake e-mail server with `pnpm email`
|
// // NOTE: You can start a fake e-mail server with `pnpm email`
|
||||||
@@ -130,22 +129,4 @@ if (authConfig.adapter) {
|
|||||||
// )
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to next-auth/edge
|
export default NextAuth(authOptions)
|
||||||
function AuthHandler(...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 Auth(req, args[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
args[1].secret ??= envSecret
|
|
||||||
args[1].trustHost ??= envTrustHost
|
|
||||||
return Auth(args[0], args[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthHandler(authConfig)
|
|
||||||
|
|
||||||
export const config = { runtime: "experimental-edge" }
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ export default function Page () {
|
|||||||
<Layout>
|
<Layout>
|
||||||
<h1>NextAuth.js Example</h1>
|
<h1>NextAuth.js Example</h1>
|
||||||
<p>
|
<p>
|
||||||
This is an example site to demonstrate how to use{' '}
|
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
|
||||||
<a href="https://authjs.dev">NextAuth.js</a> for authentication.
|
|
||||||
</p>
|
</p>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
import Layout from "../components/layout"
|
import Layout from '../components/layout'
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page () {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<p>
|
<p>
|
||||||
This is an example site to demonstrate how to use{" "}
|
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
|
||||||
<a href="https://authjs.dev">Auth.js</a> for authentication.
|
|
||||||
</p>
|
</p>
|
||||||
<h2>Terms of Service</h2>
|
<h2>Terms of Service</h2>
|
||||||
<p>
|
<p>
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
SOFTWARE.
|
||||||
</p>
|
</p>
|
||||||
<h2>Privacy Policy</h2>
|
<h2>Privacy Policy</h2>
|
||||||
<p>
|
<p>
|
||||||
This site uses JSON Web Tokens and an in-memory database which resets
|
This site uses JSON Web Tokens and an in-memory database which resets every ~2 hours.
|
||||||
every ~2 hours.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Data provided to this site is exclusively used to support signing in and
|
Data provided to this site is exclusively used to support signing in
|
||||||
is not passed to any third party services, other than via SMTP or OAuth
|
and is not passed to any third party services, other than via SMTP or OAuth for the
|
||||||
for the purposes of authentication.
|
purposes of authentication.
|
||||||
</p>
|
</p>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default function Page() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session) {
|
if (session) {
|
||||||
|
console.log(session)
|
||||||
// User is logged in, let's fetch their data.
|
// User is logged in, let's fetch their data.
|
||||||
const { supabaseAccessToken } = session
|
const { supabaseAccessToken } = session
|
||||||
const supabase = createClient(
|
const supabase = createClient(
|
||||||
|
|||||||
110
apps/example-gatsby/README.md
Normal file
110
apps/example-gatsby/README.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/example-gatsby). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<br/>
|
||||||
|
<a href="https://next-auth.js.org" target="_blank"><img width="150px" src="https://next-auth.js.org/img/logo/logo-sm.png" /></a>
|
||||||
|
<h3 align="center">NextAuth.js Example App</h3>
|
||||||
|
<p align="center">
|
||||||
|
Open Source. Full Stack. Own Your Data.
|
||||||
|
</p>
|
||||||
|
<p align="center" style="align: center;">
|
||||||
|
<a href="https://npm.im/next-auth">
|
||||||
|
<img alt="npm" src="https://img.shields.io/npm/v/next-auth?color=green&label=next-auth">
|
||||||
|
</a>
|
||||||
|
<a href="https://bundlephobia.com/result?p=next-auth-example">
|
||||||
|
<img src="https://img.shields.io/bundlephobia/minzip/next-auth?label=next-auth" alt="Bundle Size"/>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.npmtrends.com/next-auth">
|
||||||
|
<img src="https://img.shields.io/npm/dm/next-auth?label=next-auth%20downloads" alt="Downloads" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
NextAuth.js is a complete open source authentication solution.
|
||||||
|
|
||||||
|
This is an example application that shows how `next-auth` is applied to a basic Gatsby app. We are showing how to configure the backend both as a [Vercel Function](https://vercel.com/docs/concepts/functions/introduction) for deployment to Vercel, and also for [Gatsby Functions](https://www.gatsbyjs.com/docs/reference/functions) for other platforms.
|
||||||
|
|
||||||
|
The deployed version can be found at [`next-auth-gatsby-example.vercel.app`](https://next-auth-gatsby-example.vercel.app)
|
||||||
|
|
||||||
|
### About NextAuth.js
|
||||||
|
|
||||||
|
NextAuth.js is an easy to implement, full-stack (client/server) open source authentication library originally designed for [Next.js](https://nextjs.org) and [Serverless](https://vercel.com), but this example shows how to use it in a Gatsby project. Our goal is to [support even more frameworks](https://github.com/nextauthjs/next-auth/issues/2294) in the future.
|
||||||
|
|
||||||
|
Go to [next-auth.js.org](https://next-auth.js.org) for more information and documentation.
|
||||||
|
|
||||||
|
> *NextAuth.js is not officially associated with Vercel or Next.js.*
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### 1. Clone the repository and install dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/nextauthjs/next-auth-gatsby-example.git
|
||||||
|
cd next-auth-gatsby-example
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure your local environment
|
||||||
|
|
||||||
|
Copy the .env.local.example file in this directory to .env.local (which will be ignored by Git):
|
||||||
|
|
||||||
|
```
|
||||||
|
cp .env.local.example .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
Add details for one or more providers (e.g. Google, Twitter, GitHub, Email, etc).
|
||||||
|
|
||||||
|
#### Database
|
||||||
|
|
||||||
|
A database is needed to persist user accounts and to support email sign in. However, you can still use NextAuth.js for authentication without a database by using OAuth for authentication. If you do not specify a database, [JSON Web Tokens](https://jwt.io/introduction) will be enabled by default.
|
||||||
|
|
||||||
|
You **can** skip configuring a database and come back to it later if you want.
|
||||||
|
|
||||||
|
For more information about setting up a database, please check out the following links:
|
||||||
|
|
||||||
|
* Docs: [next-auth.js.org/adapters/overview](https://next-auth.js.org/adapters/overview)
|
||||||
|
|
||||||
|
### 3. Configure Authentication Providers
|
||||||
|
|
||||||
|
1. Review and update options in `nextauth.config.js` as needed.
|
||||||
|
|
||||||
|
2. When setting up OAuth, in the developer admin page for each of your OAuth services, you should configure the callback URL to use a callback path of `{server}/api/auth/callback/{provider}`.
|
||||||
|
|
||||||
|
e.g. For Google OAuth you would use: `http://localhost:3000/api/auth/callback/google`
|
||||||
|
|
||||||
|
A list of configured providers and their callback URLs is available from the endpoint `/api/auth/providers`. You can find more information at https://next-auth.js.org/configuration/providers/oauth
|
||||||
|
|
||||||
|
3. You can also choose to specify an SMTP server for passwordless sign in via email.
|
||||||
|
|
||||||
|
### 4. Start the application
|
||||||
|
|
||||||
|
To run your site locally, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
To run it in production mode, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Preparing for Production
|
||||||
|
|
||||||
|
Follow the [Deployment documentation](https://next-auth.js.org/deployment)
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
<a href="https://vercel.com?utm_source=nextauthjs&utm_campaign=oss">
|
||||||
|
<img width="170px" src="https://raw.githubusercontent.com/nextauthjs/next-auth/canary/www/static/img/powered-by-vercel.svg" alt="Powered By Vercel" />
|
||||||
|
</a>
|
||||||
|
<p align="left">Thanks to Vercel sponsoring this project by allowing it to be deployed for free for the entire NextAuth.js Team</p>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
ISC
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Gatsby Functions are not yet supported on Vercel, so you'll need to use the root `api` folder.
|
// Gatsby Functions are not yet supported on Vercel, so you'll need to use the root `api` folder.
|
||||||
import NextAuth from "next-auth"
|
import NextAuth from "next-auth/next"
|
||||||
import { authConfig } from "../../nextauth.config"
|
import { authConfig } from "../../nextauth.config"
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// If your deployment environment supports Gatsby Functions, you won't need the root `api` folder, only this.
|
// If your deployment environment supports Gatsby Functions, you won't need the root `api` folder, only this.
|
||||||
|
|
||||||
import NextAuth from "next-auth"
|
import NextAuth from "next-auth/next"
|
||||||
import { authConfig } from "../../nextauth.config"
|
import { authConfig } from "../../nextauth.config"
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
@@ -11,7 +11,8 @@ export default function Home() {
|
|||||||
<h1>NextAuth.js Example</h1>
|
<h1>NextAuth.js Example</h1>
|
||||||
<p>
|
<p>
|
||||||
An example site to demonstrate how to use{" "}
|
An example site to demonstrate how to use{" "}
|
||||||
<a href="https://authjs.dev">Auth.js</a> for authentication in Gatsby.
|
<a href="https://next-auth.js.org">NextAuth.js</a> for authentication in
|
||||||
|
Gatsby.
|
||||||
</p>
|
</p>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -9,16 +9,16 @@
|
|||||||
</p>
|
</p>
|
||||||
<p align="center" style="align: center;">
|
<p align="center" style="align: center;">
|
||||||
<a href="https://npm.im/next-auth">
|
<a href="https://npm.im/next-auth">
|
||||||
<img alt="npm" src="https://img.shields.io/npm/v/next-auth?color=green&label=next-auth&style=flat-square">
|
<img alt="npm" src="https://img.shields.io/npm/v/next-auth?color=green&label=next-auth">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://bundlephobia.com/result?p=next-auth-example">
|
<a href="https://bundlephobia.com/result?p=next-auth-example">
|
||||||
<img src="https://img.shields.io/bundlephobia/minzip/next-auth?label=size&style=flat-square" alt="Bundle Size"/>
|
<img src="https://img.shields.io/bundlephobia/minzip/next-auth?label=next-auth" alt="Bundle Size"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.npmtrends.com/next-auth">
|
<a href="https://www.npmtrends.com/next-auth">
|
||||||
<img src="https://img.shields.io/npm/dm/next-auth?label=downloads&style=flat-square" alt="Downloads" />
|
<img src="https://img.shields.io/npm/dm/next-auth?label=next-auth%20downloads" alt="Downloads" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://npm.im/next-auth">
|
<a href="https://npm.im/next-auth">
|
||||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
<img src="https://img.shields.io/badge/npm-TypeScript-blue" alt="TypeScript" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
GITHUB_ID=
|
|
||||||
GITHUB_SECRET=
|
|
||||||
# On UNIX systems you can use `openssl rand -hex 32` or
|
|
||||||
# https://generate-secret.vercel.app/32 to generate a secret.
|
|
||||||
AUTH_SECRET=
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
12
apps/example-sveltekit/.gitignore
vendored
12
apps/example-sveltekit/.gitignore
vendored
@@ -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-*
|
|
||||||
@@ -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
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": false,
|
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
|
||||||
"pluginSearchDirs": ["."],
|
|
||||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/example-sveltekit). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<br/>
|
|
||||||
<a href="https://authjs.dev" target="_blank"><img width="150px" src="https://authjs.dev/img/logo/logo-sm.png" /></a>
|
|
||||||
<h3 align="center">Auth.js Example App with <a href="https://kit.svelte.dev">SvelteKit</a></h3>
|
|
||||||
<p align="center">
|
|
||||||
Open Source. Full Stack. Own Your Data.
|
|
||||||
</p>
|
|
||||||
<p align="center" style="align: center;">
|
|
||||||
<a href="https://npm.im/@auth/sveltekit">
|
|
||||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/sveltekit?color=green&label=@auth/sveltekit&style=flat-square">
|
|
||||||
</a>
|
|
||||||
<a href="https://bundlephobia.com/result?p=sveltekit-auth-example">
|
|
||||||
<img src="https://img.shields.io/bundlephobia/minzip/@auth/sveltekit?label=size&style=flat-square" alt="Bundle Size"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://www.npmtrends.com/@auth/sveltekit">
|
|
||||||
<img src="https://img.shields.io/npm/dm/@auth/sveltekit?label=%20downloads&style=flat-square" alt="Downloads" />
|
|
||||||
</a>
|
|
||||||
<a href="https://npm.im/next-auth">
|
|
||||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
|
|
||||||
- [sveltekit.authjs.dev](https://sveltekit.authjs.dev)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {
|
|
||||||
"@sveltejs/adapter-auto": "next",
|
|
||||||
"@sveltejs/kit": "next",
|
|
||||||
"svelte": "3.55.0",
|
|
||||||
"svelte-check": "2.10.2",
|
|
||||||
"typescript": "4.9.4",
|
|
||||||
"vite": "4.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@auth/core": "latest",
|
|
||||||
"@auth/sveltekit": "latest"
|
|
||||||
},
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
1
apps/example-sveltekit/src/app.d.ts
vendored
1
apps/example-sveltekit/src/app.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
/// <reference types="@auth/sveltekit" />
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
%sveltekit.head%
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div>%sveltekit.body%</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import SvelteKitAuth from "@auth/sveltekit"
|
|
||||||
import GitHub from "@auth/core/providers/github"
|
|
||||||
import { GITHUB_ID, GITHUB_SECRET } from "$env/static/private"
|
|
||||||
|
|
||||||
export const handle = SvelteKitAuth({
|
|
||||||
providers: [GitHub({ clientId: GITHUB_ID, clientSecret: GITHUB_SECRET })],
|
|
||||||
})
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let provider: any
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form action={provider.signinUrl} method="POST">
|
|
||||||
{#if provider.callbackUrl}
|
|
||||||
<input type="hidden" name="callbackUrl" value={provider.callbackUrl} />
|
|
||||||
{/if}
|
|
||||||
<button type="submit" class="button">
|
|
||||||
<slot>Sign in with {provider.name}</slot>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { LayoutServerLoad } from "./$types"
|
|
||||||
|
|
||||||
export const load: LayoutServerLoad = async (event) => {
|
|
||||||
return {
|
|
||||||
session: await event.locals.getSession(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { page } from "$app/stores"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<header>
|
|
||||||
<div class="signedInStatus">
|
|
||||||
<p class="nojs-show loaded">
|
|
||||||
{#if $page.data.session}
|
|
||||||
{#if $page.data.session.user?.image}
|
|
||||||
<span
|
|
||||||
style="background-image: url('{$page.data.session.user.image}')"
|
|
||||||
class="avatar"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<span class="signedInText">
|
|
||||||
<small>Signed in as</small><br />
|
|
||||||
<strong
|
|
||||||
>{$page.data.session.user?.email ??
|
|
||||||
$page.data.session.user?.name}</strong
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<a href="/auth/signout" class="button">Sign out</a>
|
|
||||||
{:else}
|
|
||||||
<span class="notSignedInText">You are not signed in</span>
|
|
||||||
<a href="/auth/signin" class="buttonPrimary">Sign in</a>
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<nav>
|
|
||||||
<ul class="navItems">
|
|
||||||
<li class="navItem"><a href="/">Home</a></li>
|
|
||||||
<li class="navItem"><a href="/protected">Protected</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:global(body) {
|
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
|
||||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
|
||||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
|
|
||||||
"Noto Color Emoji";
|
|
||||||
padding: 0 1rem 1rem 1rem;
|
|
||||||
max-width: 680px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: #fff;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
:global(li),
|
|
||||||
:global(p) {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
:global(a) {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
:global(hr) {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
:global(iframe) {
|
|
||||||
background: #ccc;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
height: 10rem;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nojs-show {
|
|
||||||
opacity: 1;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.signedInStatus {
|
|
||||||
display: block;
|
|
||||||
min-height: 4rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.loaded {
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
opacity: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 0 0 0.6rem 0.6rem;
|
|
||||||
padding: 0.6rem 1rem;
|
|
||||||
margin: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
transition: all 0.2s ease-in;
|
|
||||||
}
|
|
||||||
.signedInText,
|
|
||||||
.notSignedInText {
|
|
||||||
position: absolute;
|
|
||||||
padding-top: 0.8rem;
|
|
||||||
left: 1rem;
|
|
||||||
right: 6.5rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
display: inherit;
|
|
||||||
z-index: 1;
|
|
||||||
line-height: 1.3rem;
|
|
||||||
}
|
|
||||||
.signedInText {
|
|
||||||
padding-top: 0rem;
|
|
||||||
left: 4.6rem;
|
|
||||||
}
|
|
||||||
.avatar {
|
|
||||||
border-radius: 2rem;
|
|
||||||
float: left;
|
|
||||||
height: 2.8rem;
|
|
||||||
width: 2.8rem;
|
|
||||||
background-color: white;
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
.button,
|
|
||||||
.buttonPrimary {
|
|
||||||
float: right;
|
|
||||||
margin-right: -0.4rem;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.4rem;
|
|
||||||
padding: 0.7rem 0.8rem;
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
.buttonPrimary {
|
|
||||||
background-color: #346df1;
|
|
||||||
border-color: #346df1;
|
|
||||||
color: #fff;
|
|
||||||
text-decoration: none;
|
|
||||||
padding: 0.7rem 1.4rem;
|
|
||||||
}
|
|
||||||
.buttonPrimary:hover {
|
|
||||||
box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
.navItems {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
.navItem {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<h1>SvelteKit Auth Example</h1>
|
|
||||||
<p>
|
|
||||||
This is an example site to demonstrate how to use <a
|
|
||||||
href="https://kit.svelte.dev/">SvelteKit</a
|
|
||||||
>
|
|
||||||
with <a href="https://sveltekit.authjs.dev">SvelteKit Auth</a> for authentication.
|
|
||||||
</p>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { page } from "$app/stores"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Protected page</h1>
|
|
||||||
<p>
|
|
||||||
This is a protected content. You can access this content because you are
|
|
||||||
signed in.
|
|
||||||
</p>
|
|
||||||
<p>Session expiry: {$page.data.session?.expires}</p>
|
|
||||||
@@ -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 {}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,15 +0,0 @@
|
|||||||
import adapter from '@sveltejs/adapter-auto';
|
|
||||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
|
||||||
const config = {
|
|
||||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
|
||||||
// for more information about preprocessors
|
|
||||||
preprocess: vitePreprocess(),
|
|
||||||
|
|
||||||
kit: {
|
|
||||||
adapter: adapter()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./.svelte-kit/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"strict": true
|
|
||||||
}
|
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
|
||||||
//
|
|
||||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
|
||||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { sveltekit } from "@sveltejs/kit/vite"
|
|
||||||
|
|
||||||
/** @type {import('vite').UserConfig} */
|
|
||||||
const config = {
|
|
||||||
plugins: [sveltekit()],
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/playground-gatsby). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<br/>
|
|
||||||
<a href="https://authjs.dev" target="_blank"><img width="150px" src="https://authjs.dev/img/logo/logo-sm.png" /></a>
|
|
||||||
<h3 align="center">Auth.js Example App</h3>
|
|
||||||
<p align="center">
|
|
||||||
Open Source. Full Stack. Own Your Data.
|
|
||||||
</p>
|
|
||||||
<p align="center" style="align: center;">
|
|
||||||
<a href="https://npm.im/next-auth">
|
|
||||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/core?color=green&label=@auth/core&style=flat-square">
|
|
||||||
</a>
|
|
||||||
<a href="https://bundlephobia.com/result?p=@auth/core">
|
|
||||||
<img src="https://img.shields.io/bundlephobia/minzip/@auth/core?label=bundle&style=flat-square" alt="Bundle Size"/>
|
|
||||||
</a>
|
|
||||||
<a href="https://www.npmtrends.com/@auth/core">
|
|
||||||
<img src="https://img.shields.io/npm/dm/@auth/core?label=downloads&style=flat-square" alt="Downloads" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Auth.js is a complete open source authentication solution.
|
|
||||||
|
|
||||||
This is an example application that shows how `@auth/core` is applied to a basic Gatsby app. We are showing how to configure the backend both as a [Vercel Function](https://vercel.com/docs/concepts/functions/introduction) for deployment to Vercel, and also for [Gatsby Functions](https://www.gatsbyjs.com/docs/reference/functions) for other platforms.
|
|
||||||
|
|
||||||
The deployed version can be found at [`next-auth-gatsby-example.vercel.app`](https://next-auth-gatsby-example.vercel.app)
|
|
||||||
|
|
||||||
### About Auth.js
|
|
||||||
|
|
||||||
Auth.js is an easy to implement, full-stack (client/server) open source authentication library originally designed for [Next.js](https://nextjs.org) and [Serverless](https://vercel.com), but this example shows how to use it in a Gatsby project. Our goal is to [support even more frameworks](https://github.com/nextauthjs/next-auth/issues/2294) in the future.
|
|
||||||
|
|
||||||
Go to [authjs.dev](https://authjs.dev) for more information and documentation.
|
|
||||||
|
|
||||||
> Auth.js is not officially associated with Vercel or Next.js._
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### 1. Clone the repository and install dependencies
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/nextauthjs/next-auth-gatsby-example.git
|
|
||||||
cd next-auth-gatsby-example
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Configure your local environment
|
|
||||||
|
|
||||||
Copy the .env.local.example file in this directory to .env.local (which will be ignored by Git):
|
|
||||||
|
|
||||||
```
|
|
||||||
cp .env.local.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
Add details for one or more providers (e.g. Google, Twitter, GitHub, Email, etc).
|
|
||||||
|
|
||||||
#### Database
|
|
||||||
|
|
||||||
A database is needed to persist user accounts and to support email sign in. However, you can still use Auth.js for authentication without a database by using OAuth for authentication. If you do not specify a database, [JSON Web Tokens](https://jwt.io/introduction) will be enabled by default.
|
|
||||||
|
|
||||||
You **can** skip configuring a database and come back to it later if you want.
|
|
||||||
|
|
||||||
For more information about setting up a database, please check out the following links:
|
|
||||||
|
|
||||||
- Docs: [authjs.dev/reference/adapters/overview](https://authjs.dev/reference/adapters/overview)
|
|
||||||
|
|
||||||
### 3. Configure Authentication Providers
|
|
||||||
|
|
||||||
1. Review and update options in `nextauth.config.js` as needed.
|
|
||||||
|
|
||||||
2. When setting up OAuth, in the developer admin page for each of your OAuth services, you should configure the callback URL to use a callback path of `{server}/api/auth/callback/{provider}`.
|
|
||||||
|
|
||||||
e.g. For Google OAuth you would use: `http://localhost:3000/api/auth/callback/google`
|
|
||||||
|
|
||||||
A list of configured providers and their callback URLs is available from the endpoint `/api/auth/providers`. You can find more information at [authjs.dev/reference/providers/oauth-builtin](https://authjs.dev/reference/providers/oauth-builtin).
|
|
||||||
|
|
||||||
3. You can also choose to specify an SMTP server for passwordless sign in via email.
|
|
||||||
|
|
||||||
### 4. Start the application
|
|
||||||
|
|
||||||
To run your site locally, use:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
To run it in production mode, use:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run build
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Preparing for Production
|
|
||||||
|
|
||||||
Follow the [Deployment documentation](https://authjs.dev/guides/basics/deployment)
|
|
||||||
|
|
||||||
## Acknowledgements
|
|
||||||
|
|
||||||
<a href="https://vercel.com?utm_source=authjs&utm_campaign=oss">
|
|
||||||
<img width="170px" src="https://powered-by-vercel.api.soraharu.com/powered-by-vercel.svg" alt="Powered By Vercel" />
|
|
||||||
</a>
|
|
||||||
<p align="left">Thanks to Vercel sponsoring this project by allowing it to be deployed for free for the entire Auth.js Team</p>
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
ISC
|
|
||||||
12
apps/playground-nuxt/.editorconfig
Normal file
12
apps/playground-nuxt/.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
4
apps/playground-nuxt/.eslintignore
Normal file
4
apps/playground-nuxt/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
tsconfig.json
|
||||||
|
package.json
|
||||||
10
apps/playground-nuxt/.eslintrc
Normal file
10
apps/playground-nuxt/.eslintrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"@nuxtjs/eslint-config-typescript"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"off"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: ['@nuxt/eslint-config'],
|
|
||||||
rules: {
|
|
||||||
'vue/multi-word-component-names': 'off'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
52
apps/playground-nuxt/.gitignore
vendored
52
apps/playground-nuxt/.gitignore
vendored
@@ -1,4 +1,52 @@
|
|||||||
|
# Dependencies
|
||||||
node_modules
|
node_modules
|
||||||
.nuxt
|
|
||||||
|
# Logs
|
||||||
|
*.log*
|
||||||
|
|
||||||
|
# Temp directories
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Yarn
|
||||||
|
**/.yarn/cache
|
||||||
|
**/.yarn/*state*
|
||||||
|
|
||||||
|
# Generated dirs
|
||||||
dist
|
dist
|
||||||
output
|
|
||||||
|
# Nuxt
|
||||||
|
.nuxt
|
||||||
|
.output
|
||||||
|
.vercel_build_output
|
||||||
|
.build-*
|
||||||
|
.env
|
||||||
|
.netlify
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
reports
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Intellij idea
|
||||||
|
*.iml
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
.vercel
|
||||||
|
|||||||
1
apps/playground-nuxt/.nuxtrc
Normal file
1
apps/playground-nuxt/.nuxtrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
imports.autoImport=false
|
||||||
@@ -1,13 +1,21 @@
|
|||||||
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/playground-nuxt). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
|
# NextAuth + Nuxt 3 Playground
|
||||||
|
|
||||||
Nuxt 3 support with Auth.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 `@auth/nuxt`.
|
NextAuth.js is committed to bringing easy authentication to other frameworks. [#2294](https://github.com/nextauthjs/next-auth/issues/2294)
|
||||||
|
|
||||||
|
Nuxt 3 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/nuxt.`
|
||||||
|
|
||||||
|
This package uses Nuxt's [module starter](https://github.com/nuxt/starter/tree/module).
|
||||||
|
|
||||||
|
Demo: https://next-auth-nuxt-demo.vercel.app
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
1. Setup your environment variables in `nuxt.config.ts`:
|
### Add the module to the modules section of `nuxt.config.ts`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
|
// temporary module name.
|
||||||
|
modules: ['next-auth-nuxt'],
|
||||||
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
secret: process.env.NEXTAUTH_SECRET
|
secret: process.env.NEXTAUTH_SECRET
|
||||||
@@ -15,36 +23,86 @@ export default defineNuxtConfig({
|
|||||||
clientId: process.env.GITHUB_CLIENT_ID,
|
clientId: process.env.GITHUB_CLIENT_ID,
|
||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
|
||||||
|
// Fix for GithubProvider (or whichever provider you choose) is not a function error in Vite
|
||||||
|
alias: {
|
||||||
|
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Set up Auth.js options
|
### Add API route
|
||||||
|
|
||||||
Go to the API handler file (`server/api/auth/[...].ts`) and setup your providers. This file contains the dynamic route handler for Auth.js which will also contain all of your global Auth.js configurations.
|
To add `NextAuth.js` to a project create a file called `[...].ts` in `server/api/auth`. This contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations.
|
||||||
|
|
||||||
Here's an example of what it looks like:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// server/api/auth/[...].ts
|
// ~/server/api/auth/[...].ts
|
||||||
|
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
|
||||||
import { NuxtAuthHandler } from '@/lib/auth/server'
|
import GithubProvider from 'next-auth/providers/github'
|
||||||
import GithubProvider from '@auth/core/providers/github'
|
|
||||||
import type { AuthOptions } from '@auth/core'
|
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
|
||||||
export const authOptions: AuthOptions = {
|
export const authOptions = {
|
||||||
secret: runtimeConfig.secret,
|
secret: runtimeConfig.secret,
|
||||||
providers: [
|
providers: [
|
||||||
GithubProvider({
|
GithubProvider({
|
||||||
clientId: runtimeConfig.github.clientId,
|
clientId: runtimeConfig.github.clientId,
|
||||||
clientSecret: runtimeConfig.github.clientSecret
|
clientSecret: runtimeConfig.github.clientSecret
|
||||||
})
|
}),
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NuxtAuthHandler(authOptions)
|
export default NextAuthNuxtHandler(authOptions)
|
||||||
```
|
```
|
||||||
|
|
||||||
All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by Auth.js.
|
All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by NextAuth.js.
|
||||||
|
|
||||||
|
### Frontend - Add Vue Composable
|
||||||
|
|
||||||
|
The `useSession()` Vue Composable is the easiest way to check if someone is signed in.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data: session } = useSession()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="session">
|
||||||
|
Signed in as {{ session.user.email }} <br />
|
||||||
|
<button @click="signOut">Sign out</button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
Not signed in <br />
|
||||||
|
<button @click="signIn">Sign in</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend - API Route
|
||||||
|
|
||||||
|
To protect an API Route, you can use the `getServerSession()` method.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||||
|
import { authOptions } from '~/server/api/auth/[...]'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const session = await getServerSession(event, authOptions)
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
return {
|
||||||
|
content: 'This is protected content. You can access this content because you are signed in.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: 'You must be signed in to view the protected content on this page.'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- Run `pnpm dev:generate` to generate type stubs.
|
||||||
|
- Use `pnpm dev` to start `playground` in development mode.
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<Header />
|
|
||||||
<NuxtPage />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
||||||
padding: 0 1rem 1rem 1rem;
|
|
||||||
max-width: 680px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: #fff;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
li,
|
|
||||||
p {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
1
apps/playground-nuxt/client.d.ts
vendored
Normal file
1
apps/playground-nuxt/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dist/runtime/client'
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { signIn, signOut } from '@/lib/auth/client'
|
|
||||||
|
|
||||||
const session = useSession()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<header>
|
|
||||||
<div class="signedInStatus">
|
|
||||||
<p :class="['nojs-show', 'loaded']">
|
|
||||||
<template v-if="session">
|
|
||||||
<span v-if="session.user?.image" :style="{ backgroundImage: `url(${session.user.image})` }" class="avatar" />
|
|
||||||
<span class="signedInText">
|
|
||||||
<small>Signed in as</small><br>
|
|
||||||
<strong>{{ session.user?.email || session.user?.name }}</strong>
|
|
||||||
</span>
|
|
||||||
<a href="/api/auth/signout" class="button" @click.prevent="signOut">Sign out</a>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="notSignedInText">You are not signed in</span>
|
|
||||||
<a href="/api/auth/signin" class="buttonPrimary" @click.prevent="signIn">Sign in</a>
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<nav>
|
|
||||||
<ul class="navItems">
|
|
||||||
<li class="navItem">
|
|
||||||
<NuxtLink to="/">
|
|
||||||
Home
|
|
||||||
</NuxtLink>
|
|
||||||
</li>
|
|
||||||
<li class="navItem">
|
|
||||||
<NuxtLink to="/protected">
|
|
||||||
Protected
|
|
||||||
</NuxtLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.nojs-show {
|
|
||||||
opacity: 1;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signedInStatus {
|
|
||||||
display: block;
|
|
||||||
min-height: 4rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading,
|
|
||||||
.loaded {
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
opacity: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 0 0 .6rem .6rem;
|
|
||||||
padding: .6rem 1rem;
|
|
||||||
margin: 0;
|
|
||||||
background-color: rgba(0,0,0,.05);
|
|
||||||
transition: all 0.2s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
top: -2rem;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signedInText,
|
|
||||||
.notSignedInText {
|
|
||||||
position: absolute;
|
|
||||||
padding-top: .8rem;
|
|
||||||
left: 1rem;
|
|
||||||
right: 6.5rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
display: inherit;
|
|
||||||
z-index: 1;
|
|
||||||
line-height: 1.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signedInText {
|
|
||||||
padding-top: 0rem;
|
|
||||||
left: 4.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
border-radius: 2rem;
|
|
||||||
float: left;
|
|
||||||
height: 2.8rem;
|
|
||||||
width: 2.8rem;
|
|
||||||
background-color: white;
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button,
|
|
||||||
.buttonPrimary {
|
|
||||||
float: right;
|
|
||||||
margin-right: -.4rem;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: .3rem;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.4rem;
|
|
||||||
padding: .7rem .8rem;
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonPrimary {
|
|
||||||
background-color: #346df1;
|
|
||||||
border-color: #346df1;
|
|
||||||
color: #fff;
|
|
||||||
text-decoration: none;
|
|
||||||
padding: .7rem 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonPrimary:hover {
|
|
||||||
box-shadow: inset 0 0 5rem rgba(0,0,0,0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
.navItems {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navItem {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { Session } from '@auth/core'
|
|
||||||
|
|
||||||
export default function useSession() {
|
|
||||||
return useState<Session | null>('session', () => null)
|
|
||||||
}
|
|
||||||
1
apps/playground-nuxt/handler.d.ts
vendored
Normal file
1
apps/playground-nuxt/handler.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dist/runtime/server/handler'
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import type {
|
|
||||||
LiteralUnion,
|
|
||||||
SignInOptions,
|
|
||||||
SignInAuthorizationParams,
|
|
||||||
SignOutParams,
|
|
||||||
} from './types'
|
|
||||||
import type {
|
|
||||||
BuiltInProviderType,
|
|
||||||
RedirectableProviderType,
|
|
||||||
} from '@auth/core/providers'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client-side method to initiate a signin flow
|
|
||||||
* or send the user to the signin page listing all possible providers.
|
|
||||||
* Automatically adds the CSRF token to the request.
|
|
||||||
*
|
|
||||||
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
|
|
||||||
*/
|
|
||||||
export async function signIn<
|
|
||||||
P extends RedirectableProviderType | undefined = undefined
|
|
||||||
>(
|
|
||||||
providerId?: LiteralUnion<
|
|
||||||
P extends RedirectableProviderType
|
|
||||||
? P | BuiltInProviderType
|
|
||||||
: BuiltInProviderType
|
|
||||||
>,
|
|
||||||
options?: SignInOptions,
|
|
||||||
authorizationParams?: SignInAuthorizationParams
|
|
||||||
) {
|
|
||||||
const { callbackUrl = window.location.href, redirect = true } = options ?? {}
|
|
||||||
|
|
||||||
// TODO: Support custom providers
|
|
||||||
const isCredentials = providerId === "credentials"
|
|
||||||
const isEmail = providerId === "email"
|
|
||||||
const isSupportingReturn = isCredentials || isEmail
|
|
||||||
|
|
||||||
// TODO: Handle custom base path
|
|
||||||
const signInUrl = `/api/auth/${
|
|
||||||
isCredentials ? "callback" : "signin"
|
|
||||||
}/${providerId}`
|
|
||||||
|
|
||||||
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
|
|
||||||
|
|
||||||
// TODO: Handle custom base path
|
|
||||||
// TODO: Remove this since Sveltekit offers the CSRF protection via origin check
|
|
||||||
const { csrfToken } = await $fetch("/api/auth/csrf")
|
|
||||||
|
|
||||||
console.log(_signInUrl)
|
|
||||||
|
|
||||||
const res = await fetch(_signInUrl, {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
"X-Auth-Return-Redirect": "1",
|
|
||||||
},
|
|
||||||
// @ts-expect-error -- ignore
|
|
||||||
body: new URLSearchParams({
|
|
||||||
...options,
|
|
||||||
csrfToken,
|
|
||||||
callbackUrl,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
const data = await res.clone().json()
|
|
||||||
const error = new URL(data.url).searchParams.get("error")
|
|
||||||
|
|
||||||
if (redirect || !isSupportingReturn || !error) {
|
|
||||||
// TODO: Do not redirect for Credentials and Email providers by default in next major
|
|
||||||
window.location.href = data.url ?? callbackUrl
|
|
||||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
|
||||||
if (data.url.includes("#")) window.location.reload()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs the user out, by removing the session cookie.
|
|
||||||
* Automatically adds the CSRF token to the request.
|
|
||||||
*
|
|
||||||
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
|
|
||||||
*/
|
|
||||||
export async function signOut(options?: SignOutParams) {
|
|
||||||
const { callbackUrl = window.location.href } = options ?? {}
|
|
||||||
// TODO: Custom base path
|
|
||||||
// TODO: Remove this since Sveltekit offers the CSRF protection via origin check
|
|
||||||
const csrfTokenResponse = await fetch("/api/auth/csrf")
|
|
||||||
const { csrfToken } = await csrfTokenResponse.json()
|
|
||||||
const res = await fetch(`/api/auth/signout`, {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
"X-Auth-Return-Redirect": "1",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
csrfToken,
|
|
||||||
callbackUrl,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
const data = await res.json()
|
|
||||||
|
|
||||||
const url = data.url ?? callbackUrl
|
|
||||||
window.location.href = url
|
|
||||||
// If url contains a hash, the browser does not reload the page. We reload manually
|
|
||||||
if (url.includes("#")) window.location.reload()
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { AuthHandler, AuthOptions, Session } from '@auth/core'
|
|
||||||
import { fromNodeMiddleware, H3Event } from 'h3'
|
|
||||||
import getURL from 'requrl'
|
|
||||||
import { createMiddleware } from "@hattip/adapter-node";
|
|
||||||
|
|
||||||
export function NuxtAuthHandler (options: AuthOptions) {
|
|
||||||
async function handler(ctx: { request: Request }) {
|
|
||||||
options.trustHost ??= true
|
|
||||||
|
|
||||||
return AuthHandler(ctx.request, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
const middleware = createMiddleware(handler)
|
|
||||||
|
|
||||||
return fromNodeMiddleware(middleware)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSession(
|
|
||||||
event: H3Event,
|
|
||||||
options: AuthOptions
|
|
||||||
): Promise<Session | null> {
|
|
||||||
options.trustHost ??= true
|
|
||||||
|
|
||||||
const headers = getRequestHeaders(event)
|
|
||||||
const nodeHeaders = new Headers()
|
|
||||||
|
|
||||||
const url = new URL('/api/auth/session', getURL(event.node.req))
|
|
||||||
|
|
||||||
Object.keys(headers).forEach((key) => {
|
|
||||||
nodeHeaders.append(key, headers[key] as any)
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = await AuthHandler(
|
|
||||||
new Request(url, { headers: nodeHeaders }),
|
|
||||||
options
|
|
||||||
)
|
|
||||||
|
|
||||||
const { status = 200 } = response
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
if (!data || !Object.keys(data).length) return null
|
|
||||||
if (status === 200) return data
|
|
||||||
throw new Error(data.message)
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// Taken from next-auth/react
|
|
||||||
import type { BuiltInProviderType, ProviderType } from '@auth/core/providers'
|
|
||||||
/**
|
|
||||||
* Util type that matches some strings literally, but allows any other string as well.
|
|
||||||
* @source https://github.com/microsoft/TypeScript/issues/29729#issuecomment-832522611
|
|
||||||
*/
|
|
||||||
export declare type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>);
|
|
||||||
export interface ClientSafeProvider {
|
|
||||||
id: LiteralUnion<BuiltInProviderType>;
|
|
||||||
name: string;
|
|
||||||
type: ProviderType;
|
|
||||||
signinUrl: string;
|
|
||||||
callbackUrl: string;
|
|
||||||
}
|
|
||||||
export interface SignInOptions extends Record<string, unknown> {
|
|
||||||
/**
|
|
||||||
* Specify to which URL the user will be redirected after signing in. Defaults to the page URL the sign-in is initiated from.
|
|
||||||
*
|
|
||||||
* [Documentation](https://next-auth.js.org/getting-started/client#specifying-a-callbackurl)
|
|
||||||
*/
|
|
||||||
callbackUrl?: string;
|
|
||||||
/** [Documentation](https://next-auth.js.org/getting-started/client#using-the-redirect-false-option) */
|
|
||||||
redirect?: boolean;
|
|
||||||
}
|
|
||||||
export interface SignInResponse {
|
|
||||||
error: string | undefined;
|
|
||||||
status: number;
|
|
||||||
ok: boolean;
|
|
||||||
url: string | null;
|
|
||||||
}
|
|
||||||
/** Match `inputType` of `new URLSearchParams(inputType)` */
|
|
||||||
export declare type SignInAuthorizationParams = string | string[][] | Record<string, string> | URLSearchParams;
|
|
||||||
/** [Documentation](https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1) */
|
|
||||||
export interface SignOutResponse {
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
export interface SignOutParams<R extends boolean = true> {
|
|
||||||
/** [Documentation](https://next-auth.js.org/getting-started/client#specifying-a-callbackurl-1) */
|
|
||||||
callbackUrl?: string;
|
|
||||||
/** [Documentation](https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
|
|
||||||
redirect?: R;
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "playground-nuxt",
|
"name": "next-auth-nuxt",
|
||||||
"private": true,
|
"type": "module",
|
||||||
"scripts": {
|
"version": "0.0.0",
|
||||||
"build": "nuxt build",
|
"packageManager": "pnpm@7.1.1",
|
||||||
"dev": "export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
|
"license": "MIT",
|
||||||
"generate": "nuxt generate",
|
"main": "./dist/module.cjs",
|
||||||
"preview": "nuxt preview",
|
"types": "./dist/types.d.ts",
|
||||||
"postinstall": "nuxt prepare"
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/module.mjs",
|
||||||
|
"require": "./dist/module.cjs"
|
||||||
|
},
|
||||||
|
"./handler": {
|
||||||
|
"import": "./dist/runtime/server/handler.mjs",
|
||||||
|
"types": "./dist/runtime/server/handler.d.ts"
|
||||||
|
},
|
||||||
|
"./client": {
|
||||||
|
"import": "./dist/runtime/client/index.mjs",
|
||||||
|
"types": "./dist/runtime/client/index.d.ts"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"files": [
|
||||||
"@nuxt/eslint-config": "^0.1.1",
|
"dist",
|
||||||
"eslint": "^8.29.0",
|
"handler.d.ts",
|
||||||
"h3": "1.0.2",
|
"client.d.ts"
|
||||||
"nuxt": "3.0.0"
|
],
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "nuxt-module-build",
|
||||||
|
"dev": "pnpm prepack && nuxi dev playground",
|
||||||
|
"dev:build": "nuxi build playground",
|
||||||
|
"dev:build:vercel": "NITRO_PRESET=vercel nuxi build playground",
|
||||||
|
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/core": "workspace:*",
|
"@nuxt/kit": "^3.0.0-rc.13",
|
||||||
"@hattip/adapter-node": "^0.0.22",
|
"h3": "^0.8.6",
|
||||||
"requrl": "^3.0.2"
|
"next-auth": "^4.16.2",
|
||||||
|
"pathe": "^0.3.9"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nuxt/module-builder": "^0.2.0",
|
||||||
|
"@nuxt/schema": "^3.0.0-rc.12",
|
||||||
|
"@nuxtjs/eslint-config-typescript": "^11.0.0",
|
||||||
|
"eslint": "^8.26.0",
|
||||||
|
"nuxt": "^3.0.0-rc.13",
|
||||||
|
"next-auth-nuxt": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h1>Nuxt Auth Example</h1>
|
|
||||||
<p>
|
|
||||||
This is an example site to demonstrate how to use <a href="https://v3.nuxtjs.org/">Nuxt 3</a> with <a href="https://authjs.dev/">Auth.js</a> for authentication.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const session = useSession()
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: 'auth'
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h1>Protected Page</h1>
|
|
||||||
<p>
|
|
||||||
This is a protected content. You can access this content because you are
|
|
||||||
signed in.
|
|
||||||
</p>
|
|
||||||
<p>Session expiry: {{ session?.expires }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
40
apps/playground-nuxt/playground/app.vue
Normal file
40
apps/playground-nuxt/playground/app.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<NuxtPage />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
padding: 0 1rem 1rem 1rem;
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
li,
|
||||||
|
p {
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
background: #ccc;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
height: 10rem;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: .5rem;
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Access Denied</h1>
|
||||||
|
<p>
|
||||||
|
<a href="/api/auth/signin">You must be signed in to view this page</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
30
apps/playground-nuxt/playground/components/Footer.vue
Normal file
30
apps/playground-nuxt/playground/components/Footer.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<footer class="fotter">
|
||||||
|
<hr>
|
||||||
|
<ul class="navItems">
|
||||||
|
<li class="navItem">
|
||||||
|
<a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-nuxt">Demo GitHub</a>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<a href="https://next-auth.js.org">Next.js Documentation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.footer {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItems {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItem {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
155
apps/playground-nuxt/playground/components/Header.vue
Normal file
155
apps/playground-nuxt/playground/components/Header.vue
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSession, signIn, signOut, computed } from '#imports'
|
||||||
|
|
||||||
|
const { data: session, status } = useSession()
|
||||||
|
const loading = computed(() => status.value === 'loading')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div class="signedInStatus">
|
||||||
|
<p :class="['nojs-show', !session && loading ? 'loading' : 'loaded']">
|
||||||
|
<template v-if="session">
|
||||||
|
<span v-if="session.user?.image" :style="{ backgroundImage: `url(${session.user.image})` }" class="avatar" />
|
||||||
|
<span class="signedInText">
|
||||||
|
<small>Signed in as</small><br>
|
||||||
|
<strong>{{ session.user?.email || session.user?.name }}</strong>
|
||||||
|
</span>
|
||||||
|
<a href="/api/auth/signout" class="button" @click.prevent="signOut">Sign out</a>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="notSignedInText">You are not signed in</span>
|
||||||
|
<a href="/api/auth/signin" class="buttonPrimary" @click.prevent="signIn">Sign in</a>
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="navItems">
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/">
|
||||||
|
Home
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/client">
|
||||||
|
Client
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/server">
|
||||||
|
Server
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/protected">
|
||||||
|
Protected
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/api-example">
|
||||||
|
API
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.nojs-show {
|
||||||
|
opacity: 1;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signedInStatus {
|
||||||
|
display: block;
|
||||||
|
min-height: 4rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.loaded {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
opacity: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0 0 .6rem .6rem;
|
||||||
|
padding: .6rem 1rem;
|
||||||
|
margin: 0;
|
||||||
|
background-color: rgba(0,0,0,.05);
|
||||||
|
transition: all 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
top: -2rem;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signedInText,
|
||||||
|
.notSignedInText {
|
||||||
|
position: absolute;
|
||||||
|
padding-top: .8rem;
|
||||||
|
left: 1rem;
|
||||||
|
right: 6.5rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inherit;
|
||||||
|
z-index: 1;
|
||||||
|
line-height: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signedInText {
|
||||||
|
padding-top: 0rem;
|
||||||
|
left: 4.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
border-radius: 2rem;
|
||||||
|
float: left;
|
||||||
|
height: 2.8rem;
|
||||||
|
width: 2.8rem;
|
||||||
|
background-color: white;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.buttonPrimary {
|
||||||
|
float: right;
|
||||||
|
margin-right: -.4rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: .3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.4rem;
|
||||||
|
padding: .7rem .8rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonPrimary {
|
||||||
|
background-color: #346df1;
|
||||||
|
border-color: #346df1;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: .7rem 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonPrimary:hover {
|
||||||
|
box-shadow: inset 0 0 5rem rgba(0,0,0,0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItems {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItem {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
|
import MyModule from '../src/module'
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
|
modules: [
|
||||||
|
MyModule
|
||||||
|
],
|
||||||
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
@@ -7,11 +12,9 @@ export default defineNuxtConfig({
|
|||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
vite: {
|
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
|
||||||
define: {
|
// Fix for GithubProvider is not a function error in Vite
|
||||||
'process.env.NEXTAUTH_URL': JSON.stringify(process.env.NEXTAUTH_URL),
|
alias: {
|
||||||
'process.env.AUTH_TRUST_HOST': JSON.stringify(process.env.AUTH_TRUST_HOST),
|
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
|
||||||
'process.env.VERCEL_URL': JSON.stringify(process.env.VERCEL_URL),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
4
apps/playground-nuxt/playground/package.json
Normal file
4
apps/playground-nuxt/playground/package.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "playground",
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
15
apps/playground-nuxt/playground/pages/api-example.vue
Normal file
15
apps/playground-nuxt/playground/pages/api-example.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>API Example</h1>
|
||||||
|
<p>The examples below show responses from the example API endpoints.</p>
|
||||||
|
<p>
|
||||||
|
<em>You must be signed in to see responses.</em>
|
||||||
|
</p>
|
||||||
|
<h2>Session</h2>
|
||||||
|
<p>/api/examples/session</p>
|
||||||
|
<iframe src="/api/examples/session" />
|
||||||
|
<h2>JSON Web Token</h2>
|
||||||
|
<p>/api/examples/jwt</p>
|
||||||
|
<iframe src="/api/examples/jwt" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
18
apps/playground-nuxt/playground/pages/client.vue
Normal file
18
apps/playground-nuxt/playground/pages/client.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Client Side Rendering</h1>
|
||||||
|
<p>
|
||||||
|
This page uses the <strong>useSession()</strong> Vue Composable in the <strong><Header/></strong> component.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The <strong>useSession()</strong> Vue Composable is easy to use and allows pages to render very quickly.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The advantage of this approach is that session state is shared between pages by using a provided session via <strong>Vue Plugin</strong> so
|
||||||
|
that navigation between pages using <strong>useSession()</strong> is very fast.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The disadvantage of <strong>useSession()</strong> is that it requires client side JavaScript.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
8
apps/playground-nuxt/playground/pages/index.vue
Normal file
8
apps/playground-nuxt/playground/pages/index.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Nuxt 3 + NextAuth.js Example</h1>
|
||||||
|
<p>
|
||||||
|
This is an example site to demonstrate how to use <a href="https://v3.nuxtjs.org/">Nuxt 3</a> with <a href="https://next-auth.js.org">NextAuth.js</a> for authentication.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
19
apps/playground-nuxt/playground/pages/protected.vue
Normal file
19
apps/playground-nuxt/playground/pages/protected.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSession, useFetch, useLazyFetch } from '#imports'
|
||||||
|
import AccessDenied from '~/components/AccessDenied.vue'
|
||||||
|
|
||||||
|
const { data: session } = useSession()
|
||||||
|
const { data } = await useLazyFetch('/api/examples/protected', {
|
||||||
|
server: false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AccessDenied v-if="!session" />
|
||||||
|
<template v-else>
|
||||||
|
<h1>Protected Page</h1>
|
||||||
|
<p><strong>{{ data?.content || "\u00a0" }}</strong></p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
24
apps/playground-nuxt/playground/pages/server.vue
Normal file
24
apps/playground-nuxt/playground/pages/server.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useFetch } from '#imports'
|
||||||
|
|
||||||
|
await useFetch('/api/examples/session')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Server Side Rendering</h1>
|
||||||
|
<p>
|
||||||
|
This page uses the <strong>getServerSession()</strong> method inside an api route and is fetched using the <strong>useFetch()</strong> composable.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Using <strong>getServerSession()</strong> is the recommended approach if you need to
|
||||||
|
support Server Side Rendering with authentication.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The advantage of Server Side Rendering is this page does not require client side JavaScript.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The disadvantage of Server Side Rendering is that this page is slower to render.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
apps/playground-nuxt/playground/server/api/auth/[...].ts
Normal file
17
apps/playground-nuxt/playground/server/api/auth/[...].ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
|
||||||
|
import GithubProvider from 'next-auth/providers/github'
|
||||||
|
import type { NextAuthOptions } from 'next-auth'
|
||||||
|
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
secret: runtimeConfig.secret,
|
||||||
|
providers: [
|
||||||
|
GithubProvider({
|
||||||
|
clientId: runtimeConfig.github.clientId,
|
||||||
|
clientSecret: runtimeConfig.github.clientSecret
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAuthNuxtHandler(authOptions)
|
||||||
10
apps/playground-nuxt/playground/server/api/examples/jwt.ts
Normal file
10
apps/playground-nuxt/playground/server/api/examples/jwt.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { getToken } from 'next-auth/jwt'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
// @ts-expect-error: cookies property is not present in h3
|
||||||
|
event.req.cookies = parseCookies(event)
|
||||||
|
const token = await getToken({
|
||||||
|
req: event.req
|
||||||
|
})
|
||||||
|
return token
|
||||||
|
})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||||
|
import { authOptions } from '../auth/[...]'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const session = await getServerSession(event, authOptions)
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
return {
|
||||||
|
content: 'This is protected content. You can access this content because you are signed in.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: 'You must be signed in to view the protected content on this page.'
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||||
|
import { authOptions } from '../auth/[...]'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const session = await getServerSession(event, authOptions)
|
||||||
|
return session
|
||||||
|
})
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Session } from '@auth/core'
|
|
||||||
|
|
||||||
export default defineNuxtPlugin(async () => {
|
|
||||||
const session = useSession()
|
|
||||||
|
|
||||||
addRouteMiddleware('auth', () => {
|
|
||||||
if (!session.value) return navigateTo('/')
|
|
||||||
})
|
|
||||||
|
|
||||||
if (process.server) {
|
|
||||||
const data = await $fetch<Session>('/api/auth/session', {
|
|
||||||
headers: useRequestHeaders() as any
|
|
||||||
})
|
|
||||||
|
|
||||||
const hasSession = data && Object.keys(data).length
|
|
||||||
|
|
||||||
session.value = hasSession ? data : null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
6386
apps/playground-nuxt/pnpm-lock.yaml
generated
Normal file
6386
apps/playground-nuxt/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
apps/playground-nuxt/pnpm-workspace.yaml
Normal file
2
apps/playground-nuxt/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- playground
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { NuxtAuthHandler } from '@/lib/auth/server'
|
|
||||||
import GithubProvider from '@auth/core/providers/github'
|
|
||||||
import type { AuthOptions } from '@auth/core'
|
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig()
|
|
||||||
|
|
||||||
export const authOptions: AuthOptions = {
|
|
||||||
secret: runtimeConfig.secret,
|
|
||||||
providers: [
|
|
||||||
GithubProvider({
|
|
||||||
clientId: runtimeConfig.github.clientId,
|
|
||||||
clientSecret: runtimeConfig.github.clientSecret
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NuxtAuthHandler(authOptions)
|
|
||||||
40
apps/playground-nuxt/src/module.ts
Normal file
40
apps/playground-nuxt/src/module.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { addImports, addPlugin, defineNuxtModule, extendViteConfig } from '@nuxt/kit'
|
||||||
|
import { resolve } from 'pathe'
|
||||||
|
|
||||||
|
export interface ModuleOptions {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtModule<ModuleOptions>({
|
||||||
|
meta: {
|
||||||
|
name: 'next-auth-nuxt',
|
||||||
|
configKey: 'auth'
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
},
|
||||||
|
async setup (_options, nuxt) {
|
||||||
|
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
|
||||||
|
nuxt.options.build.transpile.push(runtimeDir)
|
||||||
|
|
||||||
|
addPlugin(resolve(runtimeDir, 'plugin.client'))
|
||||||
|
|
||||||
|
// Composables are auto-imported in client.
|
||||||
|
const client = resolve(runtimeDir, 'client')
|
||||||
|
await addImports([
|
||||||
|
{ name: 'getSession', from: client },
|
||||||
|
{ name: 'getCsrfToken', from: client },
|
||||||
|
{ name: 'getProviders', from: client },
|
||||||
|
{ name: 'signIn', from: client },
|
||||||
|
{ name: 'signOut', from: client },
|
||||||
|
{ name: 'useSession', from: client }
|
||||||
|
])
|
||||||
|
|
||||||
|
// We can safely expose this to client.
|
||||||
|
extendViteConfig((config) => {
|
||||||
|
config.define = config.define || {}
|
||||||
|
config.define['process.env.NEXTAUTH_URL'] = JSON.stringify(process.env.NEXTAUTH_URL)
|
||||||
|
config.define['process.env.NEXTAUTH_URL_INTERNAL'] = JSON.stringify(process.env.NEXTAUTH_URL_INTERNAL)
|
||||||
|
config.define['process.env.VERCEL_URL'] = JSON.stringify(process.env.VERCEL_URL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
369
apps/playground-nuxt/src/runtime/client/index.ts
Normal file
369
apps/playground-nuxt/src/runtime/client/index.ts
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
import type { NextAuthClientConfig } from 'next-auth/client/_utils'
|
||||||
|
import type { Plugin, Ref } from 'vue'
|
||||||
|
import { ref, reactive, computed, inject, toRefs } from 'vue'
|
||||||
|
import { BroadcastChannel, apiBaseUrl, fetchData, now } from 'next-auth/client/_utils'
|
||||||
|
import type { Session } from 'next-auth'
|
||||||
|
import type {
|
||||||
|
BuiltInProviderType,
|
||||||
|
RedirectableProviderType
|
||||||
|
} from 'next-auth/providers'
|
||||||
|
import type { H3EventContext } from 'h3'
|
||||||
|
import parseUrl from '../lib/parse-url'
|
||||||
|
import _logger, { proxyLogger } from '../lib/logger'
|
||||||
|
import type {
|
||||||
|
ClientSafeProvider,
|
||||||
|
LiteralUnion,
|
||||||
|
SessionProviderProps,
|
||||||
|
SignInAuthorizationParams,
|
||||||
|
SignInOptions,
|
||||||
|
SignInResponse,
|
||||||
|
SignOutParams,
|
||||||
|
SignOutResponse
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
// This behaviour mirrors the default behaviour for getting the site name that
|
||||||
|
// happens server side in server/index.js
|
||||||
|
// 1. An empty value is legitimate when the code is being invoked client side as
|
||||||
|
// relative URLs are valid in that context and so defaults to empty.
|
||||||
|
// 2. When invoked server side the value is picked up from an environment
|
||||||
|
// variable and defaults to 'http://localhost:3000'.
|
||||||
|
const __NEXTAUTH: NextAuthClientConfig = {
|
||||||
|
baseUrl: parseUrl(process.env.NEXTAUTH_URL ?? process.env.VERCEL_URL).origin,
|
||||||
|
basePath: parseUrl(process.env.NEXTAUTH_URL).path,
|
||||||
|
baseUrlServer: parseUrl(
|
||||||
|
process.env.NEXTAUTH_URL_INTERNAL ??
|
||||||
|
process.env.NEXTAUTH_URL ??
|
||||||
|
process.env.VERCEL_URL
|
||||||
|
).origin,
|
||||||
|
basePathServer: parseUrl(
|
||||||
|
process.env.NEXTAUTH_URL_INTERNAL ?? process.env.NEXTAUTH_URL
|
||||||
|
).path,
|
||||||
|
_lastSync: 0,
|
||||||
|
_session: undefined,
|
||||||
|
_getSession: () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CtxOrReq {
|
||||||
|
req?: H3EventContext['req']
|
||||||
|
event?: { req: H3EventContext['req'] }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetSessionParams = CtxOrReq & {
|
||||||
|
event?: 'storage' | 'timer' | 'hidden' | string
|
||||||
|
triggerEvent?: boolean
|
||||||
|
broadcast?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = proxyLogger(_logger, __NEXTAUTH.basePath)
|
||||||
|
|
||||||
|
const broadcast = BroadcastChannel()
|
||||||
|
|
||||||
|
function isServer () {
|
||||||
|
return (process as any).server
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSession (params?: GetSessionParams) {
|
||||||
|
const session = await fetchData<Session>(
|
||||||
|
'session',
|
||||||
|
__NEXTAUTH,
|
||||||
|
logger,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
if (params?.broadcast ?? true) { broadcast.post({ event: 'session', data: { trigger: 'getSession' } }) }
|
||||||
|
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current Cross Site Request Forgery Token (CSRF Token)
|
||||||
|
* required to make POST requests (e.g. for signing in and signing out).
|
||||||
|
* You likely only need to use this if you are not using the built-in
|
||||||
|
* `signIn()` and `signOut()` methods.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken)
|
||||||
|
*/
|
||||||
|
export async function getCsrfToken (params?: CtxOrReq) {
|
||||||
|
const response = await fetchData<{ csrfToken: string }>(
|
||||||
|
'csrf',
|
||||||
|
__NEXTAUTH,
|
||||||
|
logger,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
return response?.csrfToken
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It calls `/api/auth/providers` and returns
|
||||||
|
* a list of the currently configured authentication providers.
|
||||||
|
* It can be useful if you are creating a dynamic custom sign in page.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#getproviders)
|
||||||
|
*/
|
||||||
|
export async function getProviders () {
|
||||||
|
return await fetchData<
|
||||||
|
Record<LiteralUnion<BuiltInProviderType>, ClientSafeProvider>
|
||||||
|
>('providers', __NEXTAUTH, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client-side method to initiate a signin flow
|
||||||
|
* or send the user to the signin page listing all possible providers.
|
||||||
|
* Automatically adds the CSRF token to the request.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
|
||||||
|
*/
|
||||||
|
export async function signIn<
|
||||||
|
P extends RedirectableProviderType | undefined = undefined,
|
||||||
|
> (
|
||||||
|
provider?: LiteralUnion<BuiltInProviderType>,
|
||||||
|
options?: SignInOptions,
|
||||||
|
authorizationParams?: SignInAuthorizationParams
|
||||||
|
): Promise<
|
||||||
|
P extends RedirectableProviderType ? SignInResponse | undefined : undefined
|
||||||
|
> {
|
||||||
|
const { callbackUrl = window.location.href, redirect = true } = options ?? {}
|
||||||
|
|
||||||
|
const baseUrl = apiBaseUrl(__NEXTAUTH)
|
||||||
|
const providers = await getProviders()
|
||||||
|
|
||||||
|
if (!providers) {
|
||||||
|
window.location.href = `${baseUrl}/error`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider || !(provider in providers)) {
|
||||||
|
window.location.href = `${baseUrl}/signin?${new URLSearchParams({
|
||||||
|
callbackUrl
|
||||||
|
})}`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCredentials = providers[provider].type === 'credentials'
|
||||||
|
const isEmail = providers[provider].type === 'email'
|
||||||
|
const isSupportingReturn = isCredentials || isEmail
|
||||||
|
|
||||||
|
const signInUrl = `${baseUrl}/${
|
||||||
|
isCredentials ? 'callback' : 'signin'
|
||||||
|
}/${provider}`
|
||||||
|
|
||||||
|
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
|
||||||
|
|
||||||
|
const res = await fetch(_signInUrl, {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
// @ts-expect-error: Internal
|
||||||
|
body: new URLSearchParams({
|
||||||
|
...options,
|
||||||
|
csrfToken: await getCsrfToken(),
|
||||||
|
callbackUrl,
|
||||||
|
json: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
if (redirect || !isSupportingReturn) {
|
||||||
|
const url = data.url ?? callbackUrl
|
||||||
|
window.location.href = url
|
||||||
|
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||||
|
if (url.includes('#')) { window.location.reload() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = new URL(data.url).searchParams.get('error')
|
||||||
|
|
||||||
|
if (res.ok) { await __NEXTAUTH._getSession({ event: 'storage' }) }
|
||||||
|
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
status: res.status,
|
||||||
|
ok: res.ok,
|
||||||
|
url: error ? null : data.url
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the user out, by removing the session cookie.
|
||||||
|
* Automatically adds the CSRF token to the request.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
|
||||||
|
*/
|
||||||
|
export async function signOut<R extends boolean = true> (
|
||||||
|
options?: SignOutParams<R>
|
||||||
|
): Promise<R extends true ? undefined : SignOutResponse> {
|
||||||
|
const { callbackUrl = window.location.href } = options ?? {}
|
||||||
|
const baseUrl = apiBaseUrl(__NEXTAUTH)
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
// @ts-expect-error: Internal
|
||||||
|
body: new URLSearchParams({
|
||||||
|
csrfToken: await getCsrfToken(),
|
||||||
|
callbackUrl,
|
||||||
|
json: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const res = await fetch(`${baseUrl}/signout`, fetchOptions)
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
broadcast.post({ event: 'session', data: { trigger: 'signout' } })
|
||||||
|
|
||||||
|
if (options?.redirect ?? true) {
|
||||||
|
const url = data.url ?? callbackUrl
|
||||||
|
window.location.href = url
|
||||||
|
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||||
|
if (url.includes('#')) { window.location.reload() }
|
||||||
|
// @ts-expect-error: Internal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await __NEXTAUTH._getSession({ event: 'storage' })
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SessionProviderPlugin (options: SessionProviderProps): Plugin {
|
||||||
|
return {
|
||||||
|
install (app) {
|
||||||
|
const { basePath } = options
|
||||||
|
|
||||||
|
if (basePath) { __NEXTAUTH.basePath = basePath }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If session was `null`, there was an attempt to fetch it,
|
||||||
|
* but it failed, but we still treat it as a valid initial value.
|
||||||
|
*/
|
||||||
|
const hasInitialSession = options.session !== undefined
|
||||||
|
|
||||||
|
/** If session was passed, initialize as already synced */
|
||||||
|
__NEXTAUTH._lastSync = hasInitialSession ? now() : 0
|
||||||
|
|
||||||
|
if (hasInitialSession) { __NEXTAUTH._session = options.session }
|
||||||
|
|
||||||
|
const session = ref(options.session)
|
||||||
|
|
||||||
|
/** If session was passed, initialize as not loading */
|
||||||
|
const loading = ref(!hasInitialSession)
|
||||||
|
|
||||||
|
__NEXTAUTH._getSession = async ({ event } = {}) => {
|
||||||
|
try {
|
||||||
|
const storageEvent = event === 'storage'
|
||||||
|
// We should always update if we don't have a client session yet
|
||||||
|
// or if there are events from other tabs/windows
|
||||||
|
if (storageEvent || __NEXTAUTH._session === undefined) {
|
||||||
|
__NEXTAUTH._lastSync = now()
|
||||||
|
__NEXTAUTH._session = await getSession({
|
||||||
|
broadcast: !storageEvent
|
||||||
|
})
|
||||||
|
session.value = __NEXTAUTH._session
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
// If there is no time defined for when a session should be considered
|
||||||
|
// stale, then it's okay to use the value we have until an event is
|
||||||
|
// triggered which updates it
|
||||||
|
!event ||
|
||||||
|
// If the client doesn't have a session then we don't need to call
|
||||||
|
// the server to check if it does (if they have signed in via another
|
||||||
|
// tab or window that will come through as a "stroage" event
|
||||||
|
// event anyway)
|
||||||
|
__NEXTAUTH._session === null ||
|
||||||
|
// Bail out early if the client session is not stale yet
|
||||||
|
now() < __NEXTAUTH._lastSync
|
||||||
|
) { return }
|
||||||
|
|
||||||
|
// An event or session staleness occurred, update the client session.
|
||||||
|
__NEXTAUTH._lastSync = now()
|
||||||
|
__NEXTAUTH._session = await getSession()
|
||||||
|
session.value = __NEXTAUTH._session
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('CLIENT_SESSION_ERROR', error as Error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__NEXTAUTH._getSession()
|
||||||
|
|
||||||
|
const { refetchOnWindowFocus = true } = options
|
||||||
|
|
||||||
|
// Listen for when the page is visible, if the user switches tabs
|
||||||
|
// and makes our tab visible again, re-fetch the session, but only if
|
||||||
|
// this feature is not disabled.
|
||||||
|
const visibilityHandler = () => {
|
||||||
|
if (refetchOnWindowFocus && document.visibilityState === 'visible') { __NEXTAUTH._getSession({ event: 'visibilitychange' }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', visibilityHandler, false)
|
||||||
|
|
||||||
|
const unsubscribeFromBroadcast = broadcast.receive(() =>
|
||||||
|
__NEXTAUTH._getSession({ event: 'storage' })
|
||||||
|
)
|
||||||
|
|
||||||
|
const { refetchInterval } = options
|
||||||
|
let refetchIntervalTimer: NodeJS.Timer
|
||||||
|
|
||||||
|
if (refetchInterval) {
|
||||||
|
refetchIntervalTimer = setInterval(() => {
|
||||||
|
if (__NEXTAUTH._session) { __NEXTAUTH._getSession({ event: 'poll' }) }
|
||||||
|
}, refetchInterval * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalUnmount = app.unmount
|
||||||
|
app.unmount = function nextAuthUnmount () {
|
||||||
|
document.removeEventListener('visibilitychange', visibilityHandler, false)
|
||||||
|
unsubscribeFromBroadcast?.()
|
||||||
|
clearInterval(refetchIntervalTimer)
|
||||||
|
__NEXTAUTH._lastSync = 0
|
||||||
|
__NEXTAUTH._session = undefined
|
||||||
|
__NEXTAUTH._getSession = () => {}
|
||||||
|
originalUnmount()
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = computed(() => loading.value ? 'loading' : session.value ? 'authenticated' : 'unauthenticated')
|
||||||
|
const value = reactive({
|
||||||
|
data: session,
|
||||||
|
status
|
||||||
|
})
|
||||||
|
|
||||||
|
app.provide('SessionKey', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vue Composable that gives you access
|
||||||
|
* to the logged in user's session data.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#usesession)
|
||||||
|
*/
|
||||||
|
export function useSession (): {
|
||||||
|
data: Ref<SessionProviderProps['session']>;
|
||||||
|
status: Ref<string>;
|
||||||
|
} {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return {
|
||||||
|
data: ref(null),
|
||||||
|
status: ref('loading')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = inject<{
|
||||||
|
data: SessionProviderProps['session']
|
||||||
|
status: string
|
||||||
|
}>('SessionKey')
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Could not resolve provided session value')
|
||||||
|
}
|
||||||
|
const { data, status } = toRefs(value)
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
115
apps/playground-nuxt/src/runtime/lib/errors.ts
Normal file
115
apps/playground-nuxt/src/runtime/lib/errors.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import type { Adapter } from 'next-auth/adapters'
|
||||||
|
import type { EventCallbacks, LoggerInstance } from 'next-auth'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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 UnsupportedStrategy extends UnknownError {
|
||||||
|
name = 'UnsupportedStrategyError'
|
||||||
|
code = 'CALLBACK_CREDENTIALS_JWT_ERROR'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Method = (...args: any[]) => Promise<any>
|
||||||
|
|
||||||
|
export function upperSnake (s: string) {
|
||||||
|
return s.replace(/([A-Z])/g, '_$1').toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function capitalize (s: string) {
|
||||||
|
return `${s[0].toUpperCase()}${s.slice(1)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an object of methods and adds error handling.
|
||||||
|
*/
|
||||||
|
export function eventsErrorHandler (
|
||||||
|
methods: Partial<EventCallbacks>,
|
||||||
|
logger: LoggerInstance
|
||||||
|
): Partial<EventCallbacks> {
|
||||||
|
return Object.keys(methods).reduce<any>((acc, name) => {
|
||||||
|
acc[name] = async (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
const method: Method = methods[name as keyof Method]
|
||||||
|
return await method(...args)
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handles adapter induced errors. */
|
||||||
|
export function adapterErrorHandler (
|
||||||
|
adapter: Adapter | undefined,
|
||||||
|
logger: LoggerInstance
|
||||||
|
): Adapter | undefined {
|
||||||
|
if (!adapter) { return }
|
||||||
|
|
||||||
|
return Object.keys(adapter).reduce<any>((acc, name) => {
|
||||||
|
acc[name] = async (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
logger.debug(`adapter_${name}`, { args })
|
||||||
|
const method: Method = adapter[name as keyof Method]
|
||||||
|
return await method(...args)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`adapter_error_${name}`, error as Error)
|
||||||
|
const e = new UnknownError(error as Error)
|
||||||
|
e.name = `${capitalize(name)}Error`
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
113
apps/playground-nuxt/src/runtime/lib/logger.ts
Normal file
113
apps/playground-nuxt/src/runtime/lib/logger.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { UnknownError } from './errors'
|
||||||
|
|
||||||
|
// TODO: better typing
|
||||||
|
/** Makes sure that error is always serializable */
|
||||||
|
function formatError (o: unknown): unknown {
|
||||||
|
if (o instanceof Error && !(o instanceof UnknownError)) { return { message: o.message, stack: o.stack, name: o.name } }
|
||||||
|
|
||||||
|
if (hasErrorProperty(o)) {
|
||||||
|
o.error = formatError(o.error) as Error
|
||||||
|
o.message = o.message ?? o.error.message
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasErrorProperty (
|
||||||
|
x: unknown
|
||||||
|
): x is { error: Error; [key: string]: unknown } {
|
||||||
|
return !!(x as any)?.error
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WarningCode =
|
||||||
|
| 'NEXTAUTH_URL'
|
||||||
|
| 'NO_SECRET'
|
||||||
|
| 'TWITTER_OAUTH_2_BETA'
|
||||||
|
| 'DEBUG_ENABLED'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override any of the methods, and the rest will use the default logger.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/configuration/options#logger)
|
||||||
|
*/
|
||||||
|
export interface LoggerInstance extends Record<string, Function> {
|
||||||
|
warn: (code: WarningCode) => void
|
||||||
|
error: (
|
||||||
|
code: string,
|
||||||
|
/**
|
||||||
|
* Either an instance of (JSON serializable) Error
|
||||||
|
* or an object that contains some debug information.
|
||||||
|
* (Error is still available through `metadata.error`)
|
||||||
|
*/
|
||||||
|
metadata: Error | { error: Error; [key: string]: unknown }
|
||||||
|
) => void
|
||||||
|
debug: (code: string, metadata: unknown) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const _logger: LoggerInstance = {
|
||||||
|
error (code, metadata) {
|
||||||
|
metadata = formatError(metadata) as Error
|
||||||
|
console.error(
|
||||||
|
`[next-auth][error][${code}]`,
|
||||||
|
`\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`,
|
||||||
|
metadata.message,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
},
|
||||||
|
warn (code) {
|
||||||
|
console.warn(
|
||||||
|
`[next-auth][warn][${code}]`,
|
||||||
|
`\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
debug (code, metadata) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`[next-auth][debug][${code}]`, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the built-in logger with user's implementation.
|
||||||
|
* Any `undefined` level will use the default logger.
|
||||||
|
*/
|
||||||
|
export function setLogger (
|
||||||
|
newLogger: Partial<LoggerInstance> = {},
|
||||||
|
debug?: boolean
|
||||||
|
) {
|
||||||
|
// Turn off debug logging if `debug` isn't set to `true`
|
||||||
|
if (!debug) { _logger.debug = () => {} }
|
||||||
|
|
||||||
|
if (newLogger.error) { _logger.error = newLogger.error }
|
||||||
|
if (newLogger.warn) { _logger.warn = newLogger.warn }
|
||||||
|
if (newLogger.debug) { _logger.debug = newLogger.debug }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default _logger
|
||||||
|
|
||||||
|
/** Serializes client-side log messages and sends them to the server */
|
||||||
|
export function proxyLogger (
|
||||||
|
logger: LoggerInstance = _logger,
|
||||||
|
basePath?: string
|
||||||
|
): LoggerInstance {
|
||||||
|
try {
|
||||||
|
if (typeof window === 'undefined') { return logger }
|
||||||
|
|
||||||
|
const clientLogger: Record<string, unknown> = {}
|
||||||
|
for (const level in logger) {
|
||||||
|
clientLogger[level] = (code: string, metadata: Error) => {
|
||||||
|
_logger[level](code, metadata) // Logs to console
|
||||||
|
|
||||||
|
if (level === 'error') {
|
||||||
|
metadata = formatError(metadata) as Error
|
||||||
|
}(metadata as any).client = true
|
||||||
|
const url = `${basePath}/_log`
|
||||||
|
const body = new URLSearchParams({ level, code, ...(metadata as any) })
|
||||||
|
if (navigator.sendBeacon) { return navigator.sendBeacon(url, body) }
|
||||||
|
|
||||||
|
return fetch(url, { method: 'POST', body, keepalive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clientLogger as unknown as LoggerInstance
|
||||||
|
} catch {
|
||||||
|
return _logger
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
interface InternalUrl {
|
export interface InternalUrl {
|
||||||
/** @default "http://localhost:3000" */
|
/** @default "http://localhost:3000" */
|
||||||
origin: string
|
origin: string
|
||||||
/** @default "localhost:3000" */
|
/** @default "localhost:3000" */
|
||||||
@@ -12,17 +12,15 @@ interface InternalUrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns an `URL` like object to make requests/redirects from server-side */
|
/** Returns an `URL` like object to make requests/redirects from server-side */
|
||||||
export default function parseUrl(url?: string | URL): InternalUrl {
|
export default function parseUrl (url?: string): InternalUrl {
|
||||||
const defaultUrl = new URL("http://localhost:3000/api/auth")
|
const defaultUrl = new URL('http://localhost:3000/api/auth')
|
||||||
|
|
||||||
if (url && !url.toString().startsWith("http")) {
|
if (url && !url.startsWith('http')) { url = `https://${url}` }
|
||||||
url = `https://${url}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const _url = new URL(url ?? defaultUrl)
|
const _url = new URL(url ?? defaultUrl)
|
||||||
const path = (_url.pathname === "/" ? defaultUrl.pathname : _url.pathname)
|
const path = (_url.pathname === '/' ? defaultUrl.pathname : _url.pathname)
|
||||||
// Remove trailing slash
|
// Remove trailing slash
|
||||||
.replace(/\/$/, "")
|
.replace(/\/$/, '')
|
||||||
|
|
||||||
const base = `${_url.origin}${path}`
|
const base = `${_url.origin}${path}`
|
||||||
|
|
||||||
@@ -31,6 +29,6 @@ export default function parseUrl(url?: string | URL): InternalUrl {
|
|||||||
host: _url.host,
|
host: _url.host,
|
||||||
path,
|
path,
|
||||||
base,
|
base,
|
||||||
toString: () => base,
|
toString: () => base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
7
apps/playground-nuxt/src/runtime/plugin.client.ts
Normal file
7
apps/playground-nuxt/src/runtime/plugin.client.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// @ts-expect-error: Nuxt auto-import
|
||||||
|
import { defineNuxtPlugin } from '#app'
|
||||||
|
import { SessionProviderPlugin } from './client'
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.vueApp.use(SessionProviderPlugin({}))
|
||||||
|
})
|
||||||
93
apps/playground-nuxt/src/runtime/server/handler.ts
Normal file
93
apps/playground-nuxt/src/runtime/server/handler.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import type { NextAuthAction, NextAuthOptions, Session } from 'next-auth'
|
||||||
|
import type { RequestInternal } from 'next-auth/core'
|
||||||
|
import { NextAuthHandler } from 'next-auth/core'
|
||||||
|
import {
|
||||||
|
appendHeader,
|
||||||
|
defineEventHandler,
|
||||||
|
isMethod,
|
||||||
|
sendRedirect,
|
||||||
|
setCookie,
|
||||||
|
readBody,
|
||||||
|
parseCookies,
|
||||||
|
getQuery
|
||||||
|
} from 'h3'
|
||||||
|
import type { H3Event } from 'h3'
|
||||||
|
|
||||||
|
export function NextAuthNuxtHandler (options: NextAuthOptions) {
|
||||||
|
return defineEventHandler(async (event) => {
|
||||||
|
// Catch-all route params in Nuxt goes to the underscore property
|
||||||
|
const nextauth = event.context.params._.split('/')
|
||||||
|
|
||||||
|
const req: RequestInternal | Request = {
|
||||||
|
host: process.env.NEXTAUTH_URL,
|
||||||
|
body: undefined,
|
||||||
|
query: getQuery(event),
|
||||||
|
headers: event.req.headers,
|
||||||
|
method: event.req.method,
|
||||||
|
cookies: parseCookies(event),
|
||||||
|
action: nextauth[0] as NextAuthAction,
|
||||||
|
providerId: nextauth[1],
|
||||||
|
error: nextauth[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMethod(event, 'POST')) {
|
||||||
|
req.body = await readBody(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await NextAuthHandler({
|
||||||
|
req,
|
||||||
|
options
|
||||||
|
})
|
||||||
|
|
||||||
|
const { headers, cookies, body, redirect, status = 200 } = response
|
||||||
|
event.res.statusCode = status
|
||||||
|
|
||||||
|
headers?.forEach((header) => {
|
||||||
|
appendHeader(event, header.key, header.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
cookies?.forEach((cookie) => {
|
||||||
|
setCookie(event, cookie.name, cookie.value, cookie.options)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
if (isMethod(event, 'POST')) {
|
||||||
|
const body = await readBody(event)
|
||||||
|
if (body?.json !== 'true') { await sendRedirect(event, redirect, 302) }
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: redirect
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await sendRedirect(event, redirect, 302)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return body
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerSession (
|
||||||
|
event: H3Event,
|
||||||
|
options: NextAuthOptions
|
||||||
|
): Promise<Session | null> {
|
||||||
|
options.secret = process.env.NEXTAUTH_SECRET
|
||||||
|
|
||||||
|
const session = await NextAuthHandler<Session>({
|
||||||
|
req: {
|
||||||
|
host: process.env.NEXTAUTH_URL,
|
||||||
|
action: 'session',
|
||||||
|
method: 'GET',
|
||||||
|
cookies: parseCookies(event),
|
||||||
|
headers: event.req.headers
|
||||||
|
},
|
||||||
|
options
|
||||||
|
})
|
||||||
|
|
||||||
|
const { body } = session
|
||||||
|
|
||||||
|
if (body && Object.keys(body).length) {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
78
apps/playground-nuxt/src/runtime/types.ts
Normal file
78
apps/playground-nuxt/src/runtime/types.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import type { Session } from 'next-auth'
|
||||||
|
import type { BuiltInProviderType, ProviderType } from 'next-auth/providers'
|
||||||
|
|
||||||
|
export interface UseSessionOptions<R extends boolean> {
|
||||||
|
required: R
|
||||||
|
/** Defaults to `signIn` */
|
||||||
|
onUnauthenticated?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util type that matches some strings literally, but allows any other string as well.
|
||||||
|
* @source https://github.com/microsoft/TypeScript/issues/29729#issuecomment-832522611
|
||||||
|
*/
|
||||||
|
export type LiteralUnion<T extends U, U = string> =
|
||||||
|
| T
|
||||||
|
| (U & Record<never, never>)
|
||||||
|
|
||||||
|
export interface ClientSafeProvider {
|
||||||
|
id: LiteralUnion<BuiltInProviderType>
|
||||||
|
name: string
|
||||||
|
type: ProviderType
|
||||||
|
signinUrl: string
|
||||||
|
callbackUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignInOptions extends Record<string, unknown> {
|
||||||
|
/**
|
||||||
|
* Defaults to the current URL.
|
||||||
|
* @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl
|
||||||
|
*/
|
||||||
|
callbackUrl?: string
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option */
|
||||||
|
redirect?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignInResponse {
|
||||||
|
error: string | undefined
|
||||||
|
status: number
|
||||||
|
ok: boolean
|
||||||
|
url: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Match `inputType` of `new URLSearchParams(inputType)` */
|
||||||
|
export type SignInAuthorizationParams =
|
||||||
|
| string
|
||||||
|
| string[][]
|
||||||
|
| Record<string, string>
|
||||||
|
| URLSearchParams
|
||||||
|
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
|
||||||
|
export interface SignOutResponse {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignOutParams<R extends boolean = true> {
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl-1 */
|
||||||
|
callbackUrl?: string
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
|
||||||
|
redirect?: R
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @docs: https://next-auth.js.org/getting-started/client#options */
|
||||||
|
export interface SessionProviderProps {
|
||||||
|
// children: React.ReactNode
|
||||||
|
session?: Session | null
|
||||||
|
baseUrl?: string
|
||||||
|
basePath?: string
|
||||||
|
/**
|
||||||
|
* A time interval (in seconds) after which the session will be re-fetched.
|
||||||
|
* If set to `0` (default), the session is not polled.
|
||||||
|
*/
|
||||||
|
refetchInterval?: number
|
||||||
|
/**
|
||||||
|
* `SessionProvider` automatically refetches the session when the user switches between windows.
|
||||||
|
* This option activates this behaviour if set to `true` (default).
|
||||||
|
*/
|
||||||
|
refetchOnWindowFocus?: boolean
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user