mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
189 Commits
@next-auth
...
@next-auth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2a07932b9 | ||
|
|
25c7ce1d2b | ||
|
|
227a233bd8 | ||
|
|
cf9f133aa3 | ||
|
|
2301c1be44 | ||
|
|
6e408e24bf | ||
|
|
f277989c69 | ||
|
|
6146e93288 | ||
|
|
1ff565da6c | ||
|
|
41f75cf870 | ||
|
|
dd591ed8d0 | ||
|
|
297bc2317f | ||
|
|
b170138e70 | ||
|
|
a307079e0f | ||
|
|
d52b7a6b7d | ||
|
|
30b69a07eb | ||
|
|
0d1757814f | ||
|
|
068f9b50b8 | ||
|
|
dac490b7a1 | ||
|
|
c0f51669e2 | ||
|
|
cbf8ce3510 | ||
|
|
1c19cc71df | ||
|
|
9d962a0056 | ||
|
|
8387c78e3f | ||
|
|
58e30a6af6 | ||
|
|
b7ff987baf | ||
|
|
782812a52b | ||
|
|
32f2a0cea3 | ||
|
|
3343ef18b2 | ||
|
|
6280fe9e10 | ||
|
|
52a2bf3e28 | ||
|
|
180c6252d9 | ||
|
|
362e981e6d | ||
|
|
5198eb19f7 | ||
|
|
0210cfccf3 | ||
|
|
e90925bea0 | ||
|
|
27a0b70d87 | ||
|
|
c676e93d8a | ||
|
|
f498e9cd0a | ||
|
|
2f3396d376 | ||
|
|
e62f879ebd | ||
|
|
f67959eb04 | ||
|
|
060953dacf | ||
|
|
30ad639d16 | ||
|
|
777da4302d | ||
|
|
733fd5f234 | ||
|
|
a787efc6be | ||
|
|
261968b9bb | ||
|
|
4dbbe5b2d9 | ||
|
|
d9df582fa8 | ||
|
|
af840b2106 | ||
|
|
ba89907d5a | ||
|
|
08eaeba79f | ||
|
|
c31eabfcc6 | ||
|
|
4423673424 | ||
|
|
281d0948b9 | ||
|
|
5246183c55 | ||
|
|
cb56cd44ca | ||
|
|
6758e1c6d1 | ||
|
|
462cca1087 | ||
|
|
ab48fcfe5b | ||
|
|
fe7aaeded8 | ||
|
|
c53c09ea5c | ||
|
|
4bcba45294 | ||
|
|
eb5a9bad9d | ||
|
|
9a6d95c17c | ||
|
|
5b2fc7b570 | ||
|
|
6f459225fa | ||
|
|
f38ee19a8a | ||
|
|
38a03ed7d8 | ||
|
|
e1eb684cc6 | ||
|
|
777b7b2f23 | ||
|
|
6132c3fa75 | ||
|
|
94beef77e6 | ||
|
|
490d59dd17 | ||
|
|
26a8c5fc6d | ||
|
|
e26ec74720 | ||
|
|
d13997e140 | ||
|
|
d6efda077d | ||
|
|
0a4b99de3b | ||
|
|
2d2dfecc9d | ||
|
|
2a2c3d7a45 | ||
|
|
82786ac440 | ||
|
|
dfe3e02132 | ||
|
|
92b38ed740 | ||
|
|
97feae7916 | ||
|
|
24945895e9 | ||
|
|
6deccf610f | ||
|
|
f770b90219 | ||
|
|
87f4786917 | ||
|
|
191ef06471 | ||
|
|
75e6d8f0aa | ||
|
|
17999edd30 | ||
|
|
54b1845e58 | ||
|
|
879faf9fab | ||
|
|
3e3c36891e | ||
|
|
ac5d8a9795 | ||
|
|
965c6267e2 | ||
|
|
bfc429d20b | ||
|
|
2d8e910a19 | ||
|
|
d16e04848e | ||
|
|
ff3a52895b | ||
|
|
e6e03e8842 | ||
|
|
715aad9474 | ||
|
|
902bf92a85 | ||
|
|
44f2a47e6e | ||
|
|
a3b92dbaec | ||
|
|
bdd3ab2816 | ||
|
|
ba55f06585 | ||
|
|
d2b877fb28 | ||
|
|
658b22d9fb | ||
|
|
a0beb02f77 | ||
|
|
5727c5f4e6 | ||
|
|
8104cb1287 | ||
|
|
44aaa6f1c3 | ||
|
|
ba20974b5f | ||
|
|
14b4ed1d8a | ||
|
|
6b3a82d1f5 | ||
|
|
600aaaa7e6 | ||
|
|
f1d3bc26f9 | ||
|
|
78664aab37 | ||
|
|
aeb3a44b27 | ||
|
|
d3571e01ba | ||
|
|
3b7c9886c3 | ||
|
|
39fec738c6 | ||
|
|
fa58143c6b | ||
|
|
26fb89e3c4 | ||
|
|
a82cbf5ddf | ||
|
|
24db833685 | ||
|
|
c57a810042 | ||
|
|
d980fa986b | ||
|
|
4676352ae0 | ||
|
|
c8780122b3 | ||
|
|
3131971e2c | ||
|
|
448ec1017a | ||
|
|
0e9404ebc1 | ||
|
|
d349ae2b1b | ||
|
|
32f4d5000e | ||
|
|
7f2dbfc65b | ||
|
|
a03657e615 | ||
|
|
3e312d0df1 | ||
|
|
d9167bbffe | ||
|
|
526a6c1adc | ||
|
|
ad03a4efc1 | ||
|
|
424af6cbc5 | ||
|
|
a3c6786f78 | ||
|
|
385037ab33 | ||
|
|
26a03da621 | ||
|
|
afb1fcdae3 | ||
|
|
a21db8950f | ||
|
|
e8371ab23a | ||
|
|
9cdeb2ce7d | ||
|
|
89829d8a88 | ||
|
|
aedabc8d3f | ||
|
|
9f2cdad457 | ||
|
|
b107ca4946 | ||
|
|
6590993fdc | ||
|
|
0ea96796b2 | ||
|
|
8ec940bd6a | ||
|
|
e3bcdf83f1 | ||
|
|
4084297334 | ||
|
|
c9827960b1 | ||
|
|
946a825865 | ||
|
|
c57d8c997e | ||
|
|
e2b92bf04f | ||
|
|
8bff050e4e | ||
|
|
1a79a1a612 | ||
|
|
b7065a602f | ||
|
|
61b92ec1b6 | ||
|
|
282f7ab340 | ||
|
|
4f56e414b0 | ||
|
|
2725d07eb7 | ||
|
|
5a8b029523 | ||
|
|
f62a985848 | ||
|
|
edd6fb5989 | ||
|
|
fb60554a62 | ||
|
|
9784dfb631 | ||
|
|
4ff836a8cf | ||
|
|
042955eaaa | ||
|
|
82e107c0e7 | ||
|
|
f7050347e8 | ||
|
|
c56abbd745 | ||
|
|
3f6d99e8df | ||
|
|
46eedee3c8 | ||
|
|
bb664a27da | ||
|
|
a14fbea0b5 | ||
|
|
4705632c6b | ||
|
|
2296471f02 | ||
|
|
8853000fd5 |
40
.eslintrc.js
Normal file
40
.eslintrc.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const path = require("path")
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
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: {
|
||||||
|
project: [
|
||||||
|
path.resolve(__dirname, "./packages/**/tsconfig.eslint.json"),
|
||||||
|
path.resolve(__dirname, "./apps/**/tsconfig.json"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extends: ["prettier"],
|
||||||
|
globals: {
|
||||||
|
localStorage: "readonly",
|
||||||
|
location: "readonly",
|
||||||
|
fetch: "readonly",
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
camelcase: "off",
|
||||||
|
},
|
||||||
|
plugins: ["jest"],
|
||||||
|
env: {
|
||||||
|
"jest/globals": true,
|
||||||
|
},
|
||||||
|
ignorePatterns: [".eslintrc.js"],
|
||||||
|
}
|
||||||
15
.github/CODEOWNERS
vendored
15
.github/CODEOWNERS
vendored
@@ -1,4 +1,11 @@
|
|||||||
/types/ @balazsorban44 @lluia
|
# Learn how to add code owners here:
|
||||||
/docs/ @balazsorban44 @ndom91
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
/adapters/ @balazsorban44 @ndom91
|
|
||||||
/__tests__/ @lluia
|
* @balazsorban44
|
||||||
|
.github @ThangHuuVu
|
||||||
|
/apps/ @lluia @ndom91 @ThangHuuVu
|
||||||
|
/docs/ @lluia @ndom91
|
||||||
|
/packages/ @ThangHuuVu
|
||||||
|
/packages/adapter-*/ @ndom91
|
||||||
|
/**/*test* @lluia
|
||||||
|
/**/*type* @lluia
|
||||||
1
.github/ISSUE_TEMPLATE/1_bug_framework.yml
vendored
1
.github/ISSUE_TEMPLATE/1_bug_framework.yml
vendored
@@ -5,6 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
|
||||||
Thanks for taking the time to fill out this issue after reading/searching through the [documentation](https://next-auth.js.org) first!
|
Thanks for taking the time to fill out this issue after reading/searching through the [documentation](https://next-auth.js.org) first!
|
||||||
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
|
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
|
||||||
|
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/2_bug_provider.yml
vendored
2
.github/ISSUE_TEMPLATE/2_bug_provider.yml
vendored
@@ -5,6 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
|
||||||
Thanks for taking the time to fill out this [Provider](https://next-auth.js.org/providers/overview) related issue!
|
Thanks for taking the time to fill out this [Provider](https://next-auth.js.org/providers/overview) related issue!
|
||||||
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
|
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ body:
|
|||||||
- "Slack"
|
- "Slack"
|
||||||
- "Spotify"
|
- "Spotify"
|
||||||
- "Strava"
|
- "Strava"
|
||||||
|
- "Todoist"
|
||||||
- "Trakt"
|
- "Trakt"
|
||||||
- "Twitch"
|
- "Twitch"
|
||||||
- "Twitter"
|
- "Twitter"
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
3
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -5,6 +5,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
|
||||||
Thanks for taking the time to fill out this [Adapter](https://next-auth.js.org/adapters/overview) related issue!
|
Thanks for taking the time to fill out this [Adapter](https://next-auth.js.org/adapters/overview) related issue!
|
||||||
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
|
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
|
||||||
|
|
||||||
@@ -30,8 +31,10 @@ body:
|
|||||||
- "@next-auth/pouchdb-adapter"
|
- "@next-auth/pouchdb-adapter"
|
||||||
- "@next-auth/prisma-adapter"
|
- "@next-auth/prisma-adapter"
|
||||||
- "@next-auth/sequelize-adapter"
|
- "@next-auth/sequelize-adapter"
|
||||||
|
- "@next-auth/supabase-adapter"
|
||||||
- "@next-auth/typeorm-legacy-adapter"
|
- "@next-auth/typeorm-legacy-adapter"
|
||||||
- "@next-auth/upstash-redis-adapter"
|
- "@next-auth/upstash-redis-adapter"
|
||||||
|
- "@next-auth/xata-adapter"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/5_feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/5_feature_request.yml
vendored
@@ -9,6 +9,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
|
||||||
Thank you very much for reaching out to us regarding the awesome feature that you believe should be included in the NextAuth.js library.
|
Thank you very much for reaching out to us regarding the awesome feature that you believe should be included in the NextAuth.js library.
|
||||||
|
|
||||||
_NOTE: Feature requests are converted to [discussions (Ideas 💡)](https://github.com/nextauthjs/next-auth/discussions/categories/ideas). Make sure your idea hasn't been asked yet, and upvote the existing one before opening a new instead._
|
_NOTE: Feature requests are converted to [discussions (Ideas 💡)](https://github.com/nextauthjs/next-auth/discussions/categories/ideas). Make sure your idea hasn't been asked yet, and upvote the existing one before opening a new instead._
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/6_typescript.yml
vendored
1
.github/ISSUE_TEMPLATE/6_typescript.yml
vendored
@@ -17,6 +17,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
|
||||||
Make sure you [link]() to external documentation if necessary and provide inline code examples like so:
|
Make sure you [link]() to external documentation if necessary and provide inline code examples like so:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/7_question.yml
vendored
1
.github/ISSUE_TEMPLATE/7_question.yml
vendored
@@ -9,6 +9,7 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
|
||||||
We are glad that you have a question about this library. Please provide the following information:
|
We are glad that you have a question about this library. Please provide the following information:
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -5,9 +5,14 @@ Please fill out the information below to expedite the review and (hopefully)
|
|||||||
merge of your pull request!
|
merge of your pull request!
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
> _NOTE_:
|
||||||
|
>
|
||||||
|
> - It's a good idea to open an issue first to discuss potential changes.
|
||||||
|
> - Please make sure that you are _NOT_ opening a PR to fix a potential security vulnerability. Instead, please follow the [Security guidelines](../Security.md) to disclose the issue to us confidentially.
|
||||||
|
|
||||||
## ☕️ Reasoning
|
## ☕️ Reasoning
|
||||||
|
|
||||||
What changes are being made? What feature/bug is being fixed here?
|
<!-- What changes are being made? What feature/bug is being fixed here? -->
|
||||||
|
|
||||||
## 🧢 Checklist
|
## 🧢 Checklist
|
||||||
|
|
||||||
@@ -23,6 +28,7 @@ Fixes: INSERT_ISSUE_LINK_HERE
|
|||||||
|
|
||||||
## 📌 Resources
|
## 📌 Resources
|
||||||
|
|
||||||
- [Contributing guidelines](./CONTRIBUTING.md)
|
- [Security guidelines](../Security.md)
|
||||||
- [Code of conduct](./CODE_OF_CONDUCT.md)
|
- [Contributing guidelines](../CONTRIBUTING.md)
|
||||||
|
- [Code of conduct](../CODE_OF_CONDUCT.md)
|
||||||
- [Contributing to Open Source](https://kcd.im/pull-request)
|
- [Contributing to Open Source](https://kcd.im/pull-request)
|
||||||
|
|||||||
6
.github/issue-labeler.yml
vendored
6
.github/issue-labeler.yml
vendored
@@ -30,8 +30,14 @@ prisma:
|
|||||||
sequelize:
|
sequelize:
|
||||||
- "@next-auth/sequelize-adapter"
|
- "@next-auth/sequelize-adapter"
|
||||||
|
|
||||||
|
supabase:
|
||||||
|
- "@next-auth/supabase-adapter"
|
||||||
|
|
||||||
typeorm-legacy:
|
typeorm-legacy:
|
||||||
- "@next-auth/typeorm-legacy-adapter"
|
- "@next-auth/typeorm-legacy-adapter"
|
||||||
|
|
||||||
upstash-redis:
|
upstash-redis:
|
||||||
- "@next-auth/upstash-redis-adapter"
|
- "@next-auth/upstash-redis-adapter"
|
||||||
|
|
||||||
|
xata:
|
||||||
|
- "@next-auth/xata-adapter"
|
||||||
|
|||||||
8
.github/pr-labeler.yml
vendored
8
.github/pr-labeler.yml
vendored
@@ -10,7 +10,7 @@ providers:
|
|||||||
|
|
||||||
adapters:
|
adapters:
|
||||||
- packages/next-auth/src/adapters.ts
|
- packages/next-auth/src/adapters.ts
|
||||||
- packages/*-adapter/**
|
- packages/adapter-*/**
|
||||||
|
|
||||||
dgraph:
|
dgraph:
|
||||||
- packages/adapter-dgraph/**
|
- packages/adapter-dgraph/**
|
||||||
@@ -42,12 +42,18 @@ prisma:
|
|||||||
sequelize:
|
sequelize:
|
||||||
- packages/adapter-sequelize/**
|
- packages/adapter-sequelize/**
|
||||||
|
|
||||||
|
supabase:
|
||||||
|
- packages/adapter-supabase/**
|
||||||
|
|
||||||
typeorm-legacy:
|
typeorm-legacy:
|
||||||
- packages/adapter-typeorm-legacy/**
|
- packages/adapter-typeorm-legacy/**
|
||||||
|
|
||||||
upstash-redis:
|
upstash-redis:
|
||||||
- packages/adapter-upstash-redis/**
|
- packages/adapter-upstash-redis/**
|
||||||
|
|
||||||
|
xata:
|
||||||
|
- packages/adapter-xata/**
|
||||||
|
|
||||||
core:
|
core:
|
||||||
- packages/next-auth/src/**/*
|
- packages/next-auth/src/**/*
|
||||||
|
|
||||||
|
|||||||
2
.github/version-pr/action.yml
vendored
2
.github/version-pr/action.yml
vendored
@@ -4,5 +4,5 @@ outputs:
|
|||||||
version:
|
version:
|
||||||
description: "npm package version"
|
description: "npm package version"
|
||||||
runs:
|
runs:
|
||||||
using: "node12"
|
using: "node16"
|
||||||
main: "index.js"
|
main: "index.js"
|
||||||
|
|||||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -2,7 +2,7 @@ name: Code Analysis
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, beta, next]
|
branches: [beta, next]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
schedule:
|
schedule:
|
||||||
@@ -18,10 +18,10 @@ jobs:
|
|||||||
language: ["javascript"]
|
language: ["javascript"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|||||||
2
.github/workflows/label-issue.yml
vendored
2
.github/workflows/label-issue.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
name: Triage
|
name: Triage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: github/issue-labeler@v2.4.1
|
- uses: github/issue-labeler@v2.5
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
configuration-path: ".github/issue-labeler.yml"
|
configuration-path: ".github/issue-labeler.yml"
|
||||||
|
|||||||
2
.github/workflows/label-pr.yml
vendored
2
.github/workflows/label-pr.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
name: Triage
|
name: Triage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v3
|
- uses: actions/labeler@v4
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
configuration-path: ".github/pr-labeler.yml"
|
configuration-path: ".github/pr-labeler.yml"
|
||||||
|
|||||||
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@@ -15,17 +15,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Init
|
- name: Init
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.1
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 7.5.1
|
version: 7.5.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
@@ -49,17 +49,17 @@ jobs:
|
|||||||
environment: Production
|
environment: Production
|
||||||
steps:
|
steps:
|
||||||
- name: Init
|
- name: Init
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.1
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 7.5.1
|
version: 7.5.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
@@ -81,15 +81,15 @@ jobs:
|
|||||||
environment: Preview
|
environment: Preview
|
||||||
steps:
|
steps:
|
||||||
- name: Init
|
- name: Init
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.1
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 7.5.1
|
version: 7.5.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
@@ -106,9 +106,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN_PKG }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN_PKG }}
|
||||||
- name: Comment version on PR
|
- name: Comment version on PR
|
||||||
uses: NejcZdovc/comment-pr@v1
|
uses: NejcZdovc/comment-pr@v2
|
||||||
with:
|
with:
|
||||||
message: "🎉 Experimental release [published on npm](https://www.npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n\n```sh\nnpm i next-auth@${{ env.VERSION }}\n```\n```sh\nyarn add next-auth@${{ env.VERSION }}\n```"
|
message:
|
||||||
|
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n \
|
||||||
|
```sh\npnpm add next-auth@${{ env.VERSION }}\n```\n \
|
||||||
|
```sh\nyarn add next-auth@${{ env.VERSION }}\n```\n \
|
||||||
|
```sh\nnpm i next-auth@${{ env.VERSION }}\n```"
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ steps.determine-version.outputs.version }}
|
VERSION: ${{ steps.determine-version.outputs.version }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/sync-examples.yml
vendored
2
.github/workflows/sync-examples.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Run GitHub File Sync
|
- name: Run GitHub File Sync
|
||||||
# Can update to v1 when https://github.com/BetaHuhn/repo-file-sync-action/issues/168 is resolved
|
# Can update to v1 when https://github.com/BetaHuhn/repo-file-sync-action/issues/168 is resolved
|
||||||
uses: BetaHuhn/repo-file-sync-action@v1.16.5
|
uses: BetaHuhn/repo-file-sync-action@v1.16.5
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,6 +35,7 @@ packages/next-auth/core
|
|||||||
packages/next-auth/jwt
|
packages/next-auth/jwt
|
||||||
packages/next-auth/react
|
packages/next-auth/react
|
||||||
packages/next-auth/adapters.d.ts
|
packages/next-auth/adapters.d.ts
|
||||||
|
packages/next-auth/adapters.js
|
||||||
packages/next-auth/index.d.ts
|
packages/next-auth/index.d.ts
|
||||||
packages/next-auth/index.js
|
packages/next-auth/index.js
|
||||||
packages/next-auth/next
|
packages/next-auth/next
|
||||||
@@ -64,6 +65,7 @@ dev.db*
|
|||||||
packages/adapter-prisma/prisma/dev.db
|
packages/adapter-prisma/prisma/dev.db
|
||||||
packages/adapter-prisma/prisma/migrations
|
packages/adapter-prisma/prisma/migrations
|
||||||
db.sqlite
|
db.sqlite
|
||||||
|
packages/adapter-supabase/supabase/.branches
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
coverage
|
coverage
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
|||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported by contacting me@iaincollins.com or info@balazsorban.com and yo@ndo.dev.
|
reported by contacting hi@thvu.dev, info@balazsorban.com, yo@ndo.dev and me@iaincollins.com.
|
||||||
All complaints will be reviewed and investigated and will result in a response
|
All complaints will be reviewed and investigated and will result in a response
|
||||||
that is deemed necessary and appropriate to the circumstances. The project team
|
that is deemed necessary and appropriate to the circumstances. The project team
|
||||||
is obligated to maintain confidentiality with regard to the reporter of an
|
is obligated to maintain confidentiality with regard to the reporter of an
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ Please raise any significant new functionality or breaking change an issue for d
|
|||||||
|
|
||||||
Anyone can be a contributor. Either you found a typo, or you have an awesome feature request you could implement, we encourage you to create a Pull Request.
|
Anyone can be a contributor. Either you found a typo, or you have an awesome feature request you could implement, we encourage you to create a Pull Request.
|
||||||
|
|
||||||
|
Before contributing, we recommend you read the [Tour de Source: NextAuth.js](https://sourcegraph.com/notebooks/Tm90ZWJvb2s6MTc2MQ==) post to become more familiar with the libraries inner workings.
|
||||||
|
|
||||||
### Pull Requests
|
### Pull Requests
|
||||||
|
|
||||||
- The latest changes are always in `main`, so please make your Pull Request against that branch.
|
- The latest changes are always in `main`, so please make your Pull Request against that branch.
|
||||||
@@ -26,7 +28,6 @@ Anyone can be a contributor. Either you found a typo, or you have an awesome fea
|
|||||||
|
|
||||||
A quick guide on how to setup _next-auth_ locally to work on it and test out any changes:
|
A quick guide on how to setup _next-auth_ locally to work on it and test out any changes:
|
||||||
|
|
||||||
|
|
||||||
1. Clone the repo:
|
1. Clone the repo:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -34,13 +35,21 @@ git clone git@github.com:nextauthjs/next-auth.git
|
|||||||
cd next-auth
|
cd next-auth
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Install packages. Developing requires Node.js v16:
|
2. Set up the correct pnpm version, using [Corepack](https://nodejs.org/api/corepack.html). Run the following in the project'a root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
corepack enable pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
(Now, if you run `pnpm --version`, it should print the same verion as the `packageManager` property in the [`package.json` file](https://github.com/nextauthjs/next-auth/blob/main/package.json))
|
||||||
|
|
||||||
|
3. Install packages. Developing requires Node.js v18:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Populate `.env.local`:
|
4. Populate `.env.local`:
|
||||||
|
|
||||||
Copy `apps/dev/.env.local.example` to `apps/dev/.env.local`, and add your env variables for each provider you want to test.
|
Copy `apps/dev/.env.local.example` to `apps/dev/.env.local`, and add your env variables for each provider you want to test.
|
||||||
|
|
||||||
@@ -52,11 +61,12 @@ cp .env.local.example .env.local
|
|||||||
> NOTE: You can add any environment variables to .env.local that you would like to use in your dev app.
|
> NOTE: You can add any environment variables to .env.local that you would like to use in your dev app.
|
||||||
> You can find the next-auth config under`apps/dev/pages/api/auth/[...nextauth].js`.
|
> You can find the next-auth config under`apps/dev/pages/api/auth/[...nextauth].js`.
|
||||||
|
|
||||||
4. Start the developer application/server:
|
5. Start the developer application/server:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Your developer application will be available on `http://localhost:3000`
|
Your developer application will be available on `http://localhost:3000`
|
||||||
|
|
||||||
That's it! 🎉
|
That's it! 🎉
|
||||||
@@ -77,6 +87,7 @@ If you think your custom provider might be useful to others, we encourage you to
|
|||||||
|
|
||||||
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers) (Make sure you use a named default export, like `export default function YourProvider`!)
|
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers) (Make sure you use a named default export, like `export default function YourProvider`!)
|
||||||
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
|
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
|
||||||
|
3. Add provider logo svgs, like `google-dark.svg` (dark mode) and `google.svg` (light mode) to the `/packages/next-auth/provider-logos/` directory. Don't forget to set the provider's styling options in the `provider.style` config object.
|
||||||
|
|
||||||
That's it! 🎉 Others will be able to discover this provider much more easily now!
|
That's it! 🎉 Others will be able to discover this provider much more easily now!
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ If you contact us regarding a serious issue:
|
|||||||
- We will disclose the issue (and credit you, with your consent) once a fix to resolve the issue has been released.
|
- We will disclose the issue (and credit you, with your consent) once a fix to resolve the issue has been released.
|
||||||
- If 90 days has elapsed and we still don't have a fix, we will disclose the issue publicly.
|
- If 90 days has elapsed and we still don't have a fix, we will disclose the issue publicly.
|
||||||
|
|
||||||
The best way to report an issue is by contacting us via email at info@balazsorban.com or me@iaincollins.com and yo@ndo.dev, or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details. (Please do not disclose sensitive details publicly at this stage.)
|
The best way to report an issue is by contacting us via email at hi@thvu.dev, info@balazsorban.com, yo@ndo.dev and me@iaincollins.com, or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details. (Please do not disclose sensitive details publicly at this stage.)
|
||||||
|
|
||||||
> For less serious issues (e.g. RFC compliance for unsupported flows or potential issues that may cause a problem in the future) it is appropriate to submit these these publically as bug reports or feature requests or to raise a question to open a discussion around them.
|
> For less serious issues (e.g. RFC compliance for unsupported flows or potential issues that may cause a problem in the future) it is appropriate to submit these publicly as bug reports or feature requests or to raise a question to open a discussion around them.
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
|
|||||||
@@ -47,9 +47,12 @@ EMAIL_FROM=user@gmail.com
|
|||||||
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
|
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
|
||||||
DATABASE_URL=
|
DATABASE_URL=
|
||||||
|
|
||||||
BOXYHQSAML_ISSUER="https://jackson-demo.boxyhq.com"
|
|
||||||
BOXYHQSAML_ID="tenant=boxyhq.com&product=saml-demo.boxyhq.com"
|
|
||||||
BOXYHQSAML_SECRET="dummy"
|
|
||||||
|
|
||||||
WIKIMEDIA_ID=
|
WIKIMEDIA_ID=
|
||||||
WIKIMEDIA_SECRET=
|
WIKIMEDIA_SECRET=
|
||||||
|
|
||||||
|
# Supabase Example Configuration
|
||||||
|
# Supabase Example Configuration
|
||||||
|
# NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
|
||||||
|
# SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
|
||||||
|
# SUPABASE_JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long
|
||||||
|
# NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs
|
||||||
12
apps/dev/app/layout.tsx
Normal file
12
apps/dev/app/layout.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
6
apps/dev/app/server-component/page.tsx
Normal file
6
apps/dev/app/server-component/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
|
|
||||||
|
export default async function Page() {
|
||||||
|
const session = await unstable_getServerSession()
|
||||||
|
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||||
|
}
|
||||||
@@ -17,9 +17,7 @@ export default function Footer() {
|
|||||||
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/policy">
|
<Link href="/policy">Policy</Link>
|
||||||
<a>Policy</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<em>{packageJSON.version}</em>
|
<em>{packageJSON.version}</em>
|
||||||
|
|||||||
@@ -64,49 +64,37 @@ export default function Header() {
|
|||||||
<nav>
|
<nav>
|
||||||
<ul className={styles.navItems}>
|
<ul className={styles.navItems}>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/">
|
<Link href="/">Home</Link>
|
||||||
<a>Home</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/client">
|
<Link href="/client">Client</Link>
|
||||||
<a>Client</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/server">
|
<Link href="/server">Server</Link>
|
||||||
<a>Server</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/protected">
|
<Link href="/protected">Protected</Link>
|
||||||
<a>Protected</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/protected-ssr">
|
<Link href="/protected-ssr">Protected(SSR)</Link>
|
||||||
<a>Protected(SSR)</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/api-example">
|
<Link href="/api-example">API</Link>
|
||||||
<a>API</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/credentials">
|
<Link href="/credentials">Credentials</Link>
|
||||||
<a>Credentials</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/email">
|
<Link href="/email">Email</Link>
|
||||||
<a>Email</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/middleware-protected">
|
<Link href="/middleware-protected">Middleware protected</Link>
|
||||||
<a>Middleware protected</a>
|
</li>
|
||||||
</Link>
|
<li className={styles.navItem}>
|
||||||
|
<Link href="/supabase-client-rls">Supabase RLS</Link>
|
||||||
|
</li>
|
||||||
|
<li className={styles.navItem}>
|
||||||
|
<Link href="/supabase-ssr">Supabase RLS(SSR)</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -30,12 +30,11 @@ export const config = { matcher: ["/middleware-protected"] }
|
|||||||
// export default withAuth(
|
// export default withAuth(
|
||||||
// function middleware(req, ev) {
|
// function middleware(req, ev) {
|
||||||
// console.log(req, ev)
|
// console.log(req, ev)
|
||||||
// return undefined // NOTE: `NextMiddleware` should allow returning `void`
|
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// callbacks: {
|
// callbacks: {
|
||||||
// authorized: ({ token }) => token.name === "Balázs Orbán",
|
// authorized: ({ token }) => token.name === "Balázs Orbán",
|
||||||
// }
|
// },
|
||||||
// }
|
// }
|
||||||
// )
|
// )
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ module.exports = {
|
|||||||
config.experiments = { ...config.experiments, topLevelAwait: true }
|
config.experiments = { ...config.experiments, topLevelAwait: true }
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
|
experimental: { appDir: true },
|
||||||
typescript: { ignoreBuildErrors: true },
|
typescript: { ignoreBuildErrors: true },
|
||||||
experimental: { externalDir: true },
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf .next",
|
"clean": "rm -rf .next",
|
||||||
"copy:css": "cpx \"../../packages/next-auth/css/**/*\" src/css --watch",
|
"dev": "next dev",
|
||||||
"watch:css": "cd ../../packages/next-auth && pnpm watch:css",
|
"lint": "next lint",
|
||||||
"dev": "concurrently \"pnpm dev:next\" \"pnpm watch:css\" \"pnpm copy:css\"",
|
|
||||||
"dev:next": "next dev",
|
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"email": "fake-smtp-server",
|
"email": "fake-smtp-server",
|
||||||
@@ -18,19 +16,22 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next-auth/fauna-adapter": "workspace:*",
|
"@next-auth/fauna-adapter": "workspace:*",
|
||||||
"@next-auth/prisma-adapter": "workspace:*",
|
"@next-auth/prisma-adapter": "workspace:*",
|
||||||
|
"@next-auth/supabase-adapter": "workspace:*",
|
||||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
||||||
"@prisma/client": "^3",
|
"@prisma/client": "^3",
|
||||||
|
"@supabase/supabase-js": "^2.0.5",
|
||||||
"faunadb": "^4",
|
"faunadb": "^4",
|
||||||
"next": "12.2.0",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"next": "13.0.2",
|
||||||
|
"next-auth": "workspace:*",
|
||||||
"nodemailer": "^6",
|
"nodemailer": "^6",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/jsonwebtoken": "^8.5.5",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"concurrently": "^7",
|
|
||||||
"cpx": "^1.5.0",
|
|
||||||
"fake-smtp-server": "^0.8.0",
|
"fake-smtp-server": "^0.8.0",
|
||||||
"pg": "^8.7.3",
|
"pg": "^8.7.3",
|
||||||
"prisma": "^3",
|
"prisma": "^3",
|
||||||
|
|||||||
@@ -1,251 +1,159 @@
|
|||||||
import NextAuth, { NextAuthOptions } from "next-auth"
|
import NextAuth from "next-auth"
|
||||||
// import EmailProvider from "next-auth/providers/email"
|
import type { NextAuthOptions } from "next-auth"
|
||||||
import GitHubProvider from "next-auth/providers/github"
|
import jwt from "jsonwebtoken"
|
||||||
import Auth0Provider from "next-auth/providers/auth0"
|
|
||||||
import KeycloakProvider from "next-auth/providers/keycloak"
|
// Providers
|
||||||
import TwitterProvider, {
|
import Apple from "next-auth/providers/apple"
|
||||||
// TwitterLegacy as TwitterLegacyProvider,
|
import Auth0 from "next-auth/providers/auth0"
|
||||||
} from "next-auth/providers/twitter"
|
import AzureAD from "next-auth/providers/azure-ad"
|
||||||
import CredentialsProvider from "next-auth/providers/credentials"
|
|
||||||
import IDS4Provider from "next-auth/providers/identity-server4"
|
|
||||||
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6"
|
|
||||||
import Twitch from "next-auth/providers/twitch"
|
|
||||||
import GoogleProvider from "next-auth/providers/google"
|
|
||||||
import FacebookProvider from "next-auth/providers/facebook"
|
|
||||||
import FoursquareProvider from "next-auth/providers/foursquare"
|
|
||||||
// import FreshbooksProvider from "next-auth/providers/freshbooks"
|
|
||||||
import GitlabProvider from "next-auth/providers/gitlab"
|
|
||||||
import InstagramProvider from "next-auth/providers/instagram"
|
|
||||||
import LineProvider from "next-auth/providers/line"
|
|
||||||
import LinkedInProvider from "next-auth/providers/linkedin"
|
|
||||||
import MailchimpProvider from "next-auth/providers/mailchimp"
|
|
||||||
import DiscordProvider from "next-auth/providers/discord"
|
|
||||||
import AzureADProvider from "next-auth/providers/azure-ad"
|
|
||||||
import SpotifyProvider from "next-auth/providers/spotify"
|
|
||||||
import CognitoProvider from "next-auth/providers/cognito"
|
|
||||||
import SlackProvider from "next-auth/providers/slack"
|
|
||||||
import Okta from "next-auth/providers/okta"
|
|
||||||
import AzureB2C from "next-auth/providers/azure-ad-b2c"
|
import AzureB2C from "next-auth/providers/azure-ad-b2c"
|
||||||
import OsuProvider from "next-auth/providers/osu"
|
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
|
||||||
import AppleProvider from "next-auth/providers/apple"
|
import Cognito from "next-auth/providers/cognito"
|
||||||
import PatreonProvider from "next-auth/providers/patreon"
|
import Credentials from "next-auth/providers/credentials"
|
||||||
import TraktProvider from "next-auth/providers/trakt"
|
import Discord from "next-auth/providers/discord"
|
||||||
import WorkOSProvider from "next-auth/providers/workos"
|
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
|
||||||
import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"
|
import Email from "next-auth/providers/email"
|
||||||
import WikimediaProvider from "next-auth/providers/wikimedia"
|
import Facebook from "next-auth/providers/facebook"
|
||||||
import VkProvider from "next-auth/providers/vk"
|
import Foursquare from "next-auth/providers/foursquare"
|
||||||
|
import Freshbooks from "next-auth/providers/freshbooks"
|
||||||
|
import GitHub from "next-auth/providers/github"
|
||||||
|
import Gitlab from "next-auth/providers/gitlab"
|
||||||
|
import Google from "next-auth/providers/google"
|
||||||
|
import IDS4 from "next-auth/providers/identity-server4"
|
||||||
|
import Instagram from "next-auth/providers/instagram"
|
||||||
|
import Keycloak from "next-auth/providers/keycloak"
|
||||||
|
import Line from "next-auth/providers/line"
|
||||||
|
import LinkedIn from "next-auth/providers/linkedin"
|
||||||
|
import Mailchimp from "next-auth/providers/mailchimp"
|
||||||
|
import Okta from "next-auth/providers/okta"
|
||||||
|
import Osu from "next-auth/providers/osu"
|
||||||
|
import Patreon from "next-auth/providers/patreon"
|
||||||
|
import Slack from "next-auth/providers/slack"
|
||||||
|
import Spotify from "next-auth/providers/spotify"
|
||||||
|
import Trakt from "next-auth/providers/trakt"
|
||||||
|
import Twitch from "next-auth/providers/twitch"
|
||||||
|
import Twitter, { TwitterLegacy } from "next-auth/providers/twitter"
|
||||||
|
import Vk from "next-auth/providers/vk"
|
||||||
|
import Wikimedia from "next-auth/providers/wikimedia"
|
||||||
|
import WorkOS from "next-auth/providers/workos"
|
||||||
|
|
||||||
// TypeORM
|
// Adapters
|
||||||
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
import { PrismaClient } from "@prisma/client"
|
||||||
// const adapter = TypeORMLegacyAdapter({
|
import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||||
// type: "sqlite",
|
import { Client as FaunaClient } from "faunadb"
|
||||||
// name: "next-auth-test-memory",
|
import { FaunaAdapter } from "@next-auth/fauna-adapter"
|
||||||
// database: "./typeorm/dev.db",
|
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
|
||||||
// synchronize: true,
|
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
|
||||||
// })
|
|
||||||
|
|
||||||
// // Prisma
|
// Add an adapter you want to test here.
|
||||||
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
const adapters = {
|
||||||
// import { PrismaClient } from "@prisma/client"
|
prisma() {
|
||||||
// const prisma = new PrismaClient()
|
const client = globalThis.prisma || new PrismaClient()
|
||||||
// const adapter = PrismaAdapter(prisma)
|
if (process.env.NODE_ENV !== "production") globalThis.prisma = client
|
||||||
|
return PrismaAdapter(client)
|
||||||
// // Fauna
|
},
|
||||||
// import { Client as FaunaClient } from "faunadb"
|
typeorm() {
|
||||||
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
|
return TypeORMLegacyAdapter({
|
||||||
// const client = new FaunaClient({
|
type: "sqlite",
|
||||||
// secret: process.env.FAUNA_SECRET,
|
name: "next-auth-test-memory",
|
||||||
// domain: process.env.FAUNA_DOMAIN,
|
database: "./typeorm/dev.db",
|
||||||
// })
|
synchronize: true,
|
||||||
// const adapter = FaunaAdapter(client)
|
})
|
||||||
|
},
|
||||||
// // Dummy
|
fauna() {
|
||||||
// const adapter: any = {
|
const client =
|
||||||
// getUserByEmail: (email) => ({ id: "1", email, emailVerified: null }),
|
globalThis.fauna ||
|
||||||
// createVerificationToken: (token) => token,
|
new FaunaClient({
|
||||||
// }
|
secret: process.env.FAUNA_SECRET,
|
||||||
|
domain: process.env.FAUNA_DOMAIN,
|
||||||
export const authOptions: NextAuthOptions = {
|
})
|
||||||
// adapter,
|
if (process.env.NODE_ENV !== "production") global.fauna = client
|
||||||
providers: [
|
return FaunaAdapter(client)
|
||||||
// E-mail
|
},
|
||||||
// Start fake e-mail server with `npm run start:email`
|
supabase() {
|
||||||
// EmailProvider({
|
return SupabaseAdapter({
|
||||||
// server: {
|
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
// host: "127.0.0.1",
|
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||||
// auth: null,
|
})
|
||||||
// secure: false,
|
},
|
||||||
// port: 1025,
|
noop() {
|
||||||
// tls: { rejectUnauthorized: false },
|
return undefined
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// Credentials
|
|
||||||
CredentialsProvider({
|
|
||||||
name: "Credentials",
|
|
||||||
credentials: {
|
|
||||||
password: { label: "Password", type: "password" },
|
|
||||||
},
|
|
||||||
async authorize(credentials) {
|
|
||||||
if (credentials.password === "pw") {
|
|
||||||
return {
|
|
||||||
name: "Fill Murray",
|
|
||||||
email: "bill@fillmurray.com",
|
|
||||||
image: "https://www.fillmurray.com/64/64",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// OAuth 1
|
|
||||||
// TwitterLegacyProvider({
|
|
||||||
// clientId: process.env.TWITTER_LEGACY_ID,
|
|
||||||
// clientSecret: process.env.TWITTER_LEGACY_SECRET,
|
|
||||||
// }),
|
|
||||||
// OAuth 2 / OIDC
|
|
||||||
TwitterProvider({
|
|
||||||
// Opt-in to the new Twitter API for now. Should be default in the future.
|
|
||||||
version: "2.0",
|
|
||||||
clientId: process.env.TWITTER_ID,
|
|
||||||
clientSecret: process.env.TWITTER_SECRET,
|
|
||||||
}),
|
|
||||||
GitHubProvider({
|
|
||||||
clientId: process.env.GITHUB_ID,
|
|
||||||
clientSecret: process.env.GITHUB_SECRET,
|
|
||||||
}),
|
|
||||||
Auth0Provider({
|
|
||||||
clientId: process.env.AUTH0_ID,
|
|
||||||
clientSecret: process.env.AUTH0_SECRET,
|
|
||||||
issuer: process.env.AUTH0_ISSUER,
|
|
||||||
}),
|
|
||||||
KeycloakProvider({
|
|
||||||
clientId: process.env.KEYCLOAK_ID,
|
|
||||||
clientSecret: process.env.KEYCLOAK_SECRET,
|
|
||||||
issuer: process.env.KEYCLOAK_ISSUER,
|
|
||||||
}),
|
|
||||||
Twitch({
|
|
||||||
clientId: process.env.TWITCH_ID,
|
|
||||||
clientSecret: process.env.TWITCH_SECRET,
|
|
||||||
}),
|
|
||||||
GoogleProvider({
|
|
||||||
clientId: process.env.GOOGLE_ID,
|
|
||||||
clientSecret: process.env.GOOGLE_SECRET,
|
|
||||||
}),
|
|
||||||
FacebookProvider({
|
|
||||||
clientId: process.env.FACEBOOK_ID,
|
|
||||||
clientSecret: process.env.FACEBOOK_SECRET,
|
|
||||||
}),
|
|
||||||
FoursquareProvider({
|
|
||||||
clientId: process.env.FOURSQUARE_ID,
|
|
||||||
clientSecret: process.env.FOURSQUARE_SECRET,
|
|
||||||
}),
|
|
||||||
// FreshbooksProvider({
|
|
||||||
// clientId: process.env.FRESHBOOKS_ID,
|
|
||||||
// clientSecret: process.env.FRESHBOOKS_SECRET,
|
|
||||||
// }),
|
|
||||||
GitlabProvider({
|
|
||||||
clientId: process.env.GITLAB_ID,
|
|
||||||
clientSecret: process.env.GITLAB_SECRET,
|
|
||||||
}),
|
|
||||||
InstagramProvider({
|
|
||||||
clientId: process.env.INSTAGRAM_ID,
|
|
||||||
clientSecret: process.env.INSTAGRAM_SECRET,
|
|
||||||
}),
|
|
||||||
LineProvider({
|
|
||||||
clientId: process.env.LINE_ID,
|
|
||||||
clientSecret: process.env.LINE_SECRET,
|
|
||||||
}),
|
|
||||||
LinkedInProvider({
|
|
||||||
clientId: process.env.LINKEDIN_ID,
|
|
||||||
clientSecret: process.env.LINKEDIN_SECRET,
|
|
||||||
}),
|
|
||||||
MailchimpProvider({
|
|
||||||
clientId: process.env.MAILCHIMP_ID,
|
|
||||||
clientSecret: process.env.MAILCHIMP_SECRET,
|
|
||||||
}),
|
|
||||||
IDS4Provider({
|
|
||||||
clientId: process.env.IDS4_ID,
|
|
||||||
clientSecret: process.env.IDS4_SECRET,
|
|
||||||
issuer: process.env.IDS4_ISSUER,
|
|
||||||
}),
|
|
||||||
DuendeIDS6Provider({
|
|
||||||
clientId: "interactive.confidential",
|
|
||||||
clientSecret: "secret",
|
|
||||||
issuer: "https://demo.duendesoftware.com",
|
|
||||||
}),
|
|
||||||
DiscordProvider({
|
|
||||||
clientId: process.env.DISCORD_ID,
|
|
||||||
clientSecret: process.env.DISCORD_SECRET,
|
|
||||||
}),
|
|
||||||
AzureADProvider({
|
|
||||||
clientId: process.env.AZURE_AD_CLIENT_ID,
|
|
||||||
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
|
|
||||||
tenantId: process.env.AZURE_AD_TENANT_ID,
|
|
||||||
profilePhotoSize: 48,
|
|
||||||
}),
|
|
||||||
SpotifyProvider({
|
|
||||||
clientId: process.env.SPOTIFY_ID,
|
|
||||||
clientSecret: process.env.SPOTIFY_SECRET,
|
|
||||||
}),
|
|
||||||
CognitoProvider({
|
|
||||||
clientId: process.env.COGNITO_ID,
|
|
||||||
clientSecret: process.env.COGNITO_SECRET,
|
|
||||||
issuer: process.env.COGNITO_ISSUER,
|
|
||||||
}),
|
|
||||||
Okta({
|
|
||||||
clientId: process.env.OKTA_ID,
|
|
||||||
clientSecret: process.env.OKTA_SECRET,
|
|
||||||
issuer: process.env.OKTA_ISSUER,
|
|
||||||
}),
|
|
||||||
SlackProvider({
|
|
||||||
clientId: process.env.SLACK_ID,
|
|
||||||
clientSecret: process.env.SLACK_SECRET,
|
|
||||||
}),
|
|
||||||
AzureB2C({
|
|
||||||
clientId: process.env.AZURE_B2C_ID,
|
|
||||||
clientSecret: process.env.AZURE_B2C_SECRET,
|
|
||||||
tenantId: process.env.AZURE_B2C_TENANT_ID,
|
|
||||||
primaryUserFlow: process.env.AZURE_B2C_PRIMARY_USER_FLOW,
|
|
||||||
}),
|
|
||||||
OsuProvider({
|
|
||||||
clientId: process.env.OSU_CLIENT_ID,
|
|
||||||
clientSecret: process.env.OSU_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
AppleProvider({
|
|
||||||
clientId: process.env.APPLE_ID,
|
|
||||||
clientSecret: process.env.APPLE_SECRET,
|
|
||||||
}),
|
|
||||||
PatreonProvider({
|
|
||||||
clientId: process.env.PATREON_ID,
|
|
||||||
clientSecret: process.env.PATREON_SECRET,
|
|
||||||
}),
|
|
||||||
TraktProvider({
|
|
||||||
clientId: process.env.TRAKT_ID,
|
|
||||||
clientSecret: process.env.TRAKT_SECRET,
|
|
||||||
}),
|
|
||||||
WorkOSProvider({
|
|
||||||
clientId: process.env.WORKOS_ID,
|
|
||||||
clientSecret: process.env.WORKOS_SECRET,
|
|
||||||
}),
|
|
||||||
BoxyHQSAMLProvider({
|
|
||||||
issuer: process.env.BOXYHQSAML_ISSUER ?? "https://example.com",
|
|
||||||
clientId: process.env.BOXYHQSAML_ID,
|
|
||||||
clientSecret: process.env.BOXYHQSAML_SECRET,
|
|
||||||
}),
|
|
||||||
WikimediaProvider({
|
|
||||||
clientId: process.env.WIKIMEDIA_ID,
|
|
||||||
clientSecret: process.env.WIKIMEDIA_SECRET,
|
|
||||||
}),
|
|
||||||
VkProvider({
|
|
||||||
clientId: process.env.VK_ID,
|
|
||||||
clientSecret: process.env.VK_SECRET
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
debug: true,
|
|
||||||
theme: {
|
|
||||||
colorScheme: "auto",
|
|
||||||
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
|
|
||||||
brandColor: "#1786fb",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
adapter: adapters.noop(),
|
||||||
|
callbacks: {
|
||||||
|
async session({ session, user }) {
|
||||||
|
// NOTE: this is needed when using Supabase with RLS. Otherwise this callback can be removed.
|
||||||
|
const signingSecret = process.env.SUPABASE_JWT_SECRET
|
||||||
|
if (signingSecret) {
|
||||||
|
const payload = {
|
||||||
|
aud: "authenticated",
|
||||||
|
exp: Math.floor(new Date(session.expires).getTime() / 1000),
|
||||||
|
sub: user.id,
|
||||||
|
email: user.email,
|
||||||
|
role: "authenticated",
|
||||||
|
}
|
||||||
|
session.supabaseAccessToken = jwt.sign(payload, signingSecret)
|
||||||
|
}
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
},
|
||||||
|
debug: process.env.NODE_ENV !== "production",
|
||||||
|
theme: {
|
||||||
|
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
|
||||||
|
brandColor: "#1786fb",
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
Credentials({
|
||||||
|
credentials: { password: { label: "Password", type: "password" } },
|
||||||
|
async authorize(credentials) {
|
||||||
|
if (credentials.password !== "pw") return null
|
||||||
|
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64" }
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
|
||||||
|
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
|
||||||
|
AzureAD({ clientId: process.env.AZURE_AD_CLIENT_ID, clientSecret: process.env.AZURE_AD_CLIENT_SECRET, tenantId: process.env.AZURE_AD_TENANT_ID }),
|
||||||
|
AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }),
|
||||||
|
BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }),
|
||||||
|
Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
|
||||||
|
Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }),
|
||||||
|
DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }),
|
||||||
|
Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }),
|
||||||
|
Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }),
|
||||||
|
Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }),
|
||||||
|
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }),
|
||||||
|
Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }),
|
||||||
|
Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
|
||||||
|
IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
|
||||||
|
Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }),
|
||||||
|
Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }),
|
||||||
|
Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }),
|
||||||
|
LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }),
|
||||||
|
Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }),
|
||||||
|
Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
|
||||||
|
Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }),
|
||||||
|
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
|
||||||
|
Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }),
|
||||||
|
Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }),
|
||||||
|
Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }),
|
||||||
|
Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }),
|
||||||
|
Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }),
|
||||||
|
TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }),
|
||||||
|
Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }),
|
||||||
|
Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }),
|
||||||
|
WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authOptions.adapter) {
|
||||||
|
authOptions.providers.unshift(
|
||||||
|
// NOTE: You can start a fake e-mail server with `pnpm email`
|
||||||
|
// and then go to `http://localhost:1080` in the browser
|
||||||
|
Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default NextAuth(authOptions)
|
export default NextAuth(authOptions)
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
import { getToken } from "next-auth/jwt"
|
import { getToken } from "next-auth/jwt"
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const token = await getToken({ req, secret: process.env.SECRET })
|
const token = await getToken({ req })
|
||||||
res.send(JSON.stringify(token, null, 2))
|
res.send(JSON.stringify(token, null, 2))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
// This is an example of to protect an API route
|
// This is an example of to protect an API route
|
||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
|
import { authOptions } from "../auth/[...nextauth]"
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const session = await unstable_getServerSession(req, res, options)
|
const session = await unstable_getServerSession(req, res, authOptions)
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
res.send({
|
res.send({
|
||||||
content:
|
content:
|
||||||
"This is protected content. You can access this content because you are signed in.",
|
"This is protected content. You can access this content because you are signed in.",
|
||||||
|
session,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
res.send({
|
res.send({
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// This is an example of how to access a session from an API route
|
// This is an example of how to access a session from an API route
|
||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
|
import { authOptions } from "../auth/[...nextauth]"
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const session = await unstable_getServerSession(req, res, authOptions)
|
const session = await unstable_getServerSession(req, res, authOptions)
|
||||||
res.send(JSON.stringify(session, null, 2))
|
res.json(session)
|
||||||
}
|
}
|
||||||
|
|||||||
30
apps/dev/pages/api/examples/supabase-rls.js
Normal file
30
apps/dev/pages/api/examples/supabase-rls.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// This is an example of how to query data from Supabase with RLS.
|
||||||
|
// Learn more about Row Levele Security (RLS): https://supabase.com/docs/guides/auth/row-level-security
|
||||||
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
|
import { authOptions } from "../auth/[...nextauth]"
|
||||||
|
import { createClient } from "@supabase/supabase-js"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const session = await unstable_getServerSession(req, res, authOptions)
|
||||||
|
|
||||||
|
if (!session)
|
||||||
|
return res.send(JSON.stringify({ error: "No session!" }, null, 2))
|
||||||
|
|
||||||
|
const { supabaseAccessToken } = session
|
||||||
|
|
||||||
|
const supabase = createClient(
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
|
{
|
||||||
|
global: {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${supabaseAccessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// Now you can query with RLS enabled.
|
||||||
|
const { data, error } = await supabase.from("users").select("*")
|
||||||
|
|
||||||
|
res.send(JSON.stringify({ supabaseAccessToken, data, error }, null, 2))
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { unstable_getServerSession } from "next-auth/next"
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
import Layout from "../components/layout"
|
import Layout from "../components/layout"
|
||||||
|
import { authOptions } from './api/auth/[...nextauth]';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
// As this page uses Server Side Rendering, the `session` will be already
|
// As this page uses Server Side Rendering, the `session` will be already
|
||||||
@@ -40,8 +41,8 @@ export async function getServerSideProps(context) {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
session: await unstable_getServerSession(
|
session: await unstable_getServerSession(
|
||||||
contex.req,
|
context.req,
|
||||||
contex.res,
|
context.res,
|
||||||
authOptions
|
authOptions
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
49
apps/dev/pages/supabase-client-rls.js
Normal file
49
apps/dev/pages/supabase-client-rls.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import Layout from "../components/layout"
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { useSession } from "next-auth/react"
|
||||||
|
import { createClient } from "@supabase/supabase-js"
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const { data: session } = useSession()
|
||||||
|
const [data, setData] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session) {
|
||||||
|
console.log(session)
|
||||||
|
// User is logged in, let's fetch their data.
|
||||||
|
const { supabaseAccessToken } = session
|
||||||
|
const supabase = createClient(
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
|
{
|
||||||
|
global: {
|
||||||
|
headers: { Authorization: `Bearer ${supabaseAccessToken}` },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// Fetch data with RLS enabled.
|
||||||
|
supabase
|
||||||
|
.from("users")
|
||||||
|
.select("*")
|
||||||
|
.then(({ data }) => setData(data))
|
||||||
|
}
|
||||||
|
}, [session])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<h1>Fetch Data from Supabase with RLS</h1>
|
||||||
|
<h2>Client-side data fetching with RLS:</h2>
|
||||||
|
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||||
|
<h2>API Example</h2>
|
||||||
|
<p>
|
||||||
|
You can also use Supabase in API routes. See the code in the
|
||||||
|
`/pages/api/examples/supabase-rls.js` file.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<em>You must be signed in to see responses.</em>
|
||||||
|
</p>
|
||||||
|
<p>/api/examples/supabase-rls</p>
|
||||||
|
<iframe src="/api/examples/supabase-rls" />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
68
apps/dev/pages/supabase-ssr.js
Normal file
68
apps/dev/pages/supabase-ssr.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// This is an example of how to protect content using server rendering
|
||||||
|
// and fetching data from Supabase with RLS enabled.
|
||||||
|
import { unstable_getServerSession } from "next-auth/next"
|
||||||
|
import { authOptions } from "./api/auth/[...nextauth]"
|
||||||
|
import { createClient } from "@supabase/supabase-js"
|
||||||
|
import Layout from "../components/layout"
|
||||||
|
import AccessDenied from "../components/access-denied"
|
||||||
|
|
||||||
|
export default function Page({ data, session }) {
|
||||||
|
// If no session exists, display access denied message
|
||||||
|
if (!session) {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<AccessDenied />
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If session exists, display content
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<h1>Protected Page</h1>
|
||||||
|
<p>Data fetched during SSR from Supabase with RSL enabled:</p>
|
||||||
|
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerSideProps(context) {
|
||||||
|
const session = await unstable_getServerSession(
|
||||||
|
context.req,
|
||||||
|
context.res,
|
||||||
|
authOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!session)
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
session,
|
||||||
|
data: null,
|
||||||
|
error: "No session",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { supabaseAccessToken } = session
|
||||||
|
|
||||||
|
const supabase = createClient(
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
|
{
|
||||||
|
global: {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${supabaseAccessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// Now you can query with RLS enabled.
|
||||||
|
const { data, error } = await supabase.from("users").select("*")
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
session,
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
@@ -15,11 +19,20 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"plugins": [
|
||||||
"next-auth": ["../../packages/next-auth/src"],
|
{
|
||||||
"next-auth/*": ["../../packages/next-auth/src/*"]
|
"name": "next"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": [
|
||||||
"exclude": ["node_modules", "jest.config.js"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"jest.config.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
20
apps/dev/types/nextauth.d.ts
vendored
Normal file
20
apps/dev/types/nextauth.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import NextAuth from "next-auth"
|
||||||
|
|
||||||
|
declare module "next-auth" {
|
||||||
|
/**
|
||||||
|
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
|
||||||
|
*/
|
||||||
|
interface Session {
|
||||||
|
// A JWT which can be used as Authorization header with supabase-js for RLS.
|
||||||
|
supabaseAccessToken?: string
|
||||||
|
user: {
|
||||||
|
/** The user's postal address. */
|
||||||
|
address: string
|
||||||
|
} & User
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
foo: string
|
||||||
|
}
|
||||||
|
}
|
||||||
104
apps/example-nextjs/.gitignore
vendored
104
apps/example-nextjs/.gitignore
vendored
@@ -1,110 +1,20 @@
|
|||||||
# Logs
|
.DS_Store
|
||||||
|
|
||||||
|
node_modules/
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
.yarn-integrity
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
.npm
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|
||||||
# Microbundle cache
|
*.tsbuildinfo
|
||||||
.rpt2_cache/
|
next-env.d.ts
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
.env.test
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
.next
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
.vercel
|
.vercel
|
||||||
.now
|
.env*.local
|
||||||
.env.local
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
@@ -17,9 +17,7 @@ export default function Footer() {
|
|||||||
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/policy">
|
<Link href="/policy">Policy</Link>
|
||||||
<a>Policy</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<em>next-auth@{packageJSON.dependencies["next-auth"]}</em>
|
<em>next-auth@{packageJSON.dependencies["next-auth"]}</em>
|
||||||
|
|||||||
@@ -67,39 +67,25 @@ export default function Header() {
|
|||||||
<nav>
|
<nav>
|
||||||
<ul className={styles.navItems}>
|
<ul className={styles.navItems}>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/">
|
<Link href="/">Home</Link>
|
||||||
<a>Home</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/client">
|
<Link href="/client">Client</Link>
|
||||||
<a>Client</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/server">
|
<Link href="/server">Server</Link>
|
||||||
<a>Server</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/protected">
|
<Link href="/protected">Protected</Link>
|
||||||
<a>Protected</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/api-example">
|
<Link href="/api-example">API</Link>
|
||||||
<a>API</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/admin">
|
<Link href="/admin">Admin</Link>
|
||||||
<a>Admin</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.navItem}>
|
<li className={styles.navItem}>
|
||||||
<Link href="/me">
|
<Link href="/me">Me</Link>
|
||||||
<a>Me</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import Header from "./header"
|
import Header from "./header"
|
||||||
import Footer from "./footer"
|
import Footer from "./footer"
|
||||||
import type { ReactChildren } from "react"
|
import type { ReactNode } from "react"
|
||||||
|
|
||||||
interface Props {
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Layout({ children }: Props) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import { withAuth } from "next-auth/middleware"
|
|||||||
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
||||||
export default withAuth({
|
export default withAuth({
|
||||||
callbacks: {
|
callbacks: {
|
||||||
authorized: ({ req, token }) =>
|
authorized({ req, token }) {
|
||||||
// /admin requires admin role, but /me only requires the user to be logged in.
|
// `/admin` requires admin role
|
||||||
req.nextUrl.pathname !== "/admin" || token?.userRole === "admin",
|
if (req.nextUrl.pathname === "/admin") {
|
||||||
|
return token?.userRole === "admin"
|
||||||
|
}
|
||||||
|
// `/me` only requires the user to be logged in
|
||||||
|
return !!token
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { SessionProvider } from "next-auth/react"
|
|||||||
import "./styles.css"
|
import "./styles.css"
|
||||||
|
|
||||||
import type { AppProps } from "next/app"
|
import type { AppProps } from "next/app"
|
||||||
|
import type { Session } from "next-auth"
|
||||||
|
|
||||||
// Use of the <SessionProvider> is mandatory to allow components that call
|
// Use of the <SessionProvider> is mandatory to allow components that call
|
||||||
// `useSession()` anywhere in your application to access the `session` object.
|
// `useSession()` anywhere in your application to access the `session` object.
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({
|
||||||
|
Component,
|
||||||
|
pageProps: { session, ...pageProps },
|
||||||
|
}: AppProps<{ session: Session }>) {
|
||||||
return (
|
return (
|
||||||
<SessionProvider session={pageProps.session} refetchInterval={0}>
|
<SessionProvider session={session}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { getToken } from "next-auth/jwt"
|
|||||||
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next"
|
import type { NextApiRequest, NextApiResponse } from "next"
|
||||||
|
|
||||||
const secret = process.env.NEXTAUTH_SECRET
|
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const token = await getToken({ req, secret })
|
// If you don't have the NEXTAUTH_SECRET environment variable set,
|
||||||
|
// you will have to pass your secret as `secret` to `getToken`
|
||||||
|
const token = await getToken({ req })
|
||||||
res.send(JSON.stringify(token, null, 2))
|
res.send(JSON.stringify(token, null, 2))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import Layout from "../components/layout"
|
|||||||
import AccessDenied from "../components/access-denied"
|
import AccessDenied from "../components/access-denied"
|
||||||
|
|
||||||
export default function ProtectedPage() {
|
export default function ProtectedPage() {
|
||||||
const { data: session, status } = useSession()
|
const { data: session } = useSession()
|
||||||
const loading = status === "loading"
|
|
||||||
const [content, setContent] = useState()
|
const [content, setContent] = useState()
|
||||||
|
|
||||||
// Fetch content from protected route
|
// Fetch content from protected route
|
||||||
@@ -20,8 +19,6 @@ export default function ProtectedPage() {
|
|||||||
fetchData()
|
fetchData()
|
||||||
}, [session])
|
}, [session])
|
||||||
|
|
||||||
// When rendering client side don't display anything until loading is complete
|
|
||||||
if (typeof window !== "undefined" && loading) return null
|
|
||||||
|
|
||||||
// If no session exists, display access denied message
|
// If no session exists, display access denied message
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ export default function ServerSidePage({ session }: { session: Session }) {
|
|||||||
<h1>Server Side Rendering</h1>
|
<h1>Server Side Rendering</h1>
|
||||||
<p>
|
<p>
|
||||||
This page uses the <strong>unstable_getServerSession()</strong> method
|
This page uses the <strong>unstable_getServerSession()</strong> method
|
||||||
in <strong>unstable_getServerSideProps()</strong>.
|
in <strong>getServerSideProps()</strong>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Using <strong>unstable_getServerSession()</strong> in{" "}
|
Using <strong>unstable_getServerSession()</strong> in{" "}
|
||||||
<strong>unstable_getServerSideProps()</strong> is the recommended
|
<strong>getServerSideProps()</strong> is the recommended approach if you
|
||||||
approach if you need to support Server Side Rendering with
|
need to support Server Side Rendering with authentication.
|
||||||
authentication.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The advantage of Server Side Rendering is this page does not require
|
The advantage of Server Side Rendering is this page does not require
|
||||||
|
|||||||
12
apps/playground-nuxt/.editorconfig
Normal file
12
apps/playground-nuxt/.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
4
apps/playground-nuxt/.eslintignore
Normal file
4
apps/playground-nuxt/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
tsconfig.json
|
||||||
|
package.json
|
||||||
10
apps/playground-nuxt/.eslintrc
Normal file
10
apps/playground-nuxt/.eslintrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"@nuxtjs/eslint-config-typescript"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"off"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
52
apps/playground-nuxt/.gitignore
vendored
Normal file
52
apps/playground-nuxt/.gitignore
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log*
|
||||||
|
|
||||||
|
# Temp directories
|
||||||
|
.temp
|
||||||
|
.tmp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Yarn
|
||||||
|
**/.yarn/cache
|
||||||
|
**/.yarn/*state*
|
||||||
|
|
||||||
|
# Generated dirs
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Nuxt
|
||||||
|
.nuxt
|
||||||
|
.output
|
||||||
|
.vercel_build_output
|
||||||
|
.build-*
|
||||||
|
.env
|
||||||
|
.netlify
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
reports
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Intellij idea
|
||||||
|
*.iml
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# OSX
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
.vercel
|
||||||
1
apps/playground-nuxt/.nuxtrc
Normal file
1
apps/playground-nuxt/.nuxtrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
imports.autoImport=false
|
||||||
108
apps/playground-nuxt/README.md
Normal file
108
apps/playground-nuxt/README.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# NextAuth + Nuxt 3 Playground
|
||||||
|
|
||||||
|
NextAuth.js is committed to bringing easy authentication to other frameworks. [#2294](https://github.com/nextauthjs/next-auth/issues/2294)
|
||||||
|
|
||||||
|
Nuxt 3 support with NextAuth.js is currently experimental. This directory contains a minimal, proof-of-concept application. Parts of this is expected to be abstracted away into a package like` @next-auth/nuxt.`
|
||||||
|
|
||||||
|
This package uses Nuxt's [module starter](https://github.com/nuxt/starter/tree/module).
|
||||||
|
|
||||||
|
Demo: https://next-auth-nuxt-demo.vercel.app
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Add the module to the modules section of `nuxt.config.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
// temporary module name.
|
||||||
|
modules: ['next-auth-nuxt'],
|
||||||
|
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
||||||
|
runtimeConfig: {
|
||||||
|
secret: process.env.NEXTAUTH_SECRET
|
||||||
|
github: {
|
||||||
|
clientId: process.env.GITHUB_CLIENT_ID,
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ~/server/api/auth/[...].ts
|
||||||
|
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
|
||||||
|
import GithubProvider from 'next-auth/providers/github'
|
||||||
|
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
|
||||||
|
export const authOptions = {
|
||||||
|
secret: runtimeConfig.secret,
|
||||||
|
providers: [
|
||||||
|
GithubProvider({
|
||||||
|
clientId: runtimeConfig.github.clientId,
|
||||||
|
clientSecret: runtimeConfig.github.clientSecret
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAuthNuxtHandler(authOptions)
|
||||||
|
```
|
||||||
|
|
||||||
|
All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by NextAuth.js.
|
||||||
|
|
||||||
|
### Frontend - Add Vue Composable
|
||||||
|
|
||||||
|
The `useSession()` Vue Composable is the easiest way to check if someone is signed in.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { data: session } = useSession()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="session">
|
||||||
|
Signed in as {{ session.user.email }} <br />
|
||||||
|
<button @click="signOut">Sign out</button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
Not signed in <br />
|
||||||
|
<button @click="signIn">Sign in</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend - API Route
|
||||||
|
|
||||||
|
To protect an API Route, you can use the `getServerSession()` method.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||||
|
import { authOptions } from '~/server/api/auth/[...]'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const session = await getServerSession(event, authOptions)
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
return {
|
||||||
|
content: 'This is protected content. You can access this content because you are signed in.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: 'You must be signed in to view the protected content on this page.'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- Run `pnpm dev:generate` to generate type stubs.
|
||||||
|
- Use `pnpm dev` to start `playground` in development mode.
|
||||||
1
apps/playground-nuxt/client.d.ts
vendored
Normal file
1
apps/playground-nuxt/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dist/runtime/client'
|
||||||
1
apps/playground-nuxt/handler.d.ts
vendored
Normal file
1
apps/playground-nuxt/handler.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './dist/runtime/server/handler'
|
||||||
49
apps/playground-nuxt/package.json
Normal file
49
apps/playground-nuxt/package.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "next-auth-nuxt",
|
||||||
|
"type": "module",
|
||||||
|
"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": {
|
||||||
|
"prepack": "nuxt-module-build",
|
||||||
|
"dev": "pnpm prepack && nuxi dev playground",
|
||||||
|
"dev:build": "nuxi build playground",
|
||||||
|
"dev:build:vercel": "NITRO_PRESET=vercel nuxi build playground",
|
||||||
|
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nuxt/kit": "^3.0.0-rc.13",
|
||||||
|
"h3": "^0.8.6",
|
||||||
|
"next-auth": "^4.16.2",
|
||||||
|
"pathe": "^0.3.9"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nuxt/module-builder": "^0.2.0",
|
||||||
|
"@nuxt/schema": "^3.0.0-rc.12",
|
||||||
|
"@nuxtjs/eslint-config-typescript": "^11.0.0",
|
||||||
|
"eslint": "^8.26.0",
|
||||||
|
"nuxt": "^3.0.0-rc.13",
|
||||||
|
"next-auth-nuxt": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
apps/playground-nuxt/playground/.env.example
Normal file
4
apps/playground-nuxt/playground/.env.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
GITHUB_CLIENT_ID=
|
||||||
|
GITHUB_CLIENT_SECRET=
|
||||||
|
NEXTAUTH_URL=
|
||||||
|
NEXTAUTH_SECRET=
|
||||||
40
apps/playground-nuxt/playground/app.vue
Normal file
40
apps/playground-nuxt/playground/app.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Header />
|
||||||
|
<NuxtPage />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
padding: 0 1rem 1rem 1rem;
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #fff;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
li,
|
||||||
|
p {
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
background: #ccc;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
height: 10rem;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: .5rem;
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Access Denied</h1>
|
||||||
|
<p>
|
||||||
|
<a href="/api/auth/signin">You must be signed in to view this page</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
30
apps/playground-nuxt/playground/components/Footer.vue
Normal file
30
apps/playground-nuxt/playground/components/Footer.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<footer class="fotter">
|
||||||
|
<hr>
|
||||||
|
<ul class="navItems">
|
||||||
|
<li class="navItem">
|
||||||
|
<a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-nuxt">Demo GitHub</a>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<a href="https://next-auth.js.org">Next.js Documentation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.footer {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItems {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItem {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
155
apps/playground-nuxt/playground/components/Header.vue
Normal file
155
apps/playground-nuxt/playground/components/Header.vue
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSession, signIn, signOut, computed } from '#imports'
|
||||||
|
|
||||||
|
const { data: session, status } = useSession()
|
||||||
|
const loading = computed(() => status.value === 'loading')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<div class="signedInStatus">
|
||||||
|
<p :class="['nojs-show', !session && loading ? 'loading' : 'loaded']">
|
||||||
|
<template v-if="session">
|
||||||
|
<span v-if="session.user?.image" :style="{ backgroundImage: `url(${session.user.image})` }" class="avatar" />
|
||||||
|
<span class="signedInText">
|
||||||
|
<small>Signed in as</small><br>
|
||||||
|
<strong>{{ session.user?.email || session.user?.name }}</strong>
|
||||||
|
</span>
|
||||||
|
<a href="/api/auth/signout" class="button" @click.prevent="signOut">Sign out</a>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="notSignedInText">You are not signed in</span>
|
||||||
|
<a href="/api/auth/signin" class="buttonPrimary" @click.prevent="signIn">Sign in</a>
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="navItems">
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/">
|
||||||
|
Home
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/client">
|
||||||
|
Client
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/server">
|
||||||
|
Server
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/protected">
|
||||||
|
Protected
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
<li class="navItem">
|
||||||
|
<NuxtLink to="/api-example">
|
||||||
|
API
|
||||||
|
</NuxtLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.nojs-show {
|
||||||
|
opacity: 1;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signedInStatus {
|
||||||
|
display: block;
|
||||||
|
min-height: 4rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.loaded {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
opacity: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0 0 .6rem .6rem;
|
||||||
|
padding: .6rem 1rem;
|
||||||
|
margin: 0;
|
||||||
|
background-color: rgba(0,0,0,.05);
|
||||||
|
transition: all 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
top: -2rem;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signedInText,
|
||||||
|
.notSignedInText {
|
||||||
|
position: absolute;
|
||||||
|
padding-top: .8rem;
|
||||||
|
left: 1rem;
|
||||||
|
right: 6.5rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inherit;
|
||||||
|
z-index: 1;
|
||||||
|
line-height: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signedInText {
|
||||||
|
padding-top: 0rem;
|
||||||
|
left: 4.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
border-radius: 2rem;
|
||||||
|
float: left;
|
||||||
|
height: 2.8rem;
|
||||||
|
width: 2.8rem;
|
||||||
|
background-color: white;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.buttonPrimary {
|
||||||
|
float: right;
|
||||||
|
margin-right: -.4rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: .3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.4rem;
|
||||||
|
padding: .7rem .8rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonPrimary {
|
||||||
|
background-color: #346df1;
|
||||||
|
border-color: #346df1;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: .7rem 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonPrimary:hover {
|
||||||
|
box-shadow: inset 0 0 5rem rgba(0,0,0,0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItems {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navItem {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
apps/playground-nuxt/playground/nuxt.config.ts
Normal file
20
apps/playground-nuxt/playground/nuxt.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import MyModule from '../src/module'
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: [
|
||||||
|
MyModule
|
||||||
|
],
|
||||||
|
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
|
||||||
|
runtimeConfig: {
|
||||||
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
github: {
|
||||||
|
clientId: process.env.GITHUB_CLIENT_ID,
|
||||||
|
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
|
||||||
|
// Fix for GithubProvider is not a function error in Vite
|
||||||
|
alias: {
|
||||||
|
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
|
||||||
|
}
|
||||||
|
})
|
||||||
4
apps/playground-nuxt/playground/package.json
Normal file
4
apps/playground-nuxt/playground/package.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "playground",
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
15
apps/playground-nuxt/playground/pages/api-example.vue
Normal file
15
apps/playground-nuxt/playground/pages/api-example.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>API Example</h1>
|
||||||
|
<p>The examples below show responses from the example API endpoints.</p>
|
||||||
|
<p>
|
||||||
|
<em>You must be signed in to see responses.</em>
|
||||||
|
</p>
|
||||||
|
<h2>Session</h2>
|
||||||
|
<p>/api/examples/session</p>
|
||||||
|
<iframe src="/api/examples/session" />
|
||||||
|
<h2>JSON Web Token</h2>
|
||||||
|
<p>/api/examples/jwt</p>
|
||||||
|
<iframe src="/api/examples/jwt" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
18
apps/playground-nuxt/playground/pages/client.vue
Normal file
18
apps/playground-nuxt/playground/pages/client.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Client Side Rendering</h1>
|
||||||
|
<p>
|
||||||
|
This page uses the <strong>useSession()</strong> Vue Composable in the <strong><Header/></strong> component.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The <strong>useSession()</strong> Vue Composable is easy to use and allows pages to render very quickly.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The advantage of this approach is that session state is shared between pages by using a provided session via <strong>Vue Plugin</strong> so
|
||||||
|
that navigation between pages using <strong>useSession()</strong> is very fast.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The disadvantage of <strong>useSession()</strong> is that it requires client side JavaScript.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
8
apps/playground-nuxt/playground/pages/index.vue
Normal file
8
apps/playground-nuxt/playground/pages/index.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Nuxt 3 + NextAuth.js Example</h1>
|
||||||
|
<p>
|
||||||
|
This is an example site to demonstrate how to use <a href="https://v3.nuxtjs.org/">Nuxt 3</a> with <a href="https://next-auth.js.org">NextAuth.js</a> for authentication.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
19
apps/playground-nuxt/playground/pages/protected.vue
Normal file
19
apps/playground-nuxt/playground/pages/protected.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSession, useFetch, useLazyFetch } from '#imports'
|
||||||
|
import AccessDenied from '~/components/AccessDenied.vue'
|
||||||
|
|
||||||
|
const { data: session } = useSession()
|
||||||
|
const { data } = await useLazyFetch('/api/examples/protected', {
|
||||||
|
server: false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AccessDenied v-if="!session" />
|
||||||
|
<template v-else>
|
||||||
|
<h1>Protected Page</h1>
|
||||||
|
<p><strong>{{ data?.content || "\u00a0" }}</strong></p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
24
apps/playground-nuxt/playground/pages/server.vue
Normal file
24
apps/playground-nuxt/playground/pages/server.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useFetch } from '#imports'
|
||||||
|
|
||||||
|
await useFetch('/api/examples/session')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Server Side Rendering</h1>
|
||||||
|
<p>
|
||||||
|
This page uses the <strong>getServerSession()</strong> method inside an api route and is fetched using the <strong>useFetch()</strong> composable.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Using <strong>getServerSession()</strong> is the recommended approach if you need to
|
||||||
|
support Server Side Rendering with authentication.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The advantage of Server Side Rendering is this page does not require client side JavaScript.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The disadvantage of Server Side Rendering is that this page is slower to render.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
apps/playground-nuxt/playground/server/api/auth/[...].ts
Normal file
17
apps/playground-nuxt/playground/server/api/auth/[...].ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
|
||||||
|
import GithubProvider from 'next-auth/providers/github'
|
||||||
|
import type { NextAuthOptions } from 'next-auth'
|
||||||
|
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
secret: runtimeConfig.secret,
|
||||||
|
providers: [
|
||||||
|
GithubProvider({
|
||||||
|
clientId: runtimeConfig.github.clientId,
|
||||||
|
clientSecret: runtimeConfig.github.clientSecret
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAuthNuxtHandler(authOptions)
|
||||||
10
apps/playground-nuxt/playground/server/api/examples/jwt.ts
Normal file
10
apps/playground-nuxt/playground/server/api/examples/jwt.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { getToken } from 'next-auth/jwt'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
// @ts-expect-error: cookies property is not present in h3
|
||||||
|
event.req.cookies = parseCookies(event)
|
||||||
|
const token = await getToken({
|
||||||
|
req: event.req
|
||||||
|
})
|
||||||
|
return token
|
||||||
|
})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||||
|
import { authOptions } from '../auth/[...]'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const session = await getServerSession(event, authOptions)
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
return {
|
||||||
|
content: 'This is protected content. You can access this content because you are signed in.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: 'You must be signed in to view the protected content on this page.'
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { getServerSession } from 'next-auth-nuxt/handler'
|
||||||
|
import { authOptions } from '../auth/[...]'
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const session = await getServerSession(event, authOptions)
|
||||||
|
return session
|
||||||
|
})
|
||||||
6386
apps/playground-nuxt/pnpm-lock.yaml
generated
Normal file
6386
apps/playground-nuxt/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
apps/playground-nuxt/pnpm-workspace.yaml
Normal file
2
apps/playground-nuxt/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- playground
|
||||||
40
apps/playground-nuxt/src/module.ts
Normal file
40
apps/playground-nuxt/src/module.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { addImports, addPlugin, defineNuxtModule, extendViteConfig } from '@nuxt/kit'
|
||||||
|
import { resolve } from 'pathe'
|
||||||
|
|
||||||
|
export interface ModuleOptions {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtModule<ModuleOptions>({
|
||||||
|
meta: {
|
||||||
|
name: 'next-auth-nuxt',
|
||||||
|
configKey: 'auth'
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
},
|
||||||
|
async setup (_options, nuxt) {
|
||||||
|
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
|
||||||
|
nuxt.options.build.transpile.push(runtimeDir)
|
||||||
|
|
||||||
|
addPlugin(resolve(runtimeDir, 'plugin.client'))
|
||||||
|
|
||||||
|
// Composables are auto-imported in client.
|
||||||
|
const client = resolve(runtimeDir, 'client')
|
||||||
|
await addImports([
|
||||||
|
{ name: 'getSession', from: client },
|
||||||
|
{ name: 'getCsrfToken', from: client },
|
||||||
|
{ name: 'getProviders', from: client },
|
||||||
|
{ name: 'signIn', from: client },
|
||||||
|
{ name: 'signOut', from: client },
|
||||||
|
{ name: 'useSession', from: client }
|
||||||
|
])
|
||||||
|
|
||||||
|
// We can safely expose this to client.
|
||||||
|
extendViteConfig((config) => {
|
||||||
|
config.define = config.define || {}
|
||||||
|
config.define['process.env.NEXTAUTH_URL'] = JSON.stringify(process.env.NEXTAUTH_URL)
|
||||||
|
config.define['process.env.NEXTAUTH_URL_INTERNAL'] = JSON.stringify(process.env.NEXTAUTH_URL_INTERNAL)
|
||||||
|
config.define['process.env.VERCEL_URL'] = JSON.stringify(process.env.VERCEL_URL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
369
apps/playground-nuxt/src/runtime/client/index.ts
Normal file
369
apps/playground-nuxt/src/runtime/client/index.ts
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
import type { NextAuthClientConfig } from 'next-auth/client/_utils'
|
||||||
|
import type { Plugin, Ref } from 'vue'
|
||||||
|
import { ref, reactive, computed, inject, toRefs } from 'vue'
|
||||||
|
import { BroadcastChannel, apiBaseUrl, fetchData, now } from 'next-auth/client/_utils'
|
||||||
|
import type { Session } from 'next-auth'
|
||||||
|
import type {
|
||||||
|
BuiltInProviderType,
|
||||||
|
RedirectableProviderType
|
||||||
|
} from 'next-auth/providers'
|
||||||
|
import type { H3EventContext } from 'h3'
|
||||||
|
import parseUrl from '../lib/parse-url'
|
||||||
|
import _logger, { proxyLogger } from '../lib/logger'
|
||||||
|
import type {
|
||||||
|
ClientSafeProvider,
|
||||||
|
LiteralUnion,
|
||||||
|
SessionProviderProps,
|
||||||
|
SignInAuthorizationParams,
|
||||||
|
SignInOptions,
|
||||||
|
SignInResponse,
|
||||||
|
SignOutParams,
|
||||||
|
SignOutResponse
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
// This behaviour mirrors the default behaviour for getting the site name that
|
||||||
|
// happens server side in server/index.js
|
||||||
|
// 1. An empty value is legitimate when the code is being invoked client side as
|
||||||
|
// relative URLs are valid in that context and so defaults to empty.
|
||||||
|
// 2. When invoked server side the value is picked up from an environment
|
||||||
|
// variable and defaults to 'http://localhost:3000'.
|
||||||
|
const __NEXTAUTH: NextAuthClientConfig = {
|
||||||
|
baseUrl: parseUrl(process.env.NEXTAUTH_URL ?? process.env.VERCEL_URL).origin,
|
||||||
|
basePath: parseUrl(process.env.NEXTAUTH_URL).path,
|
||||||
|
baseUrlServer: parseUrl(
|
||||||
|
process.env.NEXTAUTH_URL_INTERNAL ??
|
||||||
|
process.env.NEXTAUTH_URL ??
|
||||||
|
process.env.VERCEL_URL
|
||||||
|
).origin,
|
||||||
|
basePathServer: parseUrl(
|
||||||
|
process.env.NEXTAUTH_URL_INTERNAL ?? process.env.NEXTAUTH_URL
|
||||||
|
).path,
|
||||||
|
_lastSync: 0,
|
||||||
|
_session: undefined,
|
||||||
|
_getSession: () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CtxOrReq {
|
||||||
|
req?: H3EventContext['req']
|
||||||
|
event?: { req: H3EventContext['req'] }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetSessionParams = CtxOrReq & {
|
||||||
|
event?: 'storage' | 'timer' | 'hidden' | string
|
||||||
|
triggerEvent?: boolean
|
||||||
|
broadcast?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = proxyLogger(_logger, __NEXTAUTH.basePath)
|
||||||
|
|
||||||
|
const broadcast = BroadcastChannel()
|
||||||
|
|
||||||
|
function isServer () {
|
||||||
|
return (process as any).server
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSession (params?: GetSessionParams) {
|
||||||
|
const session = await fetchData<Session>(
|
||||||
|
'session',
|
||||||
|
__NEXTAUTH,
|
||||||
|
logger,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
if (params?.broadcast ?? true) { broadcast.post({ event: 'session', data: { trigger: 'getSession' } }) }
|
||||||
|
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current Cross Site Request Forgery Token (CSRF Token)
|
||||||
|
* required to make POST requests (e.g. for signing in and signing out).
|
||||||
|
* You likely only need to use this if you are not using the built-in
|
||||||
|
* `signIn()` and `signOut()` methods.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken)
|
||||||
|
*/
|
||||||
|
export async function getCsrfToken (params?: CtxOrReq) {
|
||||||
|
const response = await fetchData<{ csrfToken: string }>(
|
||||||
|
'csrf',
|
||||||
|
__NEXTAUTH,
|
||||||
|
logger,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
return response?.csrfToken
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It calls `/api/auth/providers` and returns
|
||||||
|
* a list of the currently configured authentication providers.
|
||||||
|
* It can be useful if you are creating a dynamic custom sign in page.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#getproviders)
|
||||||
|
*/
|
||||||
|
export async function getProviders () {
|
||||||
|
return await fetchData<
|
||||||
|
Record<LiteralUnion<BuiltInProviderType>, ClientSafeProvider>
|
||||||
|
>('providers', __NEXTAUTH, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client-side method to initiate a signin flow
|
||||||
|
* or send the user to the signin page listing all possible providers.
|
||||||
|
* Automatically adds the CSRF token to the request.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
|
||||||
|
*/
|
||||||
|
export async function signIn<
|
||||||
|
P extends RedirectableProviderType | undefined = undefined,
|
||||||
|
> (
|
||||||
|
provider?: LiteralUnion<BuiltInProviderType>,
|
||||||
|
options?: SignInOptions,
|
||||||
|
authorizationParams?: SignInAuthorizationParams
|
||||||
|
): Promise<
|
||||||
|
P extends RedirectableProviderType ? SignInResponse | undefined : undefined
|
||||||
|
> {
|
||||||
|
const { callbackUrl = window.location.href, redirect = true } = options ?? {}
|
||||||
|
|
||||||
|
const baseUrl = apiBaseUrl(__NEXTAUTH)
|
||||||
|
const providers = await getProviders()
|
||||||
|
|
||||||
|
if (!providers) {
|
||||||
|
window.location.href = `${baseUrl}/error`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider || !(provider in providers)) {
|
||||||
|
window.location.href = `${baseUrl}/signin?${new URLSearchParams({
|
||||||
|
callbackUrl
|
||||||
|
})}`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCredentials = providers[provider].type === 'credentials'
|
||||||
|
const isEmail = providers[provider].type === 'email'
|
||||||
|
const isSupportingReturn = isCredentials || isEmail
|
||||||
|
|
||||||
|
const signInUrl = `${baseUrl}/${
|
||||||
|
isCredentials ? 'callback' : 'signin'
|
||||||
|
}/${provider}`
|
||||||
|
|
||||||
|
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
|
||||||
|
|
||||||
|
const res = await fetch(_signInUrl, {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
// @ts-expect-error: Internal
|
||||||
|
body: new URLSearchParams({
|
||||||
|
...options,
|
||||||
|
csrfToken: await getCsrfToken(),
|
||||||
|
callbackUrl,
|
||||||
|
json: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
if (redirect || !isSupportingReturn) {
|
||||||
|
const url = data.url ?? callbackUrl
|
||||||
|
window.location.href = url
|
||||||
|
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||||
|
if (url.includes('#')) { window.location.reload() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = new URL(data.url).searchParams.get('error')
|
||||||
|
|
||||||
|
if (res.ok) { await __NEXTAUTH._getSession({ event: 'storage' }) }
|
||||||
|
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
status: res.status,
|
||||||
|
ok: res.ok,
|
||||||
|
url: error ? null : data.url
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the user out, by removing the session cookie.
|
||||||
|
* Automatically adds the CSRF token to the request.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
|
||||||
|
*/
|
||||||
|
export async function signOut<R extends boolean = true> (
|
||||||
|
options?: SignOutParams<R>
|
||||||
|
): Promise<R extends true ? undefined : SignOutResponse> {
|
||||||
|
const { callbackUrl = window.location.href } = options ?? {}
|
||||||
|
const baseUrl = apiBaseUrl(__NEXTAUTH)
|
||||||
|
const fetchOptions = {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
// @ts-expect-error: Internal
|
||||||
|
body: new URLSearchParams({
|
||||||
|
csrfToken: await getCsrfToken(),
|
||||||
|
callbackUrl,
|
||||||
|
json: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const res = await fetch(`${baseUrl}/signout`, fetchOptions)
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
broadcast.post({ event: 'session', data: { trigger: 'signout' } })
|
||||||
|
|
||||||
|
if (options?.redirect ?? true) {
|
||||||
|
const url = data.url ?? callbackUrl
|
||||||
|
window.location.href = url
|
||||||
|
// If url contains a hash, the browser does not reload the page. We reload manually
|
||||||
|
if (url.includes('#')) { window.location.reload() }
|
||||||
|
// @ts-expect-error: Internal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await __NEXTAUTH._getSession({ event: 'storage' })
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SessionProviderPlugin (options: SessionProviderProps): Plugin {
|
||||||
|
return {
|
||||||
|
install (app) {
|
||||||
|
const { basePath } = options
|
||||||
|
|
||||||
|
if (basePath) { __NEXTAUTH.basePath = basePath }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If session was `null`, there was an attempt to fetch it,
|
||||||
|
* but it failed, but we still treat it as a valid initial value.
|
||||||
|
*/
|
||||||
|
const hasInitialSession = options.session !== undefined
|
||||||
|
|
||||||
|
/** If session was passed, initialize as already synced */
|
||||||
|
__NEXTAUTH._lastSync = hasInitialSession ? now() : 0
|
||||||
|
|
||||||
|
if (hasInitialSession) { __NEXTAUTH._session = options.session }
|
||||||
|
|
||||||
|
const session = ref(options.session)
|
||||||
|
|
||||||
|
/** If session was passed, initialize as not loading */
|
||||||
|
const loading = ref(!hasInitialSession)
|
||||||
|
|
||||||
|
__NEXTAUTH._getSession = async ({ event } = {}) => {
|
||||||
|
try {
|
||||||
|
const storageEvent = event === 'storage'
|
||||||
|
// We should always update if we don't have a client session yet
|
||||||
|
// or if there are events from other tabs/windows
|
||||||
|
if (storageEvent || __NEXTAUTH._session === undefined) {
|
||||||
|
__NEXTAUTH._lastSync = now()
|
||||||
|
__NEXTAUTH._session = await getSession({
|
||||||
|
broadcast: !storageEvent
|
||||||
|
})
|
||||||
|
session.value = __NEXTAUTH._session
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
// If there is no time defined for when a session should be considered
|
||||||
|
// stale, then it's okay to use the value we have until an event is
|
||||||
|
// triggered which updates it
|
||||||
|
!event ||
|
||||||
|
// If the client doesn't have a session then we don't need to call
|
||||||
|
// the server to check if it does (if they have signed in via another
|
||||||
|
// tab or window that will come through as a "stroage" event
|
||||||
|
// event anyway)
|
||||||
|
__NEXTAUTH._session === null ||
|
||||||
|
// Bail out early if the client session is not stale yet
|
||||||
|
now() < __NEXTAUTH._lastSync
|
||||||
|
) { return }
|
||||||
|
|
||||||
|
// An event or session staleness occurred, update the client session.
|
||||||
|
__NEXTAUTH._lastSync = now()
|
||||||
|
__NEXTAUTH._session = await getSession()
|
||||||
|
session.value = __NEXTAUTH._session
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('CLIENT_SESSION_ERROR', error as Error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__NEXTAUTH._getSession()
|
||||||
|
|
||||||
|
const { refetchOnWindowFocus = true } = options
|
||||||
|
|
||||||
|
// Listen for when the page is visible, if the user switches tabs
|
||||||
|
// and makes our tab visible again, re-fetch the session, but only if
|
||||||
|
// this feature is not disabled.
|
||||||
|
const visibilityHandler = () => {
|
||||||
|
if (refetchOnWindowFocus && document.visibilityState === 'visible') { __NEXTAUTH._getSession({ event: 'visibilitychange' }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', visibilityHandler, false)
|
||||||
|
|
||||||
|
const unsubscribeFromBroadcast = broadcast.receive(() =>
|
||||||
|
__NEXTAUTH._getSession({ event: 'storage' })
|
||||||
|
)
|
||||||
|
|
||||||
|
const { refetchInterval } = options
|
||||||
|
let refetchIntervalTimer: NodeJS.Timer
|
||||||
|
|
||||||
|
if (refetchInterval) {
|
||||||
|
refetchIntervalTimer = setInterval(() => {
|
||||||
|
if (__NEXTAUTH._session) { __NEXTAUTH._getSession({ event: 'poll' }) }
|
||||||
|
}, refetchInterval * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalUnmount = app.unmount
|
||||||
|
app.unmount = function nextAuthUnmount () {
|
||||||
|
document.removeEventListener('visibilitychange', visibilityHandler, false)
|
||||||
|
unsubscribeFromBroadcast?.()
|
||||||
|
clearInterval(refetchIntervalTimer)
|
||||||
|
__NEXTAUTH._lastSync = 0
|
||||||
|
__NEXTAUTH._session = undefined
|
||||||
|
__NEXTAUTH._getSession = () => {}
|
||||||
|
originalUnmount()
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = computed(() => loading.value ? 'loading' : session.value ? 'authenticated' : 'unauthenticated')
|
||||||
|
const value = reactive({
|
||||||
|
data: session,
|
||||||
|
status
|
||||||
|
})
|
||||||
|
|
||||||
|
app.provide('SessionKey', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vue Composable that gives you access
|
||||||
|
* to the logged in user's session data.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/getting-started/client#usesession)
|
||||||
|
*/
|
||||||
|
export function useSession (): {
|
||||||
|
data: Ref<SessionProviderProps['session']>;
|
||||||
|
status: Ref<string>;
|
||||||
|
} {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return {
|
||||||
|
data: ref(null),
|
||||||
|
status: ref('loading')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = inject<{
|
||||||
|
data: SessionProviderProps['session']
|
||||||
|
status: string
|
||||||
|
}>('SessionKey')
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Could not resolve provided session value')
|
||||||
|
}
|
||||||
|
const { data, status } = toRefs(value)
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
115
apps/playground-nuxt/src/runtime/lib/errors.ts
Normal file
115
apps/playground-nuxt/src/runtime/lib/errors.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import type { Adapter } from 'next-auth/adapters'
|
||||||
|
import type { EventCallbacks, LoggerInstance } from 'next-auth'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as the default `Error`, but it is JSON serializable.
|
||||||
|
* @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
|
||||||
|
*/
|
||||||
|
export class UnknownError extends Error {
|
||||||
|
code: string
|
||||||
|
constructor (error: Error | string) {
|
||||||
|
super((error as Error)?.message ?? error)
|
||||||
|
this.name = 'UnknownError'
|
||||||
|
this.code = (error as any).code
|
||||||
|
if (error instanceof Error) { this.stack = error.stack }
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON () {
|
||||||
|
return {
|
||||||
|
name: this.name,
|
||||||
|
message: this.message,
|
||||||
|
stack: this.stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OAuthCallbackError extends UnknownError {
|
||||||
|
name = 'OAuthCallbackError'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an Email address is already associated with an account
|
||||||
|
* but the user is trying an OAuth account that is not linked to it.
|
||||||
|
*/
|
||||||
|
export class AccountNotLinkedError extends UnknownError {
|
||||||
|
name = 'AccountNotLinkedError'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MissingAPIRoute extends UnknownError {
|
||||||
|
name = 'MissingAPIRouteError'
|
||||||
|
code = 'MISSING_NEXTAUTH_API_ROUTE_ERROR'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MissingSecret extends UnknownError {
|
||||||
|
name = 'MissingSecretError'
|
||||||
|
code = 'NO_SECRET'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MissingAuthorize extends UnknownError {
|
||||||
|
name = 'MissingAuthorizeError'
|
||||||
|
code = 'CALLBACK_CREDENTIALS_HANDLER_ERROR'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MissingAdapter extends UnknownError {
|
||||||
|
name = 'MissingAdapterError'
|
||||||
|
code = 'EMAIL_REQUIRES_ADAPTER_ERROR'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnsupportedStrategy extends UnknownError {
|
||||||
|
name = 'UnsupportedStrategyError'
|
||||||
|
code = 'CALLBACK_CREDENTIALS_JWT_ERROR'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Method = (...args: any[]) => Promise<any>
|
||||||
|
|
||||||
|
export function upperSnake (s: string) {
|
||||||
|
return s.replace(/([A-Z])/g, '_$1').toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function capitalize (s: string) {
|
||||||
|
return `${s[0].toUpperCase()}${s.slice(1)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an object of methods and adds error handling.
|
||||||
|
*/
|
||||||
|
export function eventsErrorHandler (
|
||||||
|
methods: Partial<EventCallbacks>,
|
||||||
|
logger: LoggerInstance
|
||||||
|
): Partial<EventCallbacks> {
|
||||||
|
return Object.keys(methods).reduce<any>((acc, name) => {
|
||||||
|
acc[name] = async (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
const method: Method = methods[name as keyof Method]
|
||||||
|
return await method(...args)
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handles adapter induced errors. */
|
||||||
|
export function adapterErrorHandler (
|
||||||
|
adapter: Adapter | undefined,
|
||||||
|
logger: LoggerInstance
|
||||||
|
): Adapter | undefined {
|
||||||
|
if (!adapter) { return }
|
||||||
|
|
||||||
|
return Object.keys(adapter).reduce<any>((acc, name) => {
|
||||||
|
acc[name] = async (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
logger.debug(`adapter_${name}`, { args })
|
||||||
|
const method: Method = adapter[name as keyof Method]
|
||||||
|
return await method(...args)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`adapter_error_${name}`, error as Error)
|
||||||
|
const e = new UnknownError(error as Error)
|
||||||
|
e.name = `${capitalize(name)}Error`
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
113
apps/playground-nuxt/src/runtime/lib/logger.ts
Normal file
113
apps/playground-nuxt/src/runtime/lib/logger.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { UnknownError } from './errors'
|
||||||
|
|
||||||
|
// TODO: better typing
|
||||||
|
/** Makes sure that error is always serializable */
|
||||||
|
function formatError (o: unknown): unknown {
|
||||||
|
if (o instanceof Error && !(o instanceof UnknownError)) { return { message: o.message, stack: o.stack, name: o.name } }
|
||||||
|
|
||||||
|
if (hasErrorProperty(o)) {
|
||||||
|
o.error = formatError(o.error) as Error
|
||||||
|
o.message = o.message ?? o.error.message
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasErrorProperty (
|
||||||
|
x: unknown
|
||||||
|
): x is { error: Error; [key: string]: unknown } {
|
||||||
|
return !!(x as any)?.error
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WarningCode =
|
||||||
|
| 'NEXTAUTH_URL'
|
||||||
|
| 'NO_SECRET'
|
||||||
|
| 'TWITTER_OAUTH_2_BETA'
|
||||||
|
| 'DEBUG_ENABLED'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override any of the methods, and the rest will use the default logger.
|
||||||
|
*
|
||||||
|
* [Documentation](https://next-auth.js.org/configuration/options#logger)
|
||||||
|
*/
|
||||||
|
export interface LoggerInstance extends Record<string, Function> {
|
||||||
|
warn: (code: WarningCode) => void
|
||||||
|
error: (
|
||||||
|
code: string,
|
||||||
|
/**
|
||||||
|
* Either an instance of (JSON serializable) Error
|
||||||
|
* or an object that contains some debug information.
|
||||||
|
* (Error is still available through `metadata.error`)
|
||||||
|
*/
|
||||||
|
metadata: Error | { error: Error; [key: string]: unknown }
|
||||||
|
) => void
|
||||||
|
debug: (code: string, metadata: unknown) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const _logger: LoggerInstance = {
|
||||||
|
error (code, metadata) {
|
||||||
|
metadata = formatError(metadata) as Error
|
||||||
|
console.error(
|
||||||
|
`[next-auth][error][${code}]`,
|
||||||
|
`\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`,
|
||||||
|
metadata.message,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
},
|
||||||
|
warn (code) {
|
||||||
|
console.warn(
|
||||||
|
`[next-auth][warn][${code}]`,
|
||||||
|
`\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
debug (code, metadata) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`[next-auth][debug][${code}]`, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the built-in logger with user's implementation.
|
||||||
|
* Any `undefined` level will use the default logger.
|
||||||
|
*/
|
||||||
|
export function setLogger (
|
||||||
|
newLogger: Partial<LoggerInstance> = {},
|
||||||
|
debug?: boolean
|
||||||
|
) {
|
||||||
|
// Turn off debug logging if `debug` isn't set to `true`
|
||||||
|
if (!debug) { _logger.debug = () => {} }
|
||||||
|
|
||||||
|
if (newLogger.error) { _logger.error = newLogger.error }
|
||||||
|
if (newLogger.warn) { _logger.warn = newLogger.warn }
|
||||||
|
if (newLogger.debug) { _logger.debug = newLogger.debug }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default _logger
|
||||||
|
|
||||||
|
/** Serializes client-side log messages and sends them to the server */
|
||||||
|
export function proxyLogger (
|
||||||
|
logger: LoggerInstance = _logger,
|
||||||
|
basePath?: string
|
||||||
|
): LoggerInstance {
|
||||||
|
try {
|
||||||
|
if (typeof window === 'undefined') { return logger }
|
||||||
|
|
||||||
|
const clientLogger: Record<string, unknown> = {}
|
||||||
|
for (const level in logger) {
|
||||||
|
clientLogger[level] = (code: string, metadata: Error) => {
|
||||||
|
_logger[level](code, metadata) // Logs to console
|
||||||
|
|
||||||
|
if (level === 'error') {
|
||||||
|
metadata = formatError(metadata) as Error
|
||||||
|
}(metadata as any).client = true
|
||||||
|
const url = `${basePath}/_log`
|
||||||
|
const body = new URLSearchParams({ level, code, ...(metadata as any) })
|
||||||
|
if (navigator.sendBeacon) { return navigator.sendBeacon(url, body) }
|
||||||
|
|
||||||
|
return fetch(url, { method: 'POST', body, keepalive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clientLogger as unknown as LoggerInstance
|
||||||
|
} catch {
|
||||||
|
return _logger
|
||||||
|
}
|
||||||
|
}
|
||||||
34
apps/playground-nuxt/src/runtime/lib/parse-url.ts
Normal file
34
apps/playground-nuxt/src/runtime/lib/parse-url.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
7
apps/playground-nuxt/src/runtime/plugin.client.ts
Normal file
7
apps/playground-nuxt/src/runtime/plugin.client.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// @ts-expect-error: Nuxt auto-import
|
||||||
|
import { defineNuxtPlugin } from '#app'
|
||||||
|
import { SessionProviderPlugin } from './client'
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.vueApp.use(SessionProviderPlugin({}))
|
||||||
|
})
|
||||||
93
apps/playground-nuxt/src/runtime/server/handler.ts
Normal file
93
apps/playground-nuxt/src/runtime/server/handler.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import type { NextAuthAction, NextAuthOptions, Session } from 'next-auth'
|
||||||
|
import type { RequestInternal } from 'next-auth/core'
|
||||||
|
import { NextAuthHandler } from 'next-auth/core'
|
||||||
|
import {
|
||||||
|
appendHeader,
|
||||||
|
defineEventHandler,
|
||||||
|
isMethod,
|
||||||
|
sendRedirect,
|
||||||
|
setCookie,
|
||||||
|
readBody,
|
||||||
|
parseCookies,
|
||||||
|
getQuery
|
||||||
|
} from 'h3'
|
||||||
|
import type { H3Event } from 'h3'
|
||||||
|
|
||||||
|
export function NextAuthNuxtHandler (options: NextAuthOptions) {
|
||||||
|
return defineEventHandler(async (event) => {
|
||||||
|
// Catch-all route params in Nuxt goes to the underscore property
|
||||||
|
const nextauth = event.context.params._.split('/')
|
||||||
|
|
||||||
|
const req: RequestInternal | Request = {
|
||||||
|
host: process.env.NEXTAUTH_URL,
|
||||||
|
body: undefined,
|
||||||
|
query: getQuery(event),
|
||||||
|
headers: event.req.headers,
|
||||||
|
method: event.req.method,
|
||||||
|
cookies: parseCookies(event),
|
||||||
|
action: nextauth[0] as NextAuthAction,
|
||||||
|
providerId: nextauth[1],
|
||||||
|
error: nextauth[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMethod(event, 'POST')) {
|
||||||
|
req.body = await readBody(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await NextAuthHandler({
|
||||||
|
req,
|
||||||
|
options
|
||||||
|
})
|
||||||
|
|
||||||
|
const { headers, cookies, body, redirect, status = 200 } = response
|
||||||
|
event.res.statusCode = status
|
||||||
|
|
||||||
|
headers?.forEach((header) => {
|
||||||
|
appendHeader(event, header.key, header.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
cookies?.forEach((cookie) => {
|
||||||
|
setCookie(event, cookie.name, cookie.value, cookie.options)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
if (isMethod(event, 'POST')) {
|
||||||
|
const body = await readBody(event)
|
||||||
|
if (body?.json !== 'true') { await sendRedirect(event, redirect, 302) }
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: redirect
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await sendRedirect(event, redirect, 302)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return body
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerSession (
|
||||||
|
event: H3Event,
|
||||||
|
options: NextAuthOptions
|
||||||
|
): Promise<Session | null> {
|
||||||
|
options.secret = process.env.NEXTAUTH_SECRET
|
||||||
|
|
||||||
|
const session = await NextAuthHandler<Session>({
|
||||||
|
req: {
|
||||||
|
host: process.env.NEXTAUTH_URL,
|
||||||
|
action: 'session',
|
||||||
|
method: 'GET',
|
||||||
|
cookies: parseCookies(event),
|
||||||
|
headers: event.req.headers
|
||||||
|
},
|
||||||
|
options
|
||||||
|
})
|
||||||
|
|
||||||
|
const { body } = session
|
||||||
|
|
||||||
|
if (body && Object.keys(body).length) {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
78
apps/playground-nuxt/src/runtime/types.ts
Normal file
78
apps/playground-nuxt/src/runtime/types.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import type { Session } from 'next-auth'
|
||||||
|
import type { BuiltInProviderType, ProviderType } from 'next-auth/providers'
|
||||||
|
|
||||||
|
export interface UseSessionOptions<R extends boolean> {
|
||||||
|
required: R
|
||||||
|
/** Defaults to `signIn` */
|
||||||
|
onUnauthenticated?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util type that matches some strings literally, but allows any other string as well.
|
||||||
|
* @source https://github.com/microsoft/TypeScript/issues/29729#issuecomment-832522611
|
||||||
|
*/
|
||||||
|
export type LiteralUnion<T extends U, U = string> =
|
||||||
|
| T
|
||||||
|
| (U & Record<never, never>)
|
||||||
|
|
||||||
|
export interface ClientSafeProvider {
|
||||||
|
id: LiteralUnion<BuiltInProviderType>
|
||||||
|
name: string
|
||||||
|
type: ProviderType
|
||||||
|
signinUrl: string
|
||||||
|
callbackUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignInOptions extends Record<string, unknown> {
|
||||||
|
/**
|
||||||
|
* Defaults to the current URL.
|
||||||
|
* @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl
|
||||||
|
*/
|
||||||
|
callbackUrl?: string
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option */
|
||||||
|
redirect?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignInResponse {
|
||||||
|
error: string | undefined
|
||||||
|
status: number
|
||||||
|
ok: boolean
|
||||||
|
url: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Match `inputType` of `new URLSearchParams(inputType)` */
|
||||||
|
export type SignInAuthorizationParams =
|
||||||
|
| string
|
||||||
|
| string[][]
|
||||||
|
| Record<string, string>
|
||||||
|
| URLSearchParams
|
||||||
|
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
|
||||||
|
export interface SignOutResponse {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignOutParams<R extends boolean = true> {
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl-1 */
|
||||||
|
callbackUrl?: string
|
||||||
|
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
|
||||||
|
redirect?: R
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @docs: https://next-auth.js.org/getting-started/client#options */
|
||||||
|
export interface SessionProviderProps {
|
||||||
|
// children: React.ReactNode
|
||||||
|
session?: Session | null
|
||||||
|
baseUrl?: string
|
||||||
|
basePath?: string
|
||||||
|
/**
|
||||||
|
* A time interval (in seconds) after which the session will be re-fetched.
|
||||||
|
* If set to `0` (default), the session is not polled.
|
||||||
|
*/
|
||||||
|
refetchInterval?: number
|
||||||
|
/**
|
||||||
|
* `SessionProvider` automatically refetches the session when the user switches between windows.
|
||||||
|
* This option activates this behaviour if set to `true` (default).
|
||||||
|
*/
|
||||||
|
refetchOnWindowFocus?: boolean
|
||||||
|
}
|
||||||
4
apps/playground-nuxt/tsconfig.json
Normal file
4
apps/playground-nuxt/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// https://v3.nuxtjs.org/concepts/typescript
|
||||||
|
"extends": "./playground/.nuxt/tsconfig.json"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
VITE_GITHUB_CLIENT_ID=
|
GITHUB_CLIENT_ID=
|
||||||
VITE_GITHUB_CLIENT_SECRET=
|
GITHUB_CLIENT_SECRET=
|
||||||
VITE_NEXTAUTH_URL=
|
NEXTAUTH_SECRET=
|
||||||
VITE_NEXTAUTH_SECRET=
|
PUBLIC_NEXTAUTH_URL=http://localhost:5173
|
||||||
@@ -7,7 +7,7 @@ module.exports = {
|
|||||||
"prettier",
|
"prettier",
|
||||||
],
|
],
|
||||||
plugins: ["svelte3", "@typescript-eslint"],
|
plugins: ["svelte3", "@typescript-eslint"],
|
||||||
ignorePatterns: ["*.cjs"],
|
ignorePatterns: ["*.cjs", "build/**/*"],
|
||||||
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
|
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
|
||||||
settings: {
|
settings: {
|
||||||
"svelte3/typescript": () => require("typescript"),
|
"svelte3/typescript": () => require("typescript"),
|
||||||
|
|||||||
@@ -4,84 +4,71 @@ NextAuth.js is committed to bringing easy authentication to other frameworks. ht
|
|||||||
|
|
||||||
SvelteKit support with NextAuth.js is currently experimental. This directory contains a minimal, proof-of-concept application. Parts of this is expected to be abstracted away into a package like `@next-auth/sveltekit`
|
SvelteKit support with NextAuth.js is currently experimental. This directory contains a minimal, proof-of-concept application. Parts of this is expected to be abstracted away into a package like `@next-auth/sveltekit`
|
||||||
|
|
||||||
|
## Running this Demo
|
||||||
|
|
||||||
|
- Copy `.env.example` to `.env`
|
||||||
|
- In `.env`, set `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`
|
||||||
|
- See [https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app))
|
||||||
|
- When creating the OAuth app, set "Homepage URL" to `http://localhost:5173` and Authorization callack URL to `http://localhost:5173/api/auth/callback/github`
|
||||||
|
- In `.env`, set `NEXTAUTH_SECRET` to any random string
|
||||||
|
- Build and run the application: `yarn build && yarn start`
|
||||||
|
|
||||||
## Existing Project
|
## Existing Project
|
||||||
|
|
||||||
### Add API route
|
### Add API Route
|
||||||
|
|
||||||
To add NextAuth.js to a project create a file called `[...nextauth].js` in routes/api/auth. This contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations.
|
To add NextAuth.js to a project create a file called `[...nextauth]/+server.js` in routes/api/auth. This contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import NextAuth from "$lib"
|
import { NextAuth, options } from "$lib/next-auth"
|
||||||
import GithubProvider from "next-auth/providers/github"
|
|
||||||
|
|
||||||
const nextAuthOptions = {
|
export const { GET, POST } = NextAuth(options)
|
||||||
// Configure one or more authentication providers
|
|
||||||
providers: [
|
|
||||||
GithubProvider({
|
|
||||||
clientId: import.meta.env.VITE_GITHUB_CLIENT_ID,
|
|
||||||
clientSecret: import.meta.env.VITE_GITHUB_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
// ...add more providers here
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export const { get, post } = NextAuth(nextAuthOptions)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add [hook](https://kit.svelte.dev/docs/hooks)
|
### Add [hook](https://kit.svelte.dev/docs/hooks)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { getServerSession } from "$lib"
|
import type { Handle } from "@sveltejs/kit"
|
||||||
import GithubProvider from "next-auth/providers/github"
|
import { getServerSession, options as nextAuthOptions } from "$lib/next-auth"
|
||||||
|
|
||||||
const nextAuthOptions = {
|
export const handle: Handle = async function handle({
|
||||||
providers: [
|
event,
|
||||||
GithubProvider({
|
resolve,
|
||||||
clientId: import.meta.env.VITE_GITHUB_CLIENT_ID,
|
}): Promise<Response> {
|
||||||
clientSecret: import.meta.env.VITE_GITHUB_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handle({ event, resolve }) {
|
|
||||||
const session = await getServerSession(event.request, nextAuthOptions)
|
const session = await getServerSession(event.request, nextAuthOptions)
|
||||||
event.locals.session = session
|
event.locals.session = session
|
||||||
|
|
||||||
return resolve(event)
|
return resolve(event)
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
export function getSession(event) {
|
### Load Session from Primary Layout
|
||||||
return event.locals.session || {}
|
|
||||||
|
```ts
|
||||||
|
// src/lib/routes/+layout.server.ts
|
||||||
|
import type { LayoutServerLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = ({ locals }) => {
|
||||||
|
return {
|
||||||
|
session: locals.session,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Protecting a route
|
### Protecting a Route
|
||||||
|
|
||||||
```html
|
```ts
|
||||||
<script context="module">
|
// src/lib/routes/protected/+page.ts
|
||||||
export async function load({ session }) {
|
import { redirect } from "@sveltejs/kit"
|
||||||
const { user } = session
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
if (!user) {
|
export const load: PageLoad = async ({ parent }) => {
|
||||||
return {
|
const { session } = await parent()
|
||||||
status: 302,
|
if (!session?.user) {
|
||||||
redirect: "/",
|
throw redirect(302, "/")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
session,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
return {}
|
||||||
|
}
|
||||||
<script>
|
|
||||||
export let session
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<p>Session expiry: {session.expires}</p>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Packaging lib
|
## Packaging lib
|
||||||
|
|||||||
@@ -1,36 +1,40 @@
|
|||||||
{
|
{
|
||||||
"name": "sveltekit-nextauth",
|
"name": "sveltekit-nextauth",
|
||||||
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "svelte-kit dev",
|
"dev": "vite dev",
|
||||||
"build": "svelte-kit build",
|
"build": "vite build",
|
||||||
"preview": "svelte-kit preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"start": "HOST=127.0.0.1 PORT=5173 ORIGIN=http://localhost:5173 node ./build",
|
||||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "next",
|
"@sveltejs/adapter-auto": "^1.0.0-next.80",
|
||||||
"@sveltejs/kit": "next",
|
"@sveltejs/adapter-node": "1.0.0-next.96",
|
||||||
"@types/cookie": "^0.4.1",
|
"@sveltejs/kit": "1.0.0-next.511",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"@typescript-eslint/parser": "^5.10.1",
|
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||||
"eslint": "^7.32.0",
|
"@typescript-eslint/parser": "^5.35.1",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint": "^8.22.0",
|
||||||
"eslint-plugin-svelte3": "^3.2.1",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"prettier": "^2.5.1",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"prettier-plugin-svelte": "^2.5.0",
|
"prettier": "^2.7.1",
|
||||||
"svelte": "^3.44.0",
|
"prettier-plugin-svelte": "^2.7.0",
|
||||||
"svelte-check": "^2.2.6",
|
"svelte": "^3.49.0",
|
||||||
"svelte-preprocess": "^4.10.1",
|
"svelte-check": "^2.8.1",
|
||||||
"tslib": "^2.3.1",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"typescript": "~4.5.4"
|
"tslib": "^2.4.0",
|
||||||
|
"typescript": "~4.8.2",
|
||||||
|
"vite": "^3.1.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie": "0.4.1",
|
"cookie": "0.5.0",
|
||||||
"next-auth": "workspace:*"
|
"next-auth": "latest"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"semi": false,
|
"semi": false,
|
||||||
|
|||||||
27
apps/playground-sveltekit/src/app.d.ts
vendored
27
apps/playground-sveltekit/src/app.d.ts
vendored
@@ -1,13 +1,30 @@
|
|||||||
/// <reference types="@sveltejs/kit" />
|
/// <reference types="@sveltejs/kit" />
|
||||||
|
import type {
|
||||||
|
User as NextAuthUser,
|
||||||
|
Session as NextAuthSession,
|
||||||
|
} from "next-auth"
|
||||||
|
|
||||||
|
// optionally extend the `user`
|
||||||
|
interface User extends NextAuthUser {
|
||||||
|
// add custom fields here
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AppSession extends NextAuthSession {
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
|
||||||
// See https://kit.svelte.dev/docs/typescript
|
// See https://kit.svelte.dev/docs/typescript
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
declare namespace App {
|
declare global {
|
||||||
interface Locals {}
|
declare namespace App {
|
||||||
|
interface Locals {
|
||||||
|
session: AppSession
|
||||||
|
}
|
||||||
|
|
||||||
interface Platform {}
|
interface Platform {}
|
||||||
|
|
||||||
interface Session {}
|
interface Session extends AppSession {}
|
||||||
|
|
||||||
interface Stuff {}
|
interface Stuff {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="description" content="" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<link rel="icon" href="%svelte.assets%/favicon.png" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
%sveltekit.head%
|
||||||
%svelte.head%
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div>%svelte.body%</div>
|
<div>%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
8
apps/playground-sveltekit/src/global.d.ts
vendored
8
apps/playground-sveltekit/src/global.d.ts
vendored
@@ -1,8 +0,0 @@
|
|||||||
/// <reference types="@sveltejs/kit" />
|
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
|
||||||
VITE_GITHUB_CLIENT_ID: string
|
|
||||||
VITE_GITHUB_CLIENT_SECRET: string
|
|
||||||
VITE_NEXTAUTH_URL: string
|
|
||||||
VITE_NEXTAUTH_SECRET: string
|
|
||||||
}
|
|
||||||
14
apps/playground-sveltekit/src/hooks.server.ts
Normal file
14
apps/playground-sveltekit/src/hooks.server.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { Handle } from "@sveltejs/kit"
|
||||||
|
import { getServerSession, options as nextAuthOptions } from "$lib/next-auth"
|
||||||
|
|
||||||
|
export const handle: Handle = async function handle({
|
||||||
|
event,
|
||||||
|
resolve,
|
||||||
|
}): Promise<Response> {
|
||||||
|
const session = await getServerSession(event.request, nextAuthOptions)
|
||||||
|
if (session) {
|
||||||
|
event.locals.session = session
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(event)
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { getServerSession } from "$lib"
|
|
||||||
import type { Session } from "next-auth"
|
|
||||||
import type { NextAuthOptions } from "next-auth"
|
|
||||||
import GithubProvider from "next-auth/providers/github"
|
|
||||||
|
|
||||||
const nextAuthOptions: NextAuthOptions = {
|
|
||||||
providers: [
|
|
||||||
GithubProvider({
|
|
||||||
clientId: import.meta.env.VITE_GITHUB_CLIENT_ID,
|
|
||||||
clientSecret: import.meta.env.VITE_GITHUB_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handle({ event, resolve }): Promise<Response> {
|
|
||||||
const session = await getServerSession(event.request, nextAuthOptions)
|
|
||||||
event.locals.session = session
|
|
||||||
|
|
||||||
return resolve(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSession(event): Session {
|
|
||||||
return event.locals.session || {}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import NextAuth, { getServerSession } from "./next-auth"
|
|
||||||
|
|
||||||
export default NextAuth
|
|
||||||
export { getServerSession }
|
|
||||||
@@ -1,74 +1,103 @@
|
|||||||
import type { RequestEvent } from "@sveltejs/kit"
|
import type { ServerLoadEvent } from "@sveltejs/kit"
|
||||||
import type { IncomingRequest, NextAuthOptions, Session } from "next-auth"
|
import type { RequestInternal } from "next-auth"
|
||||||
import type { NextAuthAction } from "next-auth/lib/types"
|
import type { NextAuthAction, NextAuthOptions } from "next-auth/core/types"
|
||||||
import type { OutgoingResponse } from "next-auth/core"
|
import type { OutgoingResponse as NextAuthResponse } from "next-auth/core"
|
||||||
import { NextAuthHandler } from "next-auth/core"
|
import { NextAuthHandler } from "next-auth/core"
|
||||||
|
import GithubProvider from "next-auth/providers/github"
|
||||||
import cookie from "cookie"
|
import cookie from "cookie"
|
||||||
import getFormBody from "./utils/get-form-body"
|
import {
|
||||||
|
GITHUB_CLIENT_ID,
|
||||||
|
GITHUB_CLIENT_SECRET,
|
||||||
|
NEXTAUTH_SECRET,
|
||||||
|
} from "$env/static/private"
|
||||||
|
import { PUBLIC_NEXTAUTH_URL } from "$env/static/public"
|
||||||
|
|
||||||
async function toSvelteKitResponse(
|
// @ts-expect-error import is exported on .default during SSR
|
||||||
|
const github = GithubProvider?.default || GithubProvider
|
||||||
|
|
||||||
|
export const options: NextAuthOptions = {
|
||||||
|
providers: [
|
||||||
|
github({
|
||||||
|
clientId: GITHUB_CLIENT_ID,
|
||||||
|
clientSecret: GITHUB_CLIENT_SECRET,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const toSvelteKitResponse = async <
|
||||||
|
T extends string | any[] | Record<string, any>
|
||||||
|
>(
|
||||||
request: Request,
|
request: Request,
|
||||||
nextAuthResponse: OutgoingResponse<unknown>
|
nextAuthResponse: NextAuthResponse<T>
|
||||||
) {
|
): Promise<Response> => {
|
||||||
const { headers, cookies, body, redirect, status = 200 } = nextAuthResponse
|
const { cookies, redirect } = nextAuthResponse
|
||||||
|
|
||||||
const response = {
|
const headers = new Headers()
|
||||||
status,
|
for (const header of nextAuthResponse?.headers || []) {
|
||||||
headers: {},
|
// pass headers along from next-auth
|
||||||
|
headers.set(header.key, header.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers?.forEach((header) => {
|
// set-cookie header
|
||||||
response.headers[header.key] = header.value
|
if (cookies?.length) {
|
||||||
})
|
headers.set(
|
||||||
|
"set-cookie",
|
||||||
|
cookies
|
||||||
|
?.map((item) => cookie.serialize(item.name, item.value, item.options))
|
||||||
|
.join(",") as string
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
response.headers["set-cookie"] = cookies?.map((item) => {
|
let body = undefined
|
||||||
return cookie.serialize(item.name, item.value, item.options)
|
let status = nextAuthResponse.status || 200
|
||||||
})
|
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
let formData = null
|
let formData: FormData | null = null
|
||||||
try {
|
try {
|
||||||
formData = await request.formData()
|
formData = await request.formData()
|
||||||
formData = getFormBody(formData)
|
|
||||||
} catch {
|
} catch {
|
||||||
// no formData passed
|
// no formData passed
|
||||||
}
|
}
|
||||||
if (formData?.json !== "true") {
|
const { json } = Object.fromEntries(formData ?? [])
|
||||||
response.status = 302
|
if (json !== "true") {
|
||||||
response.headers["Location"] = redirect
|
status = 302
|
||||||
|
headers.set("Location", redirect)
|
||||||
} else {
|
} else {
|
||||||
response["body"] = { url: redirect }
|
body = { url: redirect }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response["body"] = body
|
body = nextAuthResponse.body
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
// @ts-expect-error - body is a known HTML document or JSON object
|
||||||
|
return new Response(body, {
|
||||||
|
status,
|
||||||
|
headers,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function SKNextAuthHandler(
|
const SKNextAuthHandler = async (
|
||||||
{ request, url, params }: RequestEvent,
|
{ request, url, params }: ServerLoadEvent,
|
||||||
options: NextAuthOptions
|
options: NextAuthOptions
|
||||||
) {
|
): Promise<Response> => {
|
||||||
const nextauth = params.nextauth.split("/")
|
const [action, provider] = params.nextauth!.split("/")
|
||||||
let body = null
|
let body: FormData | undefined
|
||||||
try {
|
try {
|
||||||
body = await request.formData()
|
body = await request.formData()
|
||||||
body = getFormBody(body)
|
|
||||||
} catch {
|
} catch {
|
||||||
// no formData passed
|
// no formData passed
|
||||||
}
|
}
|
||||||
options.secret = import.meta.env.VITE_NEXTAUTH_SECRET
|
options.secret = NEXTAUTH_SECRET
|
||||||
const req: IncomingRequest = {
|
const req: RequestInternal = {
|
||||||
host: import.meta.env.VITE_NEXTAUTH_URL,
|
host: PUBLIC_NEXTAUTH_URL,
|
||||||
body,
|
body: Object.fromEntries(body ?? []),
|
||||||
query: Object.fromEntries(url.searchParams),
|
query: Object.fromEntries(url.searchParams),
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
method: request.method,
|
method: request.method,
|
||||||
cookies: cookie.parse(request.headers.get("cookie") ?? ""),
|
cookies: cookie.parse(request.headers.get("cookie") || ""),
|
||||||
action: nextauth[0] as NextAuthAction,
|
action: action as NextAuthAction,
|
||||||
providerId: nextauth[1],
|
providerId: provider,
|
||||||
error: nextauth[1],
|
error: provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await NextAuthHandler({
|
const response = await NextAuthHandler({
|
||||||
@@ -79,19 +108,18 @@ async function SKNextAuthHandler(
|
|||||||
return toSvelteKitResponse(request, response)
|
return toSvelteKitResponse(request, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getServerSession(
|
export const getServerSession = async (
|
||||||
request: Request,
|
request: Request,
|
||||||
options: NextAuthOptions
|
options: NextAuthOptions
|
||||||
): Promise<Session | null> {
|
): Promise<App.Session | null> => {
|
||||||
|
options.secret = NEXTAUTH_SECRET
|
||||||
|
|
||||||
options.secret = import.meta.env.VITE_NEXTAUTH_SECRET
|
const session = await NextAuthHandler<App.Session>({
|
||||||
|
|
||||||
const session = await NextAuthHandler<Session>({
|
|
||||||
req: {
|
req: {
|
||||||
host: import.meta.env.VITE_NEXTAUTH_URL,
|
host: PUBLIC_NEXTAUTH_URL,
|
||||||
action: "session",
|
action: "session",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
cookies: cookie.parse(request.headers.get("cookie") ?? ""),
|
cookies: cookie.parse(request.headers.get("cookie") || ""),
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
@@ -99,16 +127,18 @@ export async function getServerSession(
|
|||||||
|
|
||||||
const { body } = session
|
const { body } = session
|
||||||
|
|
||||||
if (body && Object.keys(body).length) return body as Session
|
if (body && Object.keys(body).length) {
|
||||||
|
return body as App.Session
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (
|
export const NextAuth = (
|
||||||
options: NextAuthOptions
|
options: NextAuthOptions
|
||||||
): {
|
): {
|
||||||
get: (req: RequestEvent) => Promise<unknown>
|
GET: (event: ServerLoadEvent) => Promise<unknown>
|
||||||
post: (req: RequestEvent) => Promise<unknown>
|
POST: (event: ServerLoadEvent) => Promise<unknown>
|
||||||
} => ({
|
} => ({
|
||||||
get: (req) => SKNextAuthHandler(req, options),
|
GET: (event) => SKNextAuthHandler(event, options),
|
||||||
post: (req) => SKNextAuthHandler(req, options),
|
POST: (event) => SKNextAuthHandler(event, options),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
// https://dev.to/danawoodman/getting-form-body-data-in-your-sveltekit-endpoints-4a85
|
|
||||||
export default function getFormBody(
|
|
||||||
body: FormData | null
|
|
||||||
): Record<string, any> {
|
|
||||||
if (!body) return {}
|
|
||||||
|
|
||||||
// @ts-expect-error: Entries property type missing
|
|
||||||
return [...body.entries()].reduce((data, [k, v]) => {
|
|
||||||
const value = v
|
|
||||||
if (k in data)
|
|
||||||
data[k] = Array.isArray(data[k]) ? [...data[k], value] : [data[k], value]
|
|
||||||
else data[k] = value
|
|
||||||
return data
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
7
apps/playground-sveltekit/src/routes/+layout.server.ts
Normal file
7
apps/playground-sveltekit/src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { LayoutServerLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: LayoutServerLoad = ({ locals }) => {
|
||||||
|
return {
|
||||||
|
session: locals.session,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { session } from "$app/stores"
|
import { page } from "$app/stores"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<header>
|
<header>
|
||||||
<div class="signedInStatus">
|
<div class="signedInStatus">
|
||||||
<p class="nojs-show loaded">
|
<p class="nojs-show loaded">
|
||||||
{#if Object.keys($session).length}
|
{#if Object.keys($page.data.session || {}).length}
|
||||||
{#if $session.user.image}
|
{#if $page.data.session.user.image}
|
||||||
<span
|
<span
|
||||||
style="background-image: url('{$session.user.image}')"
|
style="background-image: url('{$page.data.session.user.image}')"
|
||||||
class="avatar"
|
class="avatar"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="signedInText">
|
<span class="signedInText">
|
||||||
<small>Signed in as</small><br />
|
<small>Signed in as</small><br />
|
||||||
<strong>{$session.user.email || $session.user.name}</strong>
|
<strong
|
||||||
|
>{$page.data.session.user.email ||
|
||||||
|
$page.data.session.user.name}</strong
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
<a href="/api/auth/signout" class="button">Sign out</a>
|
<a href="/api/auth/signout" class="button">Sign out</a>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -38,7 +41,8 @@
|
|||||||
:global(body) {
|
:global(body) {
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
|
||||||
|
"Noto Color Emoji";
|
||||||
padding: 0 1rem 1rem 1rem;
|
padding: 0 1rem 1rem 1rem;
|
||||||
max-width: 680px;
|
max-width: 680px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user