mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
91 Commits
patch-2
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5da5a5f33 | ||
|
|
3b85f46c21 | ||
|
|
6aa1af2ffb | ||
|
|
9364625681 | ||
|
|
1bf2e1d468 | ||
|
|
ac304f17da | ||
|
|
3a685b28f8 | ||
|
|
5b34b95c58 | ||
|
|
a706105205 | ||
|
|
39c78f27b5 | ||
|
|
b179f15cf3 | ||
|
|
54561a1231 | ||
|
|
0ea9ada3a9 | ||
|
|
209c368a73 | ||
|
|
52c5b7cad3 | ||
|
|
b8d83f52b3 | ||
|
|
7dacfbabf0 | ||
|
|
bb372cc5cc | ||
|
|
c6ca01a99e | ||
|
|
6c45abf383 | ||
|
|
2ba5314e35 | ||
|
|
582a3c339a | ||
|
|
ea23a93442 | ||
|
|
1d67ad41cc | ||
|
|
7c50b3da98 | ||
|
|
1b2c373fa1 | ||
|
|
f7275c7527 | ||
|
|
e699ff14b8 | ||
|
|
6eab7ac25f | ||
|
|
9b05dbc540 | ||
|
|
132a76d951 | ||
|
|
66cbb522d9 | ||
|
|
553924d902 | ||
|
|
cba81f0b8c | ||
|
|
b7171ab790 | ||
|
|
43c8f663c6 | ||
|
|
b16b048991 | ||
|
|
62a5d70f9b | ||
|
|
1b671ae83d | ||
|
|
cc4b9fc2fc | ||
|
|
4935166372 | ||
|
|
695f937dbd | ||
|
|
ad9eec3676 | ||
|
|
2e924edcdf | ||
|
|
c7627778eb | ||
|
|
8b5644453b | ||
|
|
84291d3e81 | ||
|
|
67e5c236f6 | ||
|
|
8972defa4b | ||
|
|
85667dd681 | ||
|
|
d9532745eb | ||
|
|
1e6daa8304 | ||
|
|
70a3e3f662 | ||
|
|
875f79d11e | ||
|
|
6cfe502ae0 | ||
|
|
91c6b05ed8 | ||
|
|
45a18930c8 | ||
|
|
6f22a49c7d | ||
|
|
fea30069c9 | ||
|
|
cd01707530 | ||
|
|
d9a2df3a3d | ||
|
|
f4a1ed1eb7 | ||
|
|
a97737cc18 | ||
|
|
b44d1a005e | ||
|
|
2c077e1491 | ||
|
|
19804661d2 | ||
|
|
b7f1e3e7f8 | ||
|
|
7757024d79 | ||
|
|
3f15dc67e1 | ||
|
|
5359694b8f | ||
|
|
66686fa5fc | ||
|
|
1d6330b719 | ||
|
|
0eb20d1097 | ||
|
|
ac30402c6a | ||
|
|
caa6c6ae42 | ||
|
|
a6ac48314e | ||
|
|
f8675bc245 | ||
|
|
3d4842dcc9 | ||
|
|
7d7d1b2f80 | ||
|
|
9a4f3db7b0 | ||
|
|
6aad07a95c | ||
|
|
cfed5b976f | ||
|
|
d34108091f | ||
|
|
7bf79b89a8 | ||
|
|
4cd688703a | ||
|
|
57b176840e | ||
|
|
6298d955df | ||
|
|
2ad1cb3f8c | ||
|
|
98707282eb | ||
|
|
f4a2430891 | ||
|
|
575bcb5710 |
70
.eslintrc.js
70
.eslintrc.js
@@ -1,20 +1,21 @@
|
|||||||
|
// @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"),
|
||||||
@@ -23,19 +24,44 @@ 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"],
|
||||||
env: {
|
ignorePatterns: [
|
||||||
"jest/globals": true,
|
"**/dist/**",
|
||||||
},
|
"**/node_modules/**",
|
||||||
ignorePatterns: [".eslintrc.js"],
|
".eslintrc.js",
|
||||||
|
"**/.turbo/**",
|
||||||
|
"**/coverage/**",
|
||||||
|
"**/build/**",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ 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 {{
|
||||||
@@ -73,7 +70,7 @@ async function run() {
|
|||||||
}),
|
}),
|
||||||
client.issues.createComment({
|
client.issues.createComment({
|
||||||
...issueCommon,
|
...issueCommon,
|
||||||
body: readFileSync(join(__dirname, "repro.md"), "utf8"),
|
body: readFileSync("repro.md", "utf8"),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
return core.info(
|
return core.info(
|
||||||
|
|||||||
8
.github/sync.yml
vendored
8
.github/sync.yml
vendored
@@ -1,12 +1,14 @@
|
|||||||
nextauthjs/next-auth-example:
|
# Note that nextauthjs/next-auth-example syncs from the v4 branch
|
||||||
- source: apps/example-nextjs
|
|
||||||
|
nextauthjs/sveltekit-auth-example:
|
||||||
|
- source: apps/example-sveltekit
|
||||||
dest: .
|
dest: .
|
||||||
deleteOrphaned: true
|
deleteOrphaned: true
|
||||||
- .github/FUNDING.yml
|
- .github/FUNDING.yml
|
||||||
- LICENSE
|
- LICENSE
|
||||||
|
|
||||||
nextauthjs/next-auth-gatsby-example:
|
nextauthjs/next-auth-gatsby-example:
|
||||||
- source: apps/example-gatsby
|
- 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 ./.github/actions/issue-validator/index.mjs
|
run: node /home/runner/work/next-auth/next-auth/.github/actions/issue-validator/index.mjs
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -29,8 +29,6 @@ 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:
|
||||||
@@ -64,15 +62,11 @@ 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: |
|
run: pnpm release
|
||||||
git config --global user.email "balazsorban44@users.noreply.github.com"
|
|
||||||
git config --global user.name "Balázs Orbán"
|
|
||||||
pnpm release
|
|
||||||
env:
|
env:
|
||||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
|
NO_VERIFY: 1
|
||||||
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
|
||||||
@@ -100,19 +94,20 @@ jobs:
|
|||||||
PR_NUMBER: ${{ github.event.number }}
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
- name: Publish to npm
|
- name: Publish to npm
|
||||||
run: |
|
run: |
|
||||||
cd packages/next-auth
|
cd packages/core
|
||||||
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_PKG }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
- 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/next-auth/v/${{ env.VERSION }})!\n \
|
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/@auth/core/v/${{ env.VERSION }})!\n \
|
||||||
```sh\npnpm add next-auth@${{ env.VERSION }}\n```\n \
|
```sh\npnpm add @auth/core@${{ env.VERSION }}\n```\n \
|
||||||
```sh\nyarn add next-auth@${{ env.VERSION }}\n```\n \
|
```sh\nyarn add @auth/core@${{ env.VERSION }}\n```\n \
|
||||||
```sh\nnpm i next-auth@${{ env.VERSION }}\n```"
|
```sh\nnpm i @auth/core@${{ env.VERSION }}\n```"
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ steps.determine-version.outputs.version }}
|
VERSION: ${{ steps.determine-version.outputs.version }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/sync-examples.yml
vendored
1
.github/workflows/sync-examples.yml
vendored
@@ -16,3 +16,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
GH_PAT: ${{ secrets.SYNC_EXAMPLE_PAT }}
|
GH_PAT: ${{ secrets.SYNC_EXAMPLE_PAT }}
|
||||||
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
|
.vscode/generated*
|
||||||
|
|
||||||
# Jetbrains
|
# Jetbrains
|
||||||
.idea
|
.idea
|
||||||
@@ -81,11 +81,13 @@ docs/.docusaurus
|
|||||||
docs/providers.json
|
docs/providers.json
|
||||||
|
|
||||||
# Core
|
# Core
|
||||||
packages/core/adapters.*
|
packages/core/*.js
|
||||||
packages/core/index.*
|
packages/core/*.d.ts
|
||||||
packages/core/jwt
|
packages/core/*.d.ts.map
|
||||||
packages/core/lib
|
packages/core/lib
|
||||||
packages/core/providers
|
packages/core/providers
|
||||||
|
docs/docs/reference/03-core
|
||||||
|
docs/docs/reference/04-sveltekit
|
||||||
|
|
||||||
|
|
||||||
# SvelteKit
|
# SvelteKit
|
||||||
|
|||||||
16
.prettierrc.js
Normal file
16
.prettierrc.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// @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
Normal file
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"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
Normal file
18
.vscode/snippets.code-snippets
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"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) 2018-2021, Iain Collins
|
Copyright (c) 2022-2023, Balázs Orbán
|
||||||
|
|
||||||
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
Normal file
4
apps/dev/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"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://next-auth.js.org">Documentation</a>
|
<a href="https://authjs.dev">Documentation</a>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<a href="https://www.npmjs.com/package/next-auth">NPM</a>
|
<a href="https://www.npmjs.com/package/@auth/core">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,7 +6,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AuthHandler, type AuthOptions } from "@auth/core"
|
import { Auth, type AuthConfig } from "@auth/core"
|
||||||
|
|
||||||
// Providers
|
// Providers
|
||||||
import Apple from "@auth/core/providers/apple"
|
import Apple from "@auth/core/providers/apple"
|
||||||
@@ -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 authOptions: AuthOptions = {
|
export const authConfig: AuthConfig = {
|
||||||
// adapter,
|
// adapter,
|
||||||
// debug: process.env.NODE_ENV !== "production",
|
// debug: process.env.NODE_ENV !== "production",
|
||||||
theme: {
|
theme: {
|
||||||
@@ -118,9 +118,10 @@ export const authOptions: AuthOptions = {
|
|||||||
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 (authOptions.adapter) {
|
if (authConfig.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,25 +131,21 @@ if (authOptions.adapter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to next-auth/edge
|
// TODO: move to next-auth/edge
|
||||||
function Auth(...args: any[]) {
|
function AuthHandler(...args: any[]) {
|
||||||
const envSecret = process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET
|
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")
|
const envTrustHost = !!(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL ?? process.env.NODE_ENV !== "production")
|
||||||
if (args.length === 1) {
|
if (args.length === 1) {
|
||||||
return async (req: Request) => {
|
return async (req: Request) => {
|
||||||
args[0].secret ??= envSecret
|
args[0].secret ??= envSecret
|
||||||
args[0].trustHost ??= envTrustHost
|
args[0].trustHost ??= envTrustHost
|
||||||
return await AuthHandler(req, args[0])
|
return Auth(req, args[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args[1].secret ??= envSecret
|
args[1].secret ??= envSecret
|
||||||
args[1].trustHost ??= envTrustHost
|
args[1].trustHost ??= envTrustHost
|
||||||
return AuthHandler(args[0], args[1])
|
return Auth(args[0], args[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// export default Auth(authOptions)
|
export default AuthHandler(authConfig)
|
||||||
|
|
||||||
export default function handle(request: Request) {
|
|
||||||
return Auth(request, authOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const config = { runtime: "experimental-edge" }
|
export const config = { runtime: "experimental-edge" }
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ 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 <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
|
This is an example site to demonstrate how to use{' '}
|
||||||
|
<a href="https://authjs.dev">NextAuth.js</a> for authentication.
|
||||||
</p>
|
</p>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
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 <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
|
This is an example site to demonstrate how to use{" "}
|
||||||
|
<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 OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
SOFTWARE.
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 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 every ~2 hours.
|
This site uses JSON Web Tokens and an in-memory database which resets
|
||||||
|
every ~2 hours.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Data provided to this site is exclusively used to support signing in
|
Data provided to this site is exclusively used to support signing in and
|
||||||
and is not passed to any third party services, other than via SMTP or OAuth for the
|
is not passed to any third party services, other than via SMTP or OAuth
|
||||||
purposes of authentication.
|
for the purposes of authentication.
|
||||||
</p>
|
</p>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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(
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
> 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
|
|
||||||
|
|
||||||
@@ -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">
|
<img alt="npm" src="https://img.shields.io/npm/v/next-auth?color=green&label=next-auth&style=flat-square">
|
||||||
</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=next-auth" alt="Bundle Size"/>
|
<img src="https://img.shields.io/bundlephobia/minzip/next-auth?label=size&style=flat-square" 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=next-auth%20downloads" alt="Downloads" />
|
<img src="https://img.shields.io/npm/dm/next-auth?label=downloads&style=flat-square" 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/npm-TypeScript-blue" alt="TypeScript" />
|
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
5
apps/example-sveltekit/.env.example
Normal file
5
apps/example-sveltekit/.env.example
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
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=
|
||||||
6
apps/example-sveltekit/.prettierrc
Normal file
6
apps/example-sveltekit/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"pluginSearchDirs": ["."],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
||||||
28
apps/example-sveltekit/README.md
Normal file
28
apps/example-sveltekit/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
> 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)
|
||||||
22
apps/example-sveltekit/package.json
Normal file
22
apps/example-sveltekit/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"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
Normal file
1
apps/example-sveltekit/src/app.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="@auth/sveltekit" />
|
||||||
13
apps/example-sveltekit/src/app.html
Normal file
13
apps/example-sveltekit/src/app.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!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>
|
||||||
7
apps/example-sveltekit/src/hooks.server.ts
Normal file
7
apps/example-sveltekit/src/hooks.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
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 })],
|
||||||
|
})
|
||||||
12
apps/example-sveltekit/src/lib/SignInButton.svelte
Normal file
12
apps/example-sveltekit/src/lib/SignInButton.svelte
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<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>
|
||||||
7
apps/example-sveltekit/src/routes/+layout.server.ts
Normal file
7
apps/example-sveltekit/src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { LayoutServerLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = async (event) => {
|
||||||
|
return {
|
||||||
|
session: await event.locals.getSession(),
|
||||||
|
}
|
||||||
|
}
|
||||||
151
apps/example-sveltekit/src/routes/+layout.svelte
Normal file
151
apps/example-sveltekit/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<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>
|
||||||
7
apps/example-sveltekit/src/routes/+page.svelte
Normal file
7
apps/example-sveltekit/src/routes/+page.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<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>
|
||||||
@@ -7,4 +7,4 @@
|
|||||||
This is a protected content. You can access this content because you are
|
This is a protected content. You can access this content because you are
|
||||||
signed in.
|
signed in.
|
||||||
</p>
|
</p>
|
||||||
<p>Session expiry: {$page.data.session.expires}</p>
|
<p>Session expiry: {$page.data.session?.expires}</p>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -14,4 +14,4 @@
|
|||||||
//
|
//
|
||||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
// 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
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
}
|
}
|
||||||
8
apps/example-sveltekit/vite.config.js
Normal file
8
apps/example-sveltekit/vite.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { sveltekit } from "@sveltejs/kit/vite"
|
||||||
|
|
||||||
|
/** @type {import('vite').UserConfig} */
|
||||||
|
const config = {
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
109
apps/playground-gatsby/README.md
Normal file
109
apps/playground-gatsby/README.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
> 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
|
||||||
@@ -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/next"
|
import NextAuth from "next-auth"
|
||||||
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/next"
|
import NextAuth from "next-auth"
|
||||||
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,8 +11,7 @@ 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://next-auth.js.org">NextAuth.js</a> for authentication in
|
<a href="https://authjs.dev">Auth.js</a> for authentication in Gatsby.
|
||||||
Gatsby.
|
|
||||||
</p>
|
</p>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,12 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
dist
|
|
||||||
node_modules
|
|
||||||
tsconfig.json
|
|
||||||
package.json
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"@nuxtjs/eslint-config-typescript"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
apps/playground-nuxt/.eslintrc.cjs
Normal file
7
apps/playground-nuxt/.eslintrc.cjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
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,52 +1,4 @@
|
|||||||
# Dependencies
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Logs
|
|
||||||
*.log*
|
|
||||||
|
|
||||||
# Temp directories
|
|
||||||
.temp
|
|
||||||
.tmp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Yarn
|
|
||||||
**/.yarn/cache
|
|
||||||
**/.yarn/*state*
|
|
||||||
|
|
||||||
# Generated dirs
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Nuxt
|
|
||||||
.nuxt
|
.nuxt
|
||||||
.output
|
dist
|
||||||
.vercel_build_output
|
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 +0,0 @@
|
|||||||
imports.autoImport=false
|
|
||||||
@@ -1,21 +1,13 @@
|
|||||||
# NextAuth + Nuxt 3 Playground
|
> 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.js is committed to bringing easy authentication to other frameworks. [#2294](https://github.com/nextauthjs/next-auth/issues/2294)
|
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`.
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
### Add the module to the modules section of `nuxt.config.ts`:
|
1. Setup your environment variables in `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
|
||||||
@@ -23,86 +15,36 @@ 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'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add API route
|
2. Set up Auth.js options
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
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 GithubProvider from 'next-auth/providers/github'
|
import { NuxtAuthHandler } from '@/lib/auth/server'
|
||||||
|
import GithubProvider from '@auth/core/providers/github'
|
||||||
|
import type { AuthOptions } from '@auth/core'
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
|
||||||
export const authOptions = {
|
export const authOptions: 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 NextAuthNuxtHandler(authOptions)
|
export default NuxtAuthHandler(authOptions)
|
||||||
```
|
```
|
||||||
|
|
||||||
All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by NextAuth.js.
|
All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by Auth.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.
|
|
||||||
|
|||||||
30
apps/playground-nuxt/app.vue
Normal file
30
apps/playground-nuxt/app.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<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
1
apps/playground-nuxt/client.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export * from './dist/runtime/client'
|
|
||||||
139
apps/playground-nuxt/components/Header.vue
Normal file
139
apps/playground-nuxt/components/Header.vue
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<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>
|
||||||
5
apps/playground-nuxt/composables/useSession.ts
Normal file
5
apps/playground-nuxt/composables/useSession.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { Session } from '@auth/core'
|
||||||
|
|
||||||
|
export default function useSession() {
|
||||||
|
return useState<Session | null>('session', () => null)
|
||||||
|
}
|
||||||
1
apps/playground-nuxt/handler.d.ts
vendored
1
apps/playground-nuxt/handler.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export * from './dist/runtime/server/handler'
|
|
||||||
107
apps/playground-nuxt/lib/auth/client.ts
Normal file
107
apps/playground-nuxt/lib/auth/client.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
45
apps/playground-nuxt/lib/auth/server.ts
Normal file
45
apps/playground-nuxt/lib/auth/server.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
42
apps/playground-nuxt/lib/auth/types.ts
Normal file
42
apps/playground-nuxt/lib/auth/types.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// 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,9 +1,4 @@
|
|||||||
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,
|
||||||
@@ -12,9 +7,11 @@ export default defineNuxtConfig({
|
|||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
|
vite: {
|
||||||
// Fix for GithubProvider is not a function error in Vite
|
define: {
|
||||||
alias: {
|
'process.env.NEXTAUTH_URL': JSON.stringify(process.env.NEXTAUTH_URL),
|
||||||
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
|
'process.env.AUTH_TRUST_HOST': JSON.stringify(process.env.AUTH_TRUST_HOST),
|
||||||
|
'process.env.VERCEL_URL': JSON.stringify(process.env.VERCEL_URL),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1,49 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "next-auth-nuxt",
|
"name": "playground-nuxt",
|
||||||
"type": "module",
|
"private": true,
|
||||||
"version": "0.0.0",
|
|
||||||
"packageManager": "pnpm@7.1.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "./dist/module.cjs",
|
|
||||||
"types": "./dist/types.d.ts",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"handler.d.ts",
|
|
||||||
"client.d.ts"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepack": "nuxt-module-build",
|
"build": "nuxt build",
|
||||||
"dev": "pnpm prepack && nuxi dev playground",
|
"dev": "export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
|
||||||
"dev:build": "nuxi build playground",
|
"generate": "nuxt generate",
|
||||||
"dev:build:vercel": "NITRO_PRESET=vercel nuxi build playground",
|
"preview": "nuxt preview",
|
||||||
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@nuxt/kit": "^3.0.0-rc.13",
|
|
||||||
"h3": "^0.8.6",
|
|
||||||
"next-auth": "^4.16.2",
|
|
||||||
"pathe": "^0.3.9"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/module-builder": "^0.2.0",
|
"@nuxt/eslint-config": "^0.1.1",
|
||||||
"@nuxt/schema": "^3.0.0-rc.12",
|
"eslint": "^8.29.0",
|
||||||
"@nuxtjs/eslint-config-typescript": "^11.0.0",
|
"h3": "1.0.2",
|
||||||
"eslint": "^8.26.0",
|
"nuxt": "3.0.0"
|
||||||
"nuxt": "^3.0.0-rc.13",
|
},
|
||||||
"next-auth-nuxt": "workspace:*"
|
"dependencies": {
|
||||||
|
"@auth/core": "workspace:*",
|
||||||
|
"@hattip/adapter-node": "^0.0.22",
|
||||||
|
"requrl": "^3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
apps/playground-nuxt/pages/index.vue
Normal file
8
apps/playground-nuxt/pages/index.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
18
apps/playground-nuxt/pages/protected.vue
Normal file
18
apps/playground-nuxt/pages/protected.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<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>
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
<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 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "playground",
|
|
||||||
"private": true
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
})
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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.'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { getServerSession } from 'next-auth-nuxt/handler'
|
|
||||||
import { authOptions } from '../auth/[...]'
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const session = await getServerSession(event, authOptions)
|
|
||||||
return session
|
|
||||||
})
|
|
||||||
19
apps/playground-nuxt/plugins/auth.ts
Normal file
19
apps/playground-nuxt/plugins/auth.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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
6386
apps/playground-nuxt/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
|||||||
packages:
|
|
||||||
- playground
|
|
||||||
17
apps/playground-nuxt/server/api/auth/[...].ts
Normal file
17
apps/playground-nuxt/server/api/auth/[...].ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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)
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,369 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
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
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
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,34 +0,0 @@
|
|||||||
export interface InternalUrl {
|
|
||||||
/** @default "http://localhost:3000" */
|
|
||||||
origin: string
|
|
||||||
/** @default "localhost:3000" */
|
|
||||||
host: string
|
|
||||||
/** @default "/api/auth" */
|
|
||||||
path: string
|
|
||||||
/** @default "http://localhost:3000/api/auth" */
|
|
||||||
base: string
|
|
||||||
/** @default "http://localhost:3000/api/auth" */
|
|
||||||
toString: () => string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns an `URL` like object to make requests/redirects from server-side */
|
|
||||||
export default function parseUrl (url?: string): InternalUrl {
|
|
||||||
const defaultUrl = new URL('http://localhost:3000/api/auth')
|
|
||||||
|
|
||||||
if (url && !url.startsWith('http')) { url = `https://${url}` }
|
|
||||||
|
|
||||||
const _url = new URL(url ?? defaultUrl)
|
|
||||||
const path = (_url.pathname === '/' ? defaultUrl.pathname : _url.pathname)
|
|
||||||
// Remove trailing slash
|
|
||||||
.replace(/\/$/, '')
|
|
||||||
|
|
||||||
const base = `${_url.origin}${path}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
origin: _url.origin,
|
|
||||||
host: _url.host,
|
|
||||||
path,
|
|
||||||
base,
|
|
||||||
toString: () => base
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// @ts-expect-error: Nuxt auto-import
|
|
||||||
import { defineNuxtPlugin } from '#app'
|
|
||||||
import { SessionProviderPlugin } from './client'
|
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
nuxtApp.vueApp.use(SessionProviderPlugin({}))
|
|
||||||
})
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
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