mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
63 Commits
next-auth@
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7af366a3b | ||
|
|
3bdf7f56f0 | ||
|
|
b00a694a4f | ||
|
|
6ffecfb87d | ||
|
|
22c29361e5 | ||
|
|
b157554a5f | ||
|
|
08fed7eddd | ||
|
|
b5e1b19771 | ||
|
|
6680860293 | ||
|
|
c7d93c61e0 | ||
|
|
a7b6a29773 | ||
|
|
092ab9c128 | ||
|
|
36f44a869a | ||
|
|
2913fbac3b | ||
|
|
2875b49f11 | ||
|
|
5259d247a2 | ||
|
|
d1d93fd75e | ||
|
|
62f672ae30 | ||
|
|
2c669b32fc | ||
|
|
2dea8919e5 | ||
|
|
6fdb0da6eb | ||
|
|
5c4a9a697d | ||
|
|
eddd8fd7f9 | ||
|
|
b74bfc68e8 | ||
|
|
0a140cdf87 | ||
|
|
157269e0fb | ||
|
|
221bc8e99c | ||
|
|
f856363ac8 | ||
|
|
f3291025e6 | ||
|
|
bc0912cc71 | ||
|
|
b19b2bcb3f | ||
|
|
63c2a15717 | ||
|
|
9209b48dbe | ||
|
|
0fcc6a0d04 | ||
|
|
cbf8c7a59f | ||
|
|
0e6c51adec | ||
|
|
c4352a7d56 | ||
|
|
7e91d7df54 | ||
|
|
c1424136db | ||
|
|
f2a07932b9 | ||
|
|
25c7ce1d2b | ||
|
|
227a233bd8 | ||
|
|
cf9f133aa3 | ||
|
|
2301c1be44 | ||
|
|
6e408e24bf | ||
|
|
f277989c69 | ||
|
|
6146e93288 | ||
|
|
1ff565da6c | ||
|
|
41f75cf870 | ||
|
|
dd591ed8d0 | ||
|
|
297bc2317f | ||
|
|
b170138e70 | ||
|
|
a307079e0f | ||
|
|
d52b7a6b7d | ||
|
|
30b69a07eb | ||
|
|
0d1757814f | ||
|
|
068f9b50b8 | ||
|
|
dac490b7a1 | ||
|
|
c0f51669e2 | ||
|
|
cbf8ce3510 | ||
|
|
1c19cc71df | ||
|
|
9d962a0056 | ||
|
|
8387c78e3f |
@@ -18,6 +18,7 @@ module.exports = {
|
||||
parserOptions: {
|
||||
project: [
|
||||
path.resolve(__dirname, "./packages/**/tsconfig.eslint.json"),
|
||||
path.resolve(__dirname, "./packages/frameworks/**/tsconfig.json"),
|
||||
path.resolve(__dirname, "./apps/**/tsconfig.json"),
|
||||
],
|
||||
},
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
1
.github/ISSUE_TEMPLATE/3_bug_adapter.yml
vendored
@@ -31,6 +31,7 @@ body:
|
||||
- "@next-auth/pouchdb-adapter"
|
||||
- "@next-auth/prisma-adapter"
|
||||
- "@next-auth/sequelize-adapter"
|
||||
- "@next-auth/supabase-adapter"
|
||||
- "@next-auth/typeorm-legacy-adapter"
|
||||
- "@next-auth/upstash-redis-adapter"
|
||||
- "@next-auth/xata-adapter"
|
||||
|
||||
2
.github/actions/issue-validator/.gitignore
vendored
Normal file
2
.github/actions/issue-validator/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
!dist
|
||||
!package-lock.json
|
||||
7
.github/actions/issue-validator/index.mjs
vendored
Normal file
7
.github/actions/issue-validator/index.mjs
vendored
Normal file
File diff suppressed because one or more lines are too long
652
.github/actions/issue-validator/licenses.txt
vendored
Normal file
652
.github/actions/issue-validator/licenses.txt
vendored
Normal file
@@ -0,0 +1,652 @@
|
||||
@actions/core
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@actions/github
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@actions/http-client
|
||||
MIT
|
||||
Actions Http Client for Node.js
|
||||
|
||||
Copyright (c) GitHub, Inc.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/auth-token
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/core
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/endpoint
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/graphql
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/plugin-paginate-rest
|
||||
MIT
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/plugin-rest-endpoint-methods
|
||||
MIT
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/request
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@octokit/request-error
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@vercel/ncc
|
||||
MIT
|
||||
Copyright 2018 ZEIT, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
before-after-hook
|
||||
Apache-2.0
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 Gregor Martynus and other contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
deprecation
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Gregor Martynus and contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
is-plain-object
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
node-fetch
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 David Frank
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
once
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
root
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2018-2021, Iain Collins
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
tr46
|
||||
MIT
|
||||
|
||||
tunnel
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Koichi Kobayashi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
universal-user-agent
|
||||
ISC
|
||||
# [ISC License](https://spdx.org/licenses/ISC)
|
||||
|
||||
Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m)
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
uuid
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
webidl-conversions
|
||||
BSD-2-Clause
|
||||
# The BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2014, Domenic Denicola
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
whatwg-url
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015–2016 Sebastian Mayr
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
wrappy
|
||||
ISC
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
445
.github/actions/issue-validator/package-lock.json
generated
vendored
Normal file
445
.github/actions/issue-validator/package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,445 @@
|
||||
{
|
||||
"name": "issue-validator",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@actions/core": "1.10.0",
|
||||
"@actions/github": "5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "0.34.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.3",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "12.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.10.1.tgz",
|
||||
"integrity": "sha512-P+SukKanjFY0ZhsK6wSVnQmxTP2eVPPE8OPSNuxaMYtgVzwJZgfGdwlYjf4RlRU4vLEw4ts2fsE2icG4nZ5ddQ=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "2.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
|
||||
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.40.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
|
||||
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.39.0",
|
||||
"deprecation": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.40.0.tgz",
|
||||
"integrity": "sha512-MFZOU5r8SwgJWDMhrLUSvyJPtVsqA6VnbVI3TNbsmw+Jnvrktzvq2fYES/6RiJA/5Ykdwq4mJmtlYUfW7CGjmw==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^12.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/ncc": {
|
||||
"version": "0.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
|
||||
"integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"ncc": "dist/ncc/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
|
||||
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"@actions/github": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
}
|
||||
},
|
||||
"@actions/http-client": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"requires": {
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"@octokit/core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
||||
"requires": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.3",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"requires": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/openapi-types": {
|
||||
"version": "12.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.10.1.tgz",
|
||||
"integrity": "sha512-P+SukKanjFY0ZhsK6wSVnQmxTP2eVPPE8OPSNuxaMYtgVzwJZgfGdwlYjf4RlRU4vLEw4ts2fsE2icG4nZ5ddQ=="
|
||||
},
|
||||
"@octokit/plugin-paginate-rest": {
|
||||
"version": "2.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
|
||||
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.40.0"
|
||||
}
|
||||
},
|
||||
"@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
|
||||
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.39.0",
|
||||
"deprecation": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@octokit/request": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
||||
"requires": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"requires": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"@octokit/types": {
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.40.0.tgz",
|
||||
"integrity": "sha512-MFZOU5r8SwgJWDMhrLUSvyJPtVsqA6VnbVI3TNbsmw+Jnvrktzvq2fYES/6RiJA/5Ykdwq4mJmtlYUfW7CGjmw==",
|
||||
"requires": {
|
||||
"@octokit/openapi-types": "^12.10.0"
|
||||
}
|
||||
},
|
||||
"@vercel/ncc": {
|
||||
"version": "0.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
|
||||
"integrity": "sha512-G9h5ZLBJ/V57Ou9vz5hI8pda/YQX5HQszCs3AmIus3XzsmRn/0Ptic5otD3xVST8QLKk7AMk7AqpsyQGN7MZ9A==",
|
||||
"dev": true
|
||||
},
|
||||
"before-after-hook": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
|
||||
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
|
||||
},
|
||||
"deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||
},
|
||||
"universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
14
.github/actions/issue-validator/package.json
vendored
Normal file
14
.github/actions/issue-validator/package.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"private": true,
|
||||
"exports": "./index.mjs",
|
||||
"scripts": {
|
||||
"build": "ncc -m -o . build src/index.mjs --license licenses.txt"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "0.34.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "1.10.0",
|
||||
"@actions/github": "5.1.1"
|
||||
}
|
||||
}
|
||||
38
.github/actions/issue-validator/repro.md
vendored
Normal file
38
.github/actions/issue-validator/repro.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
We cannot recreate the issue with the provided information. **Please add a reproduction in order for us to be able to investigate.**
|
||||
|
||||
### **Why was this issue marked with the `incomplete` label?**
|
||||
|
||||
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository ([template](https://github.com/nextauthjs/next-auth-example)), but you can also use a tool like [CodeSandbox](https://codesandbox.io/s/github/nextauthjs/next-auth-example/tree/main) or [StackBlitz](https://stackblitz.com/fork/github/nextauthjs/next-auth-example).
|
||||
|
||||
To make sure the issue is resolved as quickly as possible, please make sure that the reproduction is as **minimal** as possible. This means that you should **remove unnecessary code, files, and dependencies** that do not contribute to the issue.
|
||||
|
||||
Please test your reproduction against the latest version of NextAuth.js (`next-auth@latest`) to make sure your issue has not already been fixed.
|
||||
|
||||
### **I added a link, why was it still marked?**
|
||||
|
||||
Ensure the link is pointing to a codebase that is accessible (e.g. not a private repository). "[example.com](http://example.com/)", "n/a", "will add later", etc. are not acceptable links -- we need to see a public codebase. See the above section for accepted links.
|
||||
|
||||
### **What happens if I don't provide a sufficient minimal reproduction?**
|
||||
|
||||
Issues with the `incomplete` label that receives no meaningful activity (e.g. new comments with a reproduction link) are automatically closed and locked after 30 days.
|
||||
|
||||
If your issue has _not_ been resolved in that time and it has been closed/locked, please open a new issue with the required reproduction.
|
||||
|
||||
### **I did not open this issue, but it is relevant to me, what can I do to help?**
|
||||
|
||||
Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps. Furthermore, you can upvote the issue using the :+1: reaction on the topmost comment (please **do not** comment "I have the same issue" without repro steps). Then, we can sort issues by votes to prioritize.
|
||||
|
||||
### **I think my reproduction is good enough, why aren't you looking into it quicker?**
|
||||
|
||||
We look into every NextAuth.js issue and constantly monitor open issues for new comments.
|
||||
|
||||
However, sometimes we might miss one or two. We apologize, and kindly ask you to refrain from tagging core maintainers, as that will usually not result in increased priority.
|
||||
|
||||
Upvoting issues to show your interest will help us prioritize and address them as quickly as possible. That said, every issue is important to us, and if an issue gets closed by accident, we encourage you to open a new one linking to the old issue and we will look into it.
|
||||
|
||||
### **Useful Resources**
|
||||
|
||||
- [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve)
|
||||
- [Reporting a NextAuth.js bug](https://github.com/nextauthjs/next-auth/blob/main/.github/ISSUE_TEMPLATE/1_bug_framework.yml)
|
||||
- [How to Contribute to Open Source (Next.js)](https://www.youtube.com/watch?v=cuoNzXFLitc)
|
||||
|
||||
88
.github/actions/issue-validator/src/index.mjs
vendored
Normal file
88
.github/actions/issue-validator/src/index.mjs
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// @ts-check
|
||||
// @ts-expect-error
|
||||
import * as github from "@actions/github"
|
||||
// @ts-expect-error
|
||||
import * as core from "@actions/core"
|
||||
import { readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
|
||||
const addReproductionLabel = "incomplete"
|
||||
const __dirname =
|
||||
"/home/runner/work/nextauthjs/next-auth/.github/actions/issue-validator"
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* id :number
|
||||
* node_id :string
|
||||
* url :string
|
||||
* name :string
|
||||
* description :string
|
||||
* color :string
|
||||
* default :boolean
|
||||
* }} Label
|
||||
*
|
||||
* @typedef {{
|
||||
* pull_request: any
|
||||
* issue?: {body: string, number: number, labels: Label[]}
|
||||
* label: Label
|
||||
* }} Payload
|
||||
*
|
||||
* @typedef {{
|
||||
* payload: Payload
|
||||
* repo: any
|
||||
* }} Context
|
||||
*/
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
/** @type {Context} */
|
||||
const { payload, repo } = github.context
|
||||
const {
|
||||
issue,
|
||||
pull_request,
|
||||
label: { name: newLabel },
|
||||
} = payload
|
||||
|
||||
if (pull_request || !issue?.body || !process.env.GITHUB_TOKEN) return
|
||||
|
||||
const labels = issue.labels.map((l) => l.name)
|
||||
// const isBugReport =
|
||||
// labels.includes(bugLabel) || newLabel === bugLabel || !labels.length
|
||||
|
||||
if (
|
||||
// !(isBugReport && issue.number > 43554) &&
|
||||
![addReproductionLabel].includes(newLabel) &&
|
||||
!labels.includes(addReproductionLabel)
|
||||
) {
|
||||
return core.info(
|
||||
"Not a bug report or not manually labeled or already labeled."
|
||||
)
|
||||
}
|
||||
|
||||
const client = github.getOctokit(process.env.GITHUB_TOKEN).rest
|
||||
const issueCommon = { ...repo, issue_number: issue.number }
|
||||
|
||||
if (
|
||||
newLabel === addReproductionLabel
|
||||
// || !hasValidRepro
|
||||
) {
|
||||
await Promise.all([
|
||||
client.issues.addLabels({
|
||||
...issueCommon,
|
||||
labels: [addReproductionLabel],
|
||||
}),
|
||||
client.issues.createComment({
|
||||
...issueCommon,
|
||||
body: readFileSync(join(__dirname, "repro.md"), "utf8"),
|
||||
}),
|
||||
])
|
||||
return core.info(
|
||||
"Commented on issue, because it did not have a sufficient reproduction."
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
3
.github/issue-labeler.yml
vendored
3
.github/issue-labeler.yml
vendored
@@ -30,6 +30,9 @@ prisma:
|
||||
sequelize:
|
||||
- "@next-auth/sequelize-adapter"
|
||||
|
||||
supabase:
|
||||
- "@next-auth/supabase-adapter"
|
||||
|
||||
typeorm-legacy:
|
||||
- "@next-auth/typeorm-legacy-adapter"
|
||||
|
||||
|
||||
3
.github/pr-labeler.yml
vendored
3
.github/pr-labeler.yml
vendored
@@ -42,6 +42,9 @@ prisma:
|
||||
sequelize:
|
||||
- packages/adapter-sequelize/**
|
||||
|
||||
supabase:
|
||||
- packages/adapter-supabase/**
|
||||
|
||||
typeorm-legacy:
|
||||
- packages/adapter-typeorm-legacy/**
|
||||
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -18,10 +18,10 @@ jobs:
|
||||
language: ["javascript"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Triage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/issue-labeler@v2.4.1
|
||||
- uses: github/issue-labeler@v2.5
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: ".github/issue-labeler.yml"
|
||||
17
.github/workflows/issue-validator.yml
vendored
Normal file
17
.github/workflows/issue-validator.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Validate issue
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: 'Run issue validator'
|
||||
run: node ./.github/actions/issue-validator/index.mjs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Triage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
- uses: actions/labeler@v4
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: ".github/pr-labeler.yml"
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -15,11 +15,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Init
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2.2.1
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.5.1
|
||||
- name: Setup Node
|
||||
@@ -49,11 +49,11 @@ jobs:
|
||||
environment: Production
|
||||
steps:
|
||||
- name: Init
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2.2.1
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.5.1
|
||||
- name: Setup Node
|
||||
@@ -81,9 +81,9 @@ jobs:
|
||||
environment: Preview
|
||||
steps:
|
||||
- name: Init
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2.2.1
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.5.1
|
||||
- name: Setup Node
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN_PKG }}
|
||||
- name: Comment version on PR
|
||||
uses: NejcZdovc/comment-pr@v1
|
||||
uses: NejcZdovc/comment-pr@v2
|
||||
with:
|
||||
message:
|
||||
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n \
|
||||
|
||||
2
.github/workflows/sync-examples.yml
vendored
2
.github/workflows/sync-examples.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Run GitHub File Sync
|
||||
# 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
|
||||
|
||||
19
.gitignore
vendored
19
.gitignore
vendored
@@ -65,6 +65,7 @@ dev.db*
|
||||
packages/adapter-prisma/prisma/dev.db
|
||||
packages/adapter-prisma/prisma/migrations
|
||||
db.sqlite
|
||||
packages/adapter-supabase/supabase/.branches
|
||||
|
||||
# Tests
|
||||
coverage
|
||||
@@ -77,4 +78,20 @@ test.schema.gql
|
||||
|
||||
# docusaurus
|
||||
docs/.docusaurus
|
||||
docs/providers.json
|
||||
docs/providers.json
|
||||
|
||||
# Core
|
||||
packages/core/adapters.*
|
||||
packages/core/index.*
|
||||
packages/core/jwt
|
||||
packages/core/lib
|
||||
packages/core/providers
|
||||
|
||||
|
||||
# SvelteKit
|
||||
packages/frameworks-sveltekit/index.*
|
||||
packages/frameworks-sveltekit/client.*
|
||||
packages/frameworks-sveltekit/.svelte-kit
|
||||
packages/frameworks-sveltekit/package
|
||||
packages/frameworks-sveltekit/vite.config.js.timestamp-*
|
||||
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# CHANGELOG
|
||||
|
||||
The changelog is automatically updated using
|
||||
[scripts/release/index.ts](https://github.com/nextauthjs/next-auth/tree/main/scripts/index.ts). You
|
||||
can see it on the [releases page](../../releases).
|
||||
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
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
|
||||
that is deemed necessary and appropriate to the circumstances. The project team
|
||||
is obligated to maintain confidentiality with regard to the reporter of an
|
||||
incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
119
CONTRIBUTING.md
119
CONTRIBUTING.md
@@ -1,119 +0,0 @@
|
||||
# Contributing guide
|
||||
|
||||
Contributions and feedback on your experience of using this software are welcome.
|
||||
|
||||
This includes bug reports, feature requests, ideas, pull requests, and examples of how you have used this software.
|
||||
|
||||
Please see the [Code of Conduct](CODE_OF_CONDUCT.md) and follow any templates configured in GitHub when reporting bugs, requesting enhancements, or contributing code.
|
||||
|
||||
Please raise any significant new functionality or breaking change an issue for discussion before raising a Pull Request for it.
|
||||
|
||||
## For contributors
|
||||
|
||||
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.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
- The latest changes are always in `main`, so please make your Pull Request against that branch.
|
||||
- Pull Requests should be raised for any change
|
||||
- Pull Requests need approval of a [core contributor](https://next-auth.js.org/contributors#core-team) before merging
|
||||
- We use ESLint/Prettier for linting/formatting, so please run `pnpm lint:fix` before committing to make resolving conflicts easier (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development)
|
||||
- We encourage you to test your changes, and if you have the opportunity, please make those tests part of the Pull Request
|
||||
- If you add new functionality, please provide the corresponding documentation as well and make it part of the Pull Request
|
||||
|
||||
### Setting up local environment
|
||||
|
||||
|
||||
A quick guide on how to setup _next-auth_ locally to work on it and test out any changes:
|
||||
|
||||
1. Clone the repo:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:nextauthjs/next-auth.git
|
||||
cd next-auth
|
||||
```
|
||||
|
||||
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
|
||||
pnpm install
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```sh
|
||||
cd apps/dev
|
||||
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.
|
||||
> You can find the next-auth config under`apps/dev/pages/api/auth/[...nextauth].js`.
|
||||
|
||||
5. Start the developer application/server:
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Your developer application will be available on `http://localhost:3000`
|
||||
|
||||
That's it! 🎉
|
||||
|
||||
If you need an example project to link to, you can use [next-auth-example](https://github.com/iaincollins/next-auth-example).
|
||||
|
||||
#### Hot reloading
|
||||
|
||||
When running `pnpm dev`, you start a Next.js developer server on `http://localhost:3000`, which includes hot reloading out-of-the-box. Make changes on any of the files in `src` and see the changes immediately.
|
||||
|
||||
> NOTE: When working on CSS, you will have to manually refresh the page after changes. The reason for this is our pages using CSS are server-side rendered (using API routes). (Improving this through a PR is very welcome!)
|
||||
|
||||
> NOTE: The setup is as follows: The development application lives inside the `app` folder, and whenever you make a change to the `src` folder in the root (where next-auth is), it gets copied into `app` every time (gitignored), so Next.js can pick them up and apply hot reloading. This is to avoid some annoying issues with how symlinks are working with different React builds, and also to provide a super-fast feedback loop while developing core features.
|
||||
|
||||
#### Providers
|
||||
|
||||
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily! You only need to add two changes:
|
||||
|
||||
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)
|
||||
|
||||
That's it! 🎉 Others will be able to discover this provider much more easily now!
|
||||
|
||||
You can look at the existing built-in providers for inspiration.
|
||||
|
||||
#### Databases
|
||||
|
||||
If you would like to contribute to an existing database adapter or help create a new one, head over to the [nextauthjs/adapters](https://www.github.com/nextauthjs/adapters) repository and follow the instructions provided there.
|
||||
|
||||
#### Testing
|
||||
|
||||
Tests can be run with `pnpm test`.
|
||||
|
||||
Automated tests are currently crude and limited in functionality, but improvements are in development.
|
||||
|
||||
## For maintainers
|
||||
|
||||
We use [a custom script](https://github.com/nextauthjs/next-auth/blob/main/scripts/release/index.ts) together with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to automate releases. This makes the maintenance process easier and less error-prone. Please study the "Conventional Commits" site to understand how to write a good commit message.
|
||||
|
||||
When accepting Pull Requests, make sure the following:
|
||||
|
||||
- Use "Squash and merge"
|
||||
- Make sure you merge contributor PRs into `main`
|
||||
- Rewrite the commit message to conform to the `Conventional Commits` style.
|
||||
- Using `fix` releases a patch (x.x.1)
|
||||
- Using `feat` releases a minor (x.1.x)
|
||||
- Using `feat` when `BREAKING CHANGE` is present in the commit message releases a major (1.x.x)
|
||||
- Optionally link issues the PR will resolve (You can add "close" in front of the issue numbers to close the issues automatically, when the PR is merged. `semantic-release` will also comment back to connected issues and PRs, notifying the users that a feature is added/bug fixed, etc.)
|
||||
|
||||
### Skipping a release
|
||||
|
||||
If a commit contains `[skip release]` in their message, it will be excluded from the commit analysis and won't participate in the release type determination. This is useful, if the PR being merged should not trigger a new `npm` release.
|
||||
24
SECURITY.md
24
SECURITY.md
@@ -1,24 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
NextAuth.js practices responsible disclosure.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We request that you contact us directly to report serious issues that might impact the security of sites using NextAuth.js.
|
||||
|
||||
If you contact us regarding a serious issue:
|
||||
|
||||
- We will endeavor to get back to you within 72 hours.
|
||||
- We will aim to publish a fix within 30 days.
|
||||
- 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.
|
||||
|
||||
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 publicly as bug reports or feature requests or to raise a question to open a discussion around them.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security updates are only released for the current version.
|
||||
|
||||
Old releases are not maintained and do not receive updates.
|
||||
@@ -48,4 +48,11 @@ EMAIL_FROM=user@gmail.com
|
||||
DATABASE_URL=
|
||||
|
||||
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
|
||||
@@ -90,6 +90,12 @@ export default function Header() {
|
||||
<li className={styles.navItem}>
|
||||
<Link href="/middleware-protected">Middleware protected</Link>
|
||||
</li>
|
||||
<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>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -16,16 +16,20 @@
|
||||
"dependencies": {
|
||||
"@next-auth/fauna-adapter": "workspace:*",
|
||||
"@next-auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/supabase-adapter": "workspace:*",
|
||||
"@next-auth/typeorm-legacy-adapter": "workspace:*",
|
||||
"@prisma/client": "^3",
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"faunadb": "^4",
|
||||
"next": "13.0.2",
|
||||
"next": "13.0.6",
|
||||
"next-auth": "workspace:*",
|
||||
"@auth/core": "workspace:*",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"fake-smtp-server": "^0.8.0",
|
||||
|
||||
@@ -1,45 +1,39 @@
|
||||
import NextAuth from "next-auth"
|
||||
import type { NextAuthOptions } from "next-auth"
|
||||
import { AuthHandler, type AuthOptions } from "@auth/core"
|
||||
|
||||
// Providers
|
||||
import Apple from "next-auth/providers/apple"
|
||||
import Auth0 from "next-auth/providers/auth0"
|
||||
import AzureAD from "next-auth/providers/azure-ad"
|
||||
import AzureB2C from "next-auth/providers/azure-ad-b2c"
|
||||
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
|
||||
import Cognito from "next-auth/providers/cognito"
|
||||
import Credentials from "next-auth/providers/credentials"
|
||||
import Discord from "next-auth/providers/discord"
|
||||
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
|
||||
import Email from "next-auth/providers/email"
|
||||
import Facebook from "next-auth/providers/facebook"
|
||||
import Foursquare from "next-auth/providers/foursquare"
|
||||
import Freshbooks from "next-auth/providers/freshbooks"
|
||||
import GitHub from "next-auth/providers/github"
|
||||
import Gitlab from "next-auth/providers/gitlab"
|
||||
import Google from "next-auth/providers/google"
|
||||
import Hubspot from "next-auth/providers/hubspot"
|
||||
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 Todoist from "next-auth/providers/todoist"
|
||||
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"
|
||||
import Zitadel from "next-auth/providers/zitadel"
|
||||
|
||||
// Adapters
|
||||
import Apple from "@auth/core/providers/apple"
|
||||
import Auth0 from "@auth/core/providers/auth0"
|
||||
import AzureAD from "@auth/core/providers/azure-ad"
|
||||
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
|
||||
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
|
||||
// import Cognito from "@auth/core/providers/cognito"
|
||||
import Credentials from "@auth/core/providers/credentials"
|
||||
import Discord from "@auth/core/providers/discord"
|
||||
import DuendeIDS6 from "@auth/core/providers/duende-identity-server6"
|
||||
// import Email from "@auth/core/providers/email"
|
||||
import Facebook from "@auth/core/providers/facebook"
|
||||
import Foursquare from "@auth/core/providers/foursquare"
|
||||
import Freshbooks from "@auth/core/providers/freshbooks"
|
||||
import GitHub from "@auth/core/providers/github"
|
||||
import Gitlab from "@auth/core/providers/gitlab"
|
||||
import Google from "@auth/core/providers/google"
|
||||
// import IDS4 from "@auth/core/providers/identity-server4"
|
||||
import Instagram from "@auth/core/providers/instagram"
|
||||
// import Keycloak from "@auth/core/providers/keycloak"
|
||||
import Line from "@auth/core/providers/line"
|
||||
import LinkedIn from "@auth/core/providers/linkedin"
|
||||
import Mailchimp from "@auth/core/providers/mailchimp"
|
||||
// import Okta from "@auth/core/providers/okta"
|
||||
import Osu from "@auth/core/providers/osu"
|
||||
import Patreon from "@auth/core/providers/patreon"
|
||||
import Slack from "@auth/core/providers/slack"
|
||||
import Spotify from "@auth/core/providers/spotify"
|
||||
import Trakt from "@auth/core/providers/trakt"
|
||||
import Twitch from "@auth/core/providers/twitch"
|
||||
import Twitter from "@auth/core/providers/twitter"
|
||||
import Vk from "@auth/core/providers/vk"
|
||||
import Wikimedia from "@auth/core/providers/wikimedia"
|
||||
import WorkOS from "@auth/core/providers/workos"
|
||||
|
||||
// // Prisma
|
||||
// import { PrismaClient } from "@prisma/client"
|
||||
@@ -65,9 +59,16 @@ import Zitadel from "next-auth/providers/zitadel"
|
||||
// synchronize: true,
|
||||
// })
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
// // Supabase
|
||||
// import { SupabaseAdapter } from "@next-auth/supabase-adapter"
|
||||
// const adapter = SupabaseAdapter({
|
||||
// url: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||
// })
|
||||
|
||||
export const authOptions: AuthOptions = {
|
||||
// adapter,
|
||||
debug: process.env.NODE_ENV !== "production",
|
||||
// debug: process.env.NODE_ENV !== "production",
|
||||
theme: {
|
||||
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
|
||||
brandColor: "#1786fb",
|
||||
@@ -77,15 +78,19 @@ export const authOptions: NextAuthOptions = {
|
||||
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" }
|
||||
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" }
|
||||
},
|
||||
}),
|
||||
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
|
||||
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 }),
|
||||
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 }),
|
||||
// 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 }),
|
||||
@@ -94,36 +99,56 @@ export const authOptions: NextAuthOptions = {
|
||||
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 }),
|
||||
Hubspot({ clientId: process.env.HUBSPOT_ID, clientSecret: process.env.HUBSPOT_SECRET }),
|
||||
IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
|
||||
// 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 }),
|
||||
// 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 }),
|
||||
// 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 }),
|
||||
Todoist({ clientId: process.env.TODOIST_ID, clientSecret: process.env.TODOIST_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 }),
|
||||
Twitter({ 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 }),
|
||||
Zitadel({ issuer: process.env.ZITADEL_ISSUER, clientId: process.env.ZITADEL_CLIENT_ID, clientSecret: process.env.ZITADEL_CLIENT_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" })
|
||||
)
|
||||
// TODO:
|
||||
// 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)
|
||||
// TODO: move to next-auth/edge
|
||||
function Auth(...args: any[]) {
|
||||
const envSecret = process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET
|
||||
const envTrustHost = !!(process.env.NEXTAUTH_URL ?? process.env.AUTH_TRUST_HOST ?? process.env.VERCEL ?? process.env.NODE_ENV !== "production")
|
||||
if (args.length === 1) {
|
||||
return async (req: Request) => {
|
||||
args[0].secret ??= envSecret
|
||||
args[0].trustHost ??= envTrustHost
|
||||
return await AuthHandler(req, args[0])
|
||||
}
|
||||
}
|
||||
args[1].secret ??= envSecret
|
||||
args[1].trustHost ??= envTrustHost
|
||||
return AuthHandler(args[0], args[1])
|
||||
}
|
||||
|
||||
// export default Auth(authOptions)
|
||||
|
||||
export default function handle(request: Request) {
|
||||
return Auth(request, authOptions)
|
||||
}
|
||||
|
||||
export const config = { runtime: "experimental-edge" }
|
||||
|
||||
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))
|
||||
}
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
2
apps/dev/types/nextauth.d.ts
vendored
2
apps/dev/types/nextauth.d.ts
vendored
@@ -6,6 +6,8 @@ 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
|
||||
|
||||
13
apps/playground-sveltekit/.eslintignore
Normal file
13
apps/playground-sveltekit/.eslintignore
Normal file
@@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
@@ -1,24 +1,20 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier",
|
||||
],
|
||||
plugins: ["svelte3", "@typescript-eslint"],
|
||||
ignorePatterns: ["*.cjs", "build/**/*"],
|
||||
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
|
||||
settings: {
|
||||
"svelte3/typescript": () => require("typescript"),
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true,
|
||||
},
|
||||
}
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
plugins: ['svelte3', '@typescript-eslint'],
|
||||
ignorePatterns: ['*.cjs'],
|
||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
}
|
||||
};
|
||||
|
||||
4
apps/playground-sveltekit/.gitignore
vendored
4
apps/playground-sveltekit/.gitignore
vendored
@@ -6,3 +6,7 @@ node_modules
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.vercel
|
||||
.output
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
13
apps/playground-sveltekit/.prettierignore
Normal file
13
apps/playground-sveltekit/.prettierignore
Normal file
@@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
9
apps/playground-sveltekit/.prettierrc
Normal file
9
apps/playground-sveltekit/.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
@@ -1,43 +1,37 @@
|
||||
{
|
||||
"name": "sveltekit-nextauth",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"start": "HOST=127.0.0.1 PORT=5173 ORIGIN=http://localhost:5173 node ./build",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^1.0.0-next.80",
|
||||
"@sveltejs/adapter-node": "1.0.0-next.96",
|
||||
"@sveltejs/kit": "1.0.0-next.511",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||
"@typescript-eslint/parser": "^5.35.1",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-check": "^2.8.1",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "~4.8.2",
|
||||
"vite": "^3.1.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"cookie": "0.5.0",
|
||||
"next-auth": "latest"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"singleQuote": false
|
||||
}
|
||||
"name": "sveltekit-nextauth",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/fira-mono": "^4.5.10",
|
||||
"@neoconfetti/svelte": "^1.0.0",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-check": "^2.9.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": "0.5.0",
|
||||
"@auth/core": "workspace:*",
|
||||
"@auth/sveltekit": "workspace:^"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
4
apps/playground-sveltekit/src/app.d.ts
vendored
4
apps/playground-sveltekit/src/app.d.ts
vendored
@@ -1,4 +1,5 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
/// <reference types="next-auth-sveltekit" />
|
||||
import type {
|
||||
User as NextAuthUser,
|
||||
Session as NextAuthSession,
|
||||
@@ -18,7 +19,8 @@ interface AppSession extends NextAuthSession {
|
||||
declare global {
|
||||
declare namespace App {
|
||||
interface Locals {
|
||||
session: AppSession
|
||||
// session: AppSession
|
||||
getSession: () => Promise<AppSession>
|
||||
}
|
||||
|
||||
interface Platform {}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,14 +1,25 @@
|
||||
import type { Handle } from "@sveltejs/kit"
|
||||
import { getServerSession, options as nextAuthOptions } from "$lib/next-auth"
|
||||
import SvelteKitAuth from "@auth/sveltekit"
|
||||
import GitHub from '@auth/core/providers/github';
|
||||
import Google from '@auth/core/providers/google';
|
||||
import Credentials from '@auth/core/providers/credentials';
|
||||
import {
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET,
|
||||
} from "$env/static/private"
|
||||
|
||||
export const handle: Handle = async function handle({
|
||||
event,
|
||||
resolve,
|
||||
}): Promise<Response> {
|
||||
const session = await getServerSession(event.request, nextAuthOptions)
|
||||
if (session) {
|
||||
event.locals.session = session
|
||||
}
|
||||
|
||||
return resolve(event)
|
||||
}
|
||||
export const handle = SvelteKitAuth({
|
||||
providers: [
|
||||
GitHub({ clientId: GITHUB_CLIENT_ID, clientSecret: GITHUB_CLIENT_SECRET }),
|
||||
Google({ clientId: GOOGLE_CLIENT_ID, clientSecret: GOOGLE_CLIENT_SECRET }),
|
||||
Credentials({
|
||||
credentials: { password: { label: "Password", type: "password" } },
|
||||
async authorize(credentials) {
|
||||
if (credentials.password !== "pw") return null
|
||||
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" }
|
||||
},
|
||||
}),
|
||||
],
|
||||
debug: true,
|
||||
});
|
||||
|
||||
12
apps/playground-sveltekit/src/lib/SignInButton.svelte
Normal file
12
apps/playground-sveltekit/src/lib/SignInButton.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
export let provider: any;
|
||||
</script>
|
||||
|
||||
<form action={provider.signinUrl} method="POST">
|
||||
{#if provider.callbackUrl}
|
||||
<input type="hidden" name="callbackUrl" value={provider.callbackUrl} />
|
||||
{/if}
|
||||
<button type="submit" class="button">
|
||||
<slot>Sign in with {provider.name}</slot>
|
||||
</button>
|
||||
</form>
|
||||
@@ -1,144 +0,0 @@
|
||||
import type { ServerLoadEvent } from "@sveltejs/kit"
|
||||
import type { RequestInternal } from "next-auth"
|
||||
import type { NextAuthAction, NextAuthOptions } from "next-auth/core/types"
|
||||
import type { OutgoingResponse as NextAuthResponse } from "next-auth/core"
|
||||
import { NextAuthHandler } from "next-auth/core"
|
||||
import GithubProvider from "next-auth/providers/github"
|
||||
import cookie from "cookie"
|
||||
import {
|
||||
GITHUB_CLIENT_ID,
|
||||
GITHUB_CLIENT_SECRET,
|
||||
NEXTAUTH_SECRET,
|
||||
} from "$env/static/private"
|
||||
import { PUBLIC_NEXTAUTH_URL } from "$env/static/public"
|
||||
|
||||
// @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,
|
||||
nextAuthResponse: NextAuthResponse<T>
|
||||
): Promise<Response> => {
|
||||
const { cookies, redirect } = nextAuthResponse
|
||||
|
||||
const headers = new Headers()
|
||||
for (const header of nextAuthResponse?.headers || []) {
|
||||
// pass headers along from next-auth
|
||||
headers.set(header.key, header.value)
|
||||
}
|
||||
|
||||
// set-cookie header
|
||||
if (cookies?.length) {
|
||||
headers.set(
|
||||
"set-cookie",
|
||||
cookies
|
||||
?.map((item) => cookie.serialize(item.name, item.value, item.options))
|
||||
.join(",") as string
|
||||
)
|
||||
}
|
||||
|
||||
let body = undefined
|
||||
let status = nextAuthResponse.status || 200
|
||||
|
||||
if (redirect) {
|
||||
let formData: FormData | null = null
|
||||
try {
|
||||
formData = await request.formData()
|
||||
} catch {
|
||||
// no formData passed
|
||||
}
|
||||
const { json } = Object.fromEntries(formData ?? [])
|
||||
if (json !== "true") {
|
||||
status = 302
|
||||
headers.set("Location", redirect)
|
||||
} else {
|
||||
body = { url: redirect }
|
||||
}
|
||||
} else {
|
||||
body = nextAuthResponse.body
|
||||
}
|
||||
|
||||
// @ts-expect-error - body is a known HTML document or JSON object
|
||||
return new Response(body, {
|
||||
status,
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
const SKNextAuthHandler = async (
|
||||
{ request, url, params }: ServerLoadEvent,
|
||||
options: NextAuthOptions
|
||||
): Promise<Response> => {
|
||||
const [action, provider] = params.nextauth!.split("/")
|
||||
let body: FormData | undefined
|
||||
try {
|
||||
body = await request.formData()
|
||||
} catch {
|
||||
// no formData passed
|
||||
}
|
||||
options.secret = NEXTAUTH_SECRET
|
||||
const req: RequestInternal = {
|
||||
host: PUBLIC_NEXTAUTH_URL,
|
||||
body: Object.fromEntries(body ?? []),
|
||||
query: Object.fromEntries(url.searchParams),
|
||||
headers: request.headers,
|
||||
method: request.method,
|
||||
cookies: cookie.parse(request.headers.get("cookie") || ""),
|
||||
action: action as NextAuthAction,
|
||||
providerId: provider,
|
||||
error: provider,
|
||||
}
|
||||
|
||||
const response = await NextAuthHandler({
|
||||
req,
|
||||
options,
|
||||
})
|
||||
|
||||
return toSvelteKitResponse(request, response)
|
||||
}
|
||||
|
||||
export const getServerSession = async (
|
||||
request: Request,
|
||||
options: NextAuthOptions
|
||||
): Promise<App.Session | null> => {
|
||||
options.secret = NEXTAUTH_SECRET
|
||||
|
||||
const session = await NextAuthHandler<App.Session>({
|
||||
req: {
|
||||
host: PUBLIC_NEXTAUTH_URL,
|
||||
action: "session",
|
||||
method: "GET",
|
||||
cookies: cookie.parse(request.headers.get("cookie") || ""),
|
||||
headers: request.headers,
|
||||
},
|
||||
options,
|
||||
})
|
||||
|
||||
const { body } = session
|
||||
|
||||
if (body && Object.keys(body).length) {
|
||||
return body as App.Session
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const NextAuth = (
|
||||
options: NextAuthOptions
|
||||
): {
|
||||
GET: (event: ServerLoadEvent) => Promise<unknown>
|
||||
POST: (event: ServerLoadEvent) => Promise<unknown>
|
||||
} => ({
|
||||
GET: (event) => SKNextAuthHandler(event, options),
|
||||
POST: (event) => SKNextAuthHandler(event, options),
|
||||
})
|
||||
@@ -1,7 +1,14 @@
|
||||
import type { LayoutServerLoad } from "./$types"
|
||||
|
||||
export const load: LayoutServerLoad = ({ locals }) => {
|
||||
export const load: LayoutServerLoad = (event) => {
|
||||
console.log('layout server load', event.locals.getSession)
|
||||
let session
|
||||
if (event.locals.getSession)
|
||||
{
|
||||
session = event.locals.getSession()
|
||||
|
||||
}
|
||||
return {
|
||||
session: locals.session,
|
||||
session,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +1,144 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores"
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<header>
|
||||
<div class="signedInStatus">
|
||||
<p class="nojs-show loaded">
|
||||
{#if Object.keys($page.data.session || {}).length}
|
||||
{#if $page.data.session.user.image}
|
||||
<span
|
||||
style="background-image: url('{$page.data.session.user.image}')"
|
||||
class="avatar"
|
||||
/>
|
||||
{/if}
|
||||
<span class="signedInText">
|
||||
<small>Signed in as</small><br />
|
||||
<strong
|
||||
>{$page.data.session.user.email ||
|
||||
$page.data.session.user.name}</strong
|
||||
>
|
||||
</span>
|
||||
<a href="/api/auth/signout" class="button">Sign out</a>
|
||||
{:else}
|
||||
<span class="notSignedInText">You are not signed in</span>
|
||||
<a href="/api/auth/signin" class="buttonPrimary">Sign in</a>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="navItems">
|
||||
<li class="navItem"><a href="/">Home</a></li>
|
||||
<li class="navItem"><a href="/protected">Protected</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<slot />
|
||||
<header>
|
||||
<div class="signedInStatus">
|
||||
<p class="nojs-show loaded">
|
||||
{#if Object.keys($page.data.session || {}).length}
|
||||
{#if $page.data.session.user.image}
|
||||
<span style="background-image: url('{$page.data.session.user.image}')" class="avatar" />
|
||||
{/if}
|
||||
<span class="signedInText">
|
||||
<small>Signed in as</small><br />
|
||||
<strong>{$page.data.session.user.email || $page.data.session.user.name}</strong>
|
||||
</span>
|
||||
<a href="/auth/signout" class="button">Sign out</a>
|
||||
{:else}
|
||||
<span class="notSignedInText">You are not signed in</span>
|
||||
<a href="/auth/signin" class="buttonPrimary">Sign in</a>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="navItems">
|
||||
<li class="navItem"><a href="/">Home</a></li>
|
||||
<li class="navItem"><a href="/protected">Protected</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(body) {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
|
||||
"Noto Color Emoji";
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
:global(li),
|
||||
:global(p) {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
:global(a) {
|
||||
font-weight: 500;
|
||||
}
|
||||
:global(hr) {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
:global(iframe) {
|
||||
background: #ccc;
|
||||
border: 1px solid #ccc;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
filter: invert(1);
|
||||
}
|
||||
:global(body) {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
:global(li),
|
||||
:global(p) {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
:global(a) {
|
||||
font-weight: 500;
|
||||
}
|
||||
:global(hr) {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
:global(iframe) {
|
||||
background: #ccc;
|
||||
border: 1px solid #ccc;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.nojs-show {
|
||||
opacity: 1;
|
||||
top: 0;
|
||||
}
|
||||
.signedInStatus {
|
||||
display: block;
|
||||
min-height: 4rem;
|
||||
width: 100%;
|
||||
}
|
||||
.loaded {
|
||||
position: relative;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 0.6rem 0.6rem;
|
||||
padding: 0.6rem 1rem;
|
||||
margin: 0;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease-in;
|
||||
}
|
||||
.signedInText,
|
||||
.notSignedInText {
|
||||
position: absolute;
|
||||
padding-top: 0.8rem;
|
||||
left: 1rem;
|
||||
right: 6.5rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: inherit;
|
||||
z-index: 1;
|
||||
line-height: 1.3rem;
|
||||
}
|
||||
.signedInText {
|
||||
padding-top: 0rem;
|
||||
left: 4.6rem;
|
||||
}
|
||||
.avatar {
|
||||
border-radius: 2rem;
|
||||
float: left;
|
||||
height: 2.8rem;
|
||||
width: 2.8rem;
|
||||
background-color: white;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.button,
|
||||
.buttonPrimary {
|
||||
float: right;
|
||||
margin-right: -0.4rem;
|
||||
font-weight: 500;
|
||||
border-radius: 0.3rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
line-height: 1.4rem;
|
||||
padding: 0.7rem 0.8rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: transparent;
|
||||
color: #555;
|
||||
}
|
||||
.buttonPrimary {
|
||||
background-color: #346df1;
|
||||
border-color: #346df1;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
padding: 0.7rem 1.4rem;
|
||||
}
|
||||
.buttonPrimary:hover {
|
||||
box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.navItems {
|
||||
margin-bottom: 2rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.navItem {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.nojs-show {
|
||||
opacity: 1;
|
||||
top: 0;
|
||||
}
|
||||
.signedInStatus {
|
||||
display: block;
|
||||
min-height: 4rem;
|
||||
width: 100%;
|
||||
}
|
||||
.loaded {
|
||||
position: relative;
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 0.6rem 0.6rem;
|
||||
padding: 0.6rem 1rem;
|
||||
margin: 0;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease-in;
|
||||
}
|
||||
.signedInText,
|
||||
.notSignedInText {
|
||||
position: absolute;
|
||||
padding-top: 0.8rem;
|
||||
left: 1rem;
|
||||
right: 6.5rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: inherit;
|
||||
z-index: 1;
|
||||
line-height: 1.3rem;
|
||||
}
|
||||
.signedInText {
|
||||
padding-top: 0rem;
|
||||
left: 4.6rem;
|
||||
}
|
||||
.avatar {
|
||||
border-radius: 2rem;
|
||||
float: left;
|
||||
height: 2.8rem;
|
||||
width: 2.8rem;
|
||||
background-color: white;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.button,
|
||||
.buttonPrimary {
|
||||
float: right;
|
||||
margin-right: -0.4rem;
|
||||
font-weight: 500;
|
||||
border-radius: 0.3rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
line-height: 1.4rem;
|
||||
padding: 0.7rem 0.8rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: transparent;
|
||||
color: #555;
|
||||
}
|
||||
.buttonPrimary {
|
||||
background-color: #346df1;
|
||||
border-color: #346df1;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
padding: 0.7rem 1.4rem;
|
||||
}
|
||||
.buttonPrimary:hover {
|
||||
box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.navItems {
|
||||
margin-bottom: 2rem;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.navItem {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
<script>
|
||||
import { signIn, signOut } from '@auth/sveltekit/client';
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<h1>SvelteKit + NextAuth.js Example</h1>
|
||||
<p>
|
||||
This is an example site to demonstrate how to use <a
|
||||
href="https://kit.svelte.dev/">SvelteKit</a
|
||||
>
|
||||
with <a href="https://next-auth.js.org">NextAuth.js</a> for authentication.
|
||||
This is an example site to demonstrate how to use <a href="https://kit.svelte.dev/">SvelteKit</a>
|
||||
with <a href="https://next-auth.js.org">NextAuth.js</a> for authentication.
|
||||
|
||||
{#if Object.keys($page.data.session || {}).length}
|
||||
{#if $page.data.session.user.image}
|
||||
<span style="background-image: url('{$page.data.session.user.image}')" class="avatar" />
|
||||
{/if}
|
||||
<span class="signedInText">
|
||||
<small>Signed in as</small><br />
|
||||
<strong>{$page.data.session.user.email || $page.data.session.user.name}</strong>
|
||||
</span>
|
||||
<button on:click={() => signOut()} class="button">Sign out</button>
|
||||
{:else}
|
||||
<span class="notSignedInText">You are not signed in</span>
|
||||
<button on:click={() => signIn('github')}>Sign In with GitHub</button>
|
||||
<button on:click={() => signIn('credentials', { redirect: false })}>Sign In credentials</button>
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { NextAuth, options } from "$lib/next-auth"
|
||||
|
||||
export const { GET, POST } = NextAuth(options)
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,14 +1,15 @@
|
||||
import adapter from "@sveltejs/adapter-node" // or use https://github.com/sveltejs/kit/tree/master/packages/adapter-auto
|
||||
import preprocess from "svelte-preprocess"
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: preprocess(),
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
},
|
||||
}
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
export default config
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
|
||||
8
apps/playground-sveltekit/vite.config.js
Normal file
8
apps/playground-sveltekit/vite.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit()]
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,8 +0,0 @@
|
||||
import { sveltekit } from "@sveltejs/kit/vite"
|
||||
import type { UserConfig } from "vite"
|
||||
|
||||
const config: UserConfig = {
|
||||
plugins: [sveltekit()],
|
||||
}
|
||||
|
||||
export default config
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting me@iaincollins.com. 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 is obligated to maintain
|
||||
confidentiality with regard to the reporter of an incident. Further details of
|
||||
specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -1,26 +0,0 @@
|
||||
# Contributing guide
|
||||
|
||||
Contributions and feedback on your experience of using this software are welcome.
|
||||
|
||||
This includes bug reports, feature requests, ideas, pull requests, and examples of how you have used this software.
|
||||
|
||||
Please see the [Code of Conduct](CODE_OF_CONDUCT.md) and follow any templates configured in GitHub when reporting bugs, requesting enhancements, or contributing code.
|
||||
|
||||
Please raise any significant new functionality or breaking change an issue for discussion before raising a Pull Request for it.
|
||||
|
||||
## For contributors
|
||||
|
||||
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.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
- The latest changes are always in `main`, so please make your Pull Request against that branch.
|
||||
- Pull Requests should be raised for any change
|
||||
|
||||
### Setting up local environment
|
||||
|
||||
The local environment can be setup by doing the following.
|
||||
|
||||
1. Clone the repository `$ git clone https://github.com/nextauthjs/docs.git`
|
||||
2. Install dependencies `$ npm install`
|
||||
3. Start development server `$ npm start`
|
||||
@@ -15,26 +15,23 @@ The MongoDB adapter does not handle connections automatically, so you will have
|
||||
npm install next-auth @next-auth/mongodb-adapter mongodb
|
||||
```
|
||||
|
||||
2. Add `lib/mongodb.js`
|
||||
2. Add `lib/mongodb.ts`
|
||||
|
||||
```js
|
||||
```ts
|
||||
// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
|
||||
import { MongoClient } from "mongodb"
|
||||
|
||||
const uri = process.env.MONGODB_URI
|
||||
const options = {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true,
|
||||
}
|
||||
|
||||
let client
|
||||
let clientPromise
|
||||
import { MongoClient } from 'mongodb'
|
||||
|
||||
if (!process.env.MONGODB_URI) {
|
||||
throw new Error("Please add your Mongo URI to .env.local")
|
||||
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const uri = process.env.MONGODB_URI
|
||||
const options = {}
|
||||
|
||||
let client
|
||||
let clientPromise: Promise<MongoClient>
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// In development mode, use a global variable so that the value
|
||||
// is preserved across module reloads caused by HMR (Hot Module Replacement).
|
||||
if (!global._mongoClientPromise) {
|
||||
|
||||
@@ -21,6 +21,7 @@ We have a list of official adapters that are distributed as their own packages u
|
||||
- [`neo4j`](./neo4j)
|
||||
- [`typeorm-legacy`](./typeorm)
|
||||
- [`sequelize`](./sequelize)
|
||||
- [`supabase`](./supabase)
|
||||
- [`dgraph`](./dgraph)
|
||||
- [`upstash-redis`](./upstash-redis)
|
||||
|
||||
|
||||
309
docs/docs/adapters/supabase.md
Normal file
309
docs/docs/adapters/supabase.md
Normal file
@@ -0,0 +1,309 @@
|
||||
---
|
||||
id: supabase
|
||||
title: Supabase
|
||||
---
|
||||
|
||||
# Supabase
|
||||
|
||||
This is the Supabase Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
|
||||
|
||||
:::note
|
||||
This adapter is developed by the community and not officially maintained or supported by Supabase. It uses the Supabase Database to store user and session data in a separate `next_auth` schema. It is a standalone Auth server that does not interface with Supabase Auth and therefore provides a different feature set.
|
||||
|
||||
If you’re looking for an officially maintained Auth server with additional features like [built-in email server](https://supabase.com/docs/guides/auth/auth-email#configure-email-settings?utm_source=next-auth-docs&medium=referral&campaign=next-auth), [phone auth](https://supabase.com/docs/guides/auth/auth-twilio?utm_source=next-auth-docs&medium=referral&campaign=next-auth), and [Multi Factor Authentication (MFA / 2FA)](https://supabase.com/contact/mfa?utm_source=next-auth-docs&medium=referral&campaign=next-auth), please use [Supabase Auth](https://supabase.com/auth) with the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs?utm_source=next-auth-docs&medium=referral&campaign=next-auth).
|
||||
:::
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install `@supabase/supabase-js`, `next-auth` and `@next-auth/supabase-adapter`.
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
npm install @supabase/supabase-js next-auth @next-auth/supabase-adapter
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import NextAuth from "next-auth"
|
||||
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
export default NextAuth({
|
||||
// https://next-auth.js.org/configuration/providers
|
||||
providers: [...],
|
||||
adapter: SupabaseAdapter({
|
||||
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||
}),
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
### Create the `next_auth` schema in Supabase
|
||||
|
||||
Setup your database as described in our main [schema](/adapters/models), by copying the SQL schema below in the Supabase [SQL Editor](https://app.supabase.com/project/_/sql).
|
||||
|
||||
Alternatively you can select the NextAuth Quickstart card on the [SQL Editor page](https://app.supabase.com/project/_/sql), or [create a migration with the Supabase CLI](https://supabase.com/docs/guides/cli/local-development#database-migrations?utm_source=next-auth-docs&medium=referral&campaign=next-auth).
|
||||
|
||||
```sql
|
||||
--
|
||||
-- Name: next_auth; Type: SCHEMA;
|
||||
--
|
||||
CREATE SCHEMA next_auth;
|
||||
|
||||
GRANT USAGE ON SCHEMA next_auth TO service_role;
|
||||
GRANT ALL ON SCHEMA next_auth TO postgres;
|
||||
|
||||
--
|
||||
-- Create users table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.users
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
name text,
|
||||
email text,
|
||||
"emailVerified" timestamp with time zone,
|
||||
image text,
|
||||
CONSTRAINT users_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT email_unique UNIQUE (email)
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.users TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.users TO service_role;
|
||||
|
||||
--- uid() function to be used in RLS policies
|
||||
CREATE FUNCTION next_auth.uid() RETURNS uuid
|
||||
LANGUAGE sql STABLE
|
||||
AS $$
|
||||
select
|
||||
coalesce(
|
||||
nullif(current_setting('request.jwt.claim.sub', true), ''),
|
||||
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
|
||||
)::uuid
|
||||
$$;
|
||||
|
||||
--
|
||||
-- Create sessions table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.sessions
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
expires timestamp with time zone NOT NULL,
|
||||
"sessionToken" text NOT NULL,
|
||||
"userId" uuid,
|
||||
CONSTRAINT sessions_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"),
|
||||
CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId")
|
||||
REFERENCES next_auth.users (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.sessions TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.sessions TO service_role;
|
||||
|
||||
--
|
||||
-- Create accounts table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.accounts
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
type text NOT NULL,
|
||||
provider text NOT NULL,
|
||||
"providerAccountId" text NOT NULL,
|
||||
refresh_token text,
|
||||
access_token text,
|
||||
expires_at bigint,
|
||||
token_type text,
|
||||
scope text,
|
||||
id_token text,
|
||||
session_state text,
|
||||
oauth_token_secret text,
|
||||
oauth_token text,
|
||||
"userId" uuid,
|
||||
CONSTRAINT accounts_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"),
|
||||
CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId")
|
||||
REFERENCES next_auth.users (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.accounts TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.accounts TO service_role;
|
||||
|
||||
--
|
||||
-- Create verification_tokens table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.verification_tokens
|
||||
(
|
||||
identifier text,
|
||||
token text,
|
||||
expires timestamp with time zone NOT NULL,
|
||||
CONSTRAINT verification_tokens_pkey PRIMARY KEY (token),
|
||||
CONSTRAINT token_unique UNIQUE (token),
|
||||
CONSTRAINT token_identifier_unique UNIQUE (token, identifier)
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.verification_tokens TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.verification_tokens TO service_role;
|
||||
```
|
||||
|
||||
### Expose the `next_auth` schema in Supabase
|
||||
|
||||
Expose the `next_auth` schema via the Serverless API in the [API settings](https://app.supabase.com/project/_/settings/api) by adding `next_auth` to the "Exposed schemas" list.
|
||||
|
||||
When developing locally add `next_auth` to the `schemas` array in the `config.toml` file in the `supabase` folder that was generated by the [Supabase CLI](https://supabase.com/docs/guides/cli/local-development#initialize-your-project?utm_source=next-auth-docs&medium=referral&campaign=next-auth).
|
||||
|
||||
## Enabling Row Level Security (RLS)
|
||||
|
||||
Postgres provides a powerful feature called [Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security?utm_source=next-auth-docs&medium=referral&campaign=next-auth) to limit access to data.
|
||||
|
||||
This works by sending a signed JWT to your [Supabase Serverless API](https://supabase.com/docs/guides/api?utm_source=next-auth-docs&medium=referral&campaign=next-auth). There is two steps to make this work with NextAuth:
|
||||
|
||||
### 1. Generate the Supabase `access_token` JWT in the session callback
|
||||
|
||||
To sign the JWT use the `jsonwebtoken` package:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
npm install jsonwebtoken
|
||||
```
|
||||
|
||||
Using the [NexthAuth Session callback](https://next-auth.js.org/configuration/callbacks#session-callback) create the Supabase `access_token` and append it to the `session` object.
|
||||
|
||||
To sign the JWT use the Supabase JWT secret which can be found in the [API settings](https://app.supabase.com/project/_/settings/api)
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import NextAuth from "next-auth"
|
||||
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
export default NextAuth({
|
||||
// https://next-auth.js.org/configuration/providers
|
||||
providers: [...],
|
||||
adapter: SupabaseAdapter({
|
||||
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||
}),
|
||||
callbacks: {
|
||||
async session({ session, user }) {
|
||||
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
|
||||
},
|
||||
},
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### 2. Inject the Supabase `access_token` JWT into the Supabase Client
|
||||
|
||||
For example, given the following public schema:
|
||||
|
||||
```sql
|
||||
/**
|
||||
* USERS
|
||||
* Note: This table contains user data. Users should only be able to view and update their own data.
|
||||
*/
|
||||
create table users (
|
||||
-- UUID from next_auth.users
|
||||
id uuid not null primary key,
|
||||
name text,
|
||||
email text,
|
||||
image text,
|
||||
constraint "users_id_fkey" foreign key ("id")
|
||||
references next_auth.users (id) match simple
|
||||
on update no action
|
||||
on delete cascade -- if user is deleted in NextAuth they will also be deleted in our public table.
|
||||
);
|
||||
alter table users enable row level security;
|
||||
create policy "Can view own user data." on users for select using (next_auth.uid() = id);
|
||||
create policy "Can update own user data." on users for update using (next_auth.uid() = id);
|
||||
|
||||
/**
|
||||
* This trigger automatically creates a user entry when a new user signs up via NextAuth.
|
||||
*/
|
||||
create function public.handle_new_user()
|
||||
returns trigger as $$
|
||||
begin
|
||||
insert into public.users (id, name, email, image)
|
||||
values (new.id, new.name, new.email, new.image);
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
create trigger on_auth_user_created
|
||||
after insert on next_auth.users
|
||||
for each row execute procedure public.handle_new_user();
|
||||
```
|
||||
|
||||
The `supabaseAccessToken` is now available on the `session` object and can be passed to the supabase-js client. This works in any environment: client-side, server-side (API routes, SSR), as well as in middleware edge functions!
|
||||
|
||||
```js
|
||||
// ...
|
||||
// Use `useSession()` or `unstable_getServerSession()` to get the NextAuth 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("*")
|
||||
```
|
||||
|
||||
## Usage with TypeScript
|
||||
|
||||
You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion.
|
||||
|
||||
Creating a new supabase client object:
|
||||
|
||||
```tsx
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
import { Database } from "../database.types"
|
||||
|
||||
const supabase = createClient<Database>()
|
||||
```
|
||||
|
||||
### Extend the session type with the `supabaseAccessToken`
|
||||
|
||||
In order to extend the `session` object with the `supabaseAccessToken` we need to extend the `session` interface in a `types/next-auth.d.ts` file:
|
||||
|
||||
```ts title="types/next-auth.d.ts"
|
||||
import NextAuth, { DefaultSession } 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
|
||||
} & DefaultSession["user"]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -75,9 +75,10 @@ You can also use `unstable_getServerSession` in Next.js' server components:
|
||||
|
||||
```tsx
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "pages/api/auth/[...nextauth]"
|
||||
|
||||
export default async function Page() {
|
||||
const session = await unstable_getServerSession()
|
||||
const session = await unstable_getServerSession(authOptions)
|
||||
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -472,7 +472,8 @@ cookies: {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
secure: useSecureCookies
|
||||
secure: useSecureCookies,
|
||||
maxAge: 900
|
||||
}
|
||||
},
|
||||
state: {
|
||||
@@ -482,6 +483,7 @@ cookies: {
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: useSecureCookies,
|
||||
maxAge: 900
|
||||
},
|
||||
},
|
||||
nonce: {
|
||||
|
||||
@@ -13,7 +13,7 @@ Adding support for signing in via email in addition to one or more OAuth service
|
||||
Configuration is similar to other providers, but the options are different:
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import EmailProvider from `next-auth/providers/email`
|
||||
import EmailProvider from "next-auth/providers/email"
|
||||
...
|
||||
providers: [
|
||||
EmailProvider({
|
||||
|
||||
@@ -174,6 +174,10 @@ interface OAuthConfig {
|
||||
issuer?: string
|
||||
client?: Partial<ClientMetadata>
|
||||
allowDangerousEmailAccountLinking?: boolean
|
||||
/**
|
||||
* Object containing the settings for the styling of the providers sign-in buttons
|
||||
*/
|
||||
style: ProviderStyleType
|
||||
}
|
||||
```
|
||||
|
||||
@@ -428,7 +432,8 @@ If you think your custom provider might be useful to others, we encourage you to
|
||||
You only need to add three changes:
|
||||
|
||||
1. Add your config: [`src/providers/{provider}.ts`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers)<br />
|
||||
• make sure you use a named default export, like this: `export default function YourProvider`
|
||||
- Make sure you use a named default export, like this: `export default function YourProvider`
|
||||
- Add two SVG's of the provider logo, like `google-dark.svg` (dark mode) and `google.svg` (light mode), to the `/packages/next-auth/provider-logos/` directory as well as the styling config to the provider config object. See existing provider for example
|
||||
2. Add provider documentation: [`/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/docs/docs/providers)
|
||||
3. Add the new provider name to the `Provider type` dropdown options in [`the provider issue template`](<[http](https://github.com/nextauthjs/next-auth/edit/main/.github/ISSUE_TEMPLATE/2_bug_provider.yml)>)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ http://developers.strava.com/docs/reference/
|
||||
|
||||
The **Strava Provider** comes with a set of default options:
|
||||
|
||||
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.js)
|
||||
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.ts)
|
||||
|
||||
You can override any of the options to suit your own use case. Ensure the redirect_uri configuration fits your needs accordingly.
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@ title: Tutorials and Explainers
|
||||
- Learn how to use Sign-In With Ethereum to authenticate your users with their existing Ethereum wallets - identifiers they personally control.
|
||||
- Example application: [spruceid/siwe-next-auth-example](https://github.com/spruceid/siwe-next-auth-example)
|
||||
|
||||
#### [Next.js Authentication with Okta and NextAuth.js 4.0](https://thetombomb.com/posts/nextjs-nextauth-okta) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
- Learn how to perform authentication with an OIDC Application in Okta and NextAuth.js.
|
||||
|
||||
## Fullstack
|
||||
|
||||
#### [Build a FullStack App with Next.js, NextAuth.js, Supabase & Prisma](https://themodern.dev/courses/build-a-fullstack-app-with-nextjs-supabase-and-prisma-322389284337222224) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
@@ -61,7 +61,7 @@ For the time being, the `withAuth` middleware only supports `"jwt"` as [session
|
||||
More details can be found [here](https://next-auth.js.org/configuration/nextjs#middleware).
|
||||
|
||||
:::tip
|
||||
To inclue all `dashboard` nested routes (sub pages like `/dashboard/settings`, `/dashboard/profile`) you can pass `matcher: "/dashboard/:path*"` to `config`.
|
||||
To include all `dashboard` nested routes (sub pages like `/dashboard/settings`, `/dashboard/profile`) you can pass `matcher: "/dashboard/:path*"` to `config`.
|
||||
|
||||
For other patterns check out the [Next.js Middleware documentation](https://nextjs.org/docs/advanced-features/middleware#matcher).
|
||||
:::
|
||||
|
||||
@@ -65,6 +65,7 @@ module.exports = {
|
||||
"adapters/neo4j",
|
||||
"adapters/typeorm",
|
||||
"adapters/sequelize",
|
||||
"adapters/supabase",
|
||||
"adapters/mikro-orm",
|
||||
"adapters/dgraph",
|
||||
"adapters/upstash-redis",
|
||||
|
||||
@@ -14,6 +14,10 @@ If you did not find a guide or tutorial covering your use case, please [open an
|
||||
- How to restrict access to pages and API routes.
|
||||
- [Usage with class components](/tutorials/usage-with-class-components)
|
||||
- How to use `useSession()` hook with class components.
|
||||
- [Next.js Authentication with Okta and NextAuth.js 4.0](https://thetombomb.com/posts/nextjs-nextauth-okta)
|
||||
- Learn how to perform authentication with an OIDC Application in Okta and NextAuth.js.
|
||||
|
||||
|
||||
|
||||
### Advanced
|
||||
|
||||
|
||||
15
package.json
15
package.json
@@ -11,6 +11,7 @@
|
||||
"clean": "turbo run clean --no-cache",
|
||||
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
|
||||
"dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*",
|
||||
"dev:kit": "turbo run dev --parallel --continue --filter=sveltekit-nextauth...",
|
||||
"dev:docs": "turbo run dev --filter=next-auth-docs",
|
||||
"email": "cd apps/dev && pnpm email",
|
||||
"release": "release",
|
||||
@@ -18,7 +19,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.6.0",
|
||||
"@balazsorban/monorepo-release": "0.0.5",
|
||||
"@balazsorban/monorepo-release": "0.1.0",
|
||||
"@types/jest": "^28.1.3",
|
||||
"@types/node": "^17.0.25",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
@@ -39,6 +40,11 @@
|
||||
"turbo": "1.3.1",
|
||||
"typescript": "4.8.4"
|
||||
},
|
||||
"release": {
|
||||
"packageDirectories": [
|
||||
"packages"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0"
|
||||
},
|
||||
@@ -64,5 +70,10 @@
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nextauth"
|
||||
}
|
||||
]
|
||||
],
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"undici": "5.11.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/firebase-adapter",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "Firebase adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase emulators:exec --only firestore --project next-auth-test jest"
|
||||
"test": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase --token '$FIREBASE_TOKEN' emulators:exec --only firestore --project next-auth-test jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"firebase": "^9.7.0",
|
||||
@@ -38,9 +38,9 @@
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:*",
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"firebase": "^9.7.0",
|
||||
"firebase-tools": "^10.7.2",
|
||||
"firebase": "^9.14.0",
|
||||
"firebase-tools": "^11.16.1",
|
||||
"jest": "^27.4.3",
|
||||
"next-auth": "workspace:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,10 +86,10 @@ export function FirestoreAdapter({
|
||||
async getUserByEmail(email) {
|
||||
const userQuery = query(Users, where("email", "==", email), limit(1))
|
||||
const userSnapshots = await getDocs(userQuery)
|
||||
const userSnpashot = userSnapshots.docs[0]
|
||||
const userSnapshot = userSnapshots.docs[0]
|
||||
|
||||
if (userSnpashot?.exists() && Users.converter) {
|
||||
return Users.converter.fromFirestore(userSnpashot)
|
||||
if (userSnapshot?.exists() && Users.converter) {
|
||||
return Users.converter.fromFirestore(userSnapshot)
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.base.json",
|
||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/pouchdb-adapter",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "PouchDB adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.base.json",
|
||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/sequelize-adapter",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "Sequelize adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -42,4 +42,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export const Account = {
|
||||
expires_at: { type: DataTypes.INTEGER },
|
||||
token_type: { type: DataTypes.STRING },
|
||||
scope: { type: DataTypes.STRING },
|
||||
id_token: { type: DataTypes.STRING },
|
||||
id_token: { type: DataTypes.TEXT },
|
||||
session_state: { type: DataTypes.STRING },
|
||||
userId: { type: DataTypes.UUID },
|
||||
}
|
||||
|
||||
3
packages/adapter-supabase/.env.example
Normal file
3
packages/adapter-supabase/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
# Run `supabase start` in this directory to get the Supabase API URL and service role key!
|
||||
SUPABASE_URL=http://localhost:54321
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
|
||||
57
packages/adapter-supabase/README.md
Normal file
57
packages/adapter-supabase/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://next-auth.js.org" target="_blank">
|
||||
<img height="64px" src="https://next-auth.js.org/img/logo/logo-sm.png" /></a><img height="64px" src="./logo.svg" />
|
||||
<h3 align="center"><b>Supabase Adapter</b> - NextAuth.js</h3>
|
||||
<p align="center">
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="Build Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/supabase-adapter/latest" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/supabase-adapter" alt="@next-auth/supabase-adapter Version" />
|
||||
</p>
|
||||
</p>
|
||||
|
||||
## Overview
|
||||
|
||||
This is the Supabase Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
|
||||
|
||||
You can find more Supabase information in the docs at [next-auth.js.org/adapters/supabase](https://next-auth.js.org/adapters/supabase).
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install `@supabase/supabase-js`, `next-auth` and `@next-auth/supabase-adapter`.
|
||||
|
||||
```js
|
||||
npm install @supabase/supabase-js next-auth @next-auth/supabase-adapter
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
|
||||
|
||||
```js
|
||||
import NextAuth from "next-auth"
|
||||
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
export default NextAuth({
|
||||
// https://next-auth.js.org/configuration/providers
|
||||
providers: [
|
||||
// ...
|
||||
],
|
||||
adapter: SupabaseAdapter({
|
||||
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||
}),
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
15
packages/adapter-supabase/logo.svg
Normal file
15
packages/adapter-supabase/logo.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="109" height="113" viewBox="0 0 109 113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint1_linear)" fill-opacity="0.2"/>
|
||||
<path d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z" fill="#3ECF8E"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="53.9738" y1="54.974" x2="94.1635" y2="71.8295" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#249361"/>
|
||||
<stop offset="1" stop-color="#3ECF8E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="36.1558" y1="30.578" x2="54.4844" y2="65.0806" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
40
packages/adapter-supabase/package.json
Normal file
40
packages/adapter-supabase/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@next-auth/supabase-adapter",
|
||||
"version": "0.2.0",
|
||||
"description": "Supabase adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "Martin Sonnberger <martin.sonnberger@icloud.com>",
|
||||
"main": "dist/index.js",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
"next.js",
|
||||
"supabase"
|
||||
],
|
||||
"license": "ISC",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "./tests/test.sh"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"next-auth": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
"@next-auth/tsconfig": "workspace:^0.0.0",
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"jest": "^27.4.3",
|
||||
"next-auth": "workspace:*"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
140
packages/adapter-supabase/src/database.types.ts
Normal file
140
packages/adapter-supabase/src/database.types.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
export type Json =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| { [key: string]: Json }
|
||||
| Json[]
|
||||
|
||||
export interface Database {
|
||||
next_auth: {
|
||||
Tables: {
|
||||
accounts: {
|
||||
Row: {
|
||||
id: string
|
||||
type: string | null
|
||||
provider: string | null
|
||||
providerAccountId: string | null
|
||||
refresh_token: string | null
|
||||
access_token: string | null
|
||||
expires_at: number | null
|
||||
token_type: string | null
|
||||
scope: string | null
|
||||
id_token: string | null
|
||||
session_state: string | null
|
||||
oauth_token_secret: string | null
|
||||
oauth_token: string | null
|
||||
userId: string | null
|
||||
}
|
||||
Insert: {
|
||||
id?: string
|
||||
type?: string | null
|
||||
provider?: string | null
|
||||
providerAccountId?: string | null
|
||||
refresh_token?: string | null
|
||||
access_token?: string | null
|
||||
expires_at?: number | null
|
||||
token_type?: string | null
|
||||
scope?: string | null
|
||||
id_token?: string | null
|
||||
session_state?: string | null
|
||||
oauth_token_secret?: string | null
|
||||
oauth_token?: string | null
|
||||
userId?: string | null
|
||||
}
|
||||
Update: {
|
||||
id?: string
|
||||
type?: string | null
|
||||
provider?: string | null
|
||||
providerAccountId?: string | null
|
||||
refresh_token?: string | null
|
||||
access_token?: string | null
|
||||
expires_at?: number | null
|
||||
token_type?: string | null
|
||||
scope?: string | null
|
||||
id_token?: string | null
|
||||
session_state?: string | null
|
||||
oauth_token_secret?: string | null
|
||||
oauth_token?: string | null
|
||||
userId?: string | null
|
||||
}
|
||||
}
|
||||
sessions: {
|
||||
Row: {
|
||||
expires: string | null
|
||||
sessionToken: string | null
|
||||
userId: string | null
|
||||
id: string
|
||||
}
|
||||
Insert: {
|
||||
expires?: string | null
|
||||
sessionToken?: string | null
|
||||
userId?: string | null
|
||||
id?: string
|
||||
}
|
||||
Update: {
|
||||
expires?: string | null
|
||||
sessionToken?: string | null
|
||||
userId?: string | null
|
||||
id?: string
|
||||
}
|
||||
}
|
||||
users: {
|
||||
Row: {
|
||||
name: string | null
|
||||
email: string | null
|
||||
emailVerified: string | null
|
||||
image: string | null
|
||||
id: string
|
||||
}
|
||||
Insert: {
|
||||
name?: string | null
|
||||
email?: string | null
|
||||
emailVerified?: string | null
|
||||
image?: string | null
|
||||
id?: string
|
||||
}
|
||||
Update: {
|
||||
name?: string | null
|
||||
email?: string | null
|
||||
emailVerified?: string | null
|
||||
image?: string | null
|
||||
id?: string
|
||||
}
|
||||
}
|
||||
verification_tokens: {
|
||||
Row: {
|
||||
id: number
|
||||
identifier: string | null
|
||||
token: string | null
|
||||
expires: string | null
|
||||
}
|
||||
Insert: {
|
||||
id?: number
|
||||
identifier?: string | null
|
||||
token?: string | null
|
||||
expires?: string | null
|
||||
}
|
||||
Update: {
|
||||
id?: number
|
||||
identifier?: string | null
|
||||
token?: string | null
|
||||
expires?: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
Views: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Functions: {
|
||||
uid: {
|
||||
Args: Record<PropertyKey, never>
|
||||
Returns: string
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
[_ in never]: never
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
212
packages/adapter-supabase/src/index.ts
Normal file
212
packages/adapter-supabase/src/index.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
import { Database } from "./database.types"
|
||||
import {
|
||||
Adapter,
|
||||
AdapterSession,
|
||||
AdapterUser,
|
||||
VerificationToken,
|
||||
} from "next-auth/adapters"
|
||||
|
||||
function isDate(date: any) {
|
||||
return (
|
||||
new Date(date).toString() !== "Invalid Date" && !isNaN(Date.parse(date))
|
||||
)
|
||||
}
|
||||
|
||||
export function format<T>(obj: Record<string, any>): T {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value === null) {
|
||||
delete obj[key]
|
||||
}
|
||||
|
||||
if (isDate(value)) {
|
||||
obj[key] = new Date(value)
|
||||
}
|
||||
}
|
||||
|
||||
return obj as T
|
||||
}
|
||||
|
||||
export const SupabaseAdapter = ({
|
||||
url,
|
||||
secret,
|
||||
}: {
|
||||
url: string
|
||||
secret: string
|
||||
}): Adapter => {
|
||||
const supabase = createClient<Database, "next_auth">(url, secret, {
|
||||
db: { schema: "next_auth" },
|
||||
global: {
|
||||
headers: { "X-Client-Info": "@next-auth/supabase-adapter@0.1.0" },
|
||||
},
|
||||
})
|
||||
return {
|
||||
async createUser(user) {
|
||||
const { data, error } = await supabase
|
||||
.from("users")
|
||||
.insert({
|
||||
...user,
|
||||
emailVerified: user.emailVerified?.toISOString(),
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return format<AdapterUser>(data)
|
||||
},
|
||||
async getUser(id) {
|
||||
const { data, error } = await supabase
|
||||
.from("users")
|
||||
.select()
|
||||
.eq("id", id)
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) return null
|
||||
|
||||
return format<AdapterUser>(data)
|
||||
},
|
||||
async getUserByEmail(email) {
|
||||
const { data, error } = await supabase
|
||||
.from("users")
|
||||
.select()
|
||||
.eq("email", email)
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) return null
|
||||
|
||||
return format<AdapterUser>(data)
|
||||
},
|
||||
async getUserByAccount({ providerAccountId, provider }) {
|
||||
const { data, error } = await supabase
|
||||
.from("accounts")
|
||||
.select("users (*)")
|
||||
.match({ provider, providerAccountId })
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data || !data.users) return null
|
||||
|
||||
return format<AdapterUser>(data.users)
|
||||
},
|
||||
async updateUser(user) {
|
||||
const { data, error } = await supabase
|
||||
.from("users")
|
||||
.update({
|
||||
...user,
|
||||
emailVerified: user.emailVerified?.toISOString(),
|
||||
})
|
||||
.eq("id", user.id)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return format<AdapterUser>(data)
|
||||
},
|
||||
async deleteUser(userId) {
|
||||
const { error } = await supabase.from("users").delete().eq("id", userId)
|
||||
|
||||
if (error) throw error
|
||||
},
|
||||
async linkAccount(account) {
|
||||
const { error } = await supabase.from("accounts").insert(account)
|
||||
|
||||
if (error) throw error
|
||||
},
|
||||
async unlinkAccount({ providerAccountId, provider }) {
|
||||
const { error } = await supabase
|
||||
.from("accounts")
|
||||
.delete()
|
||||
.match({ provider, providerAccountId })
|
||||
|
||||
if (error) throw error
|
||||
},
|
||||
async createSession({ sessionToken, userId, expires }) {
|
||||
const { data, error } = await supabase
|
||||
.from("sessions")
|
||||
.insert({ sessionToken, userId, expires: expires.toISOString() })
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return format<AdapterSession>(data)
|
||||
},
|
||||
async getSessionAndUser(sessionToken) {
|
||||
const { data, error } = await supabase
|
||||
.from("sessions")
|
||||
.select("*, users(*)")
|
||||
.eq("sessionToken", sessionToken)
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) return null
|
||||
|
||||
const { users: user, ...session } = data
|
||||
|
||||
return {
|
||||
user: format<AdapterUser>(
|
||||
user as Database["next_auth"]["Tables"]["users"]["Row"]
|
||||
),
|
||||
session: format<AdapterSession>(session),
|
||||
}
|
||||
},
|
||||
async updateSession(session) {
|
||||
const { data, error } = await supabase
|
||||
.from("sessions")
|
||||
.update({
|
||||
...session,
|
||||
expires: session.expires?.toISOString(),
|
||||
})
|
||||
.eq("sessionToken", session.sessionToken)
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return format<AdapterSession>(data)
|
||||
},
|
||||
async deleteSession(sessionToken) {
|
||||
const { error } = await supabase
|
||||
.from("sessions")
|
||||
.delete()
|
||||
.eq("sessionToken", sessionToken)
|
||||
|
||||
if (error) throw error
|
||||
},
|
||||
async createVerificationToken(token) {
|
||||
const { data, error } = await supabase
|
||||
.from("verification_tokens")
|
||||
.insert({
|
||||
...token,
|
||||
expires: token.expires.toISOString(),
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
const { id, ...verificationToken } = data
|
||||
|
||||
return format<VerificationToken>(verificationToken)
|
||||
},
|
||||
async useVerificationToken({ identifier, token }) {
|
||||
const { data, error } = await supabase
|
||||
.from("verification_tokens")
|
||||
.delete()
|
||||
.match({ identifier, token })
|
||||
.select()
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) return null
|
||||
|
||||
const { id, ...verificationToken } = data
|
||||
|
||||
return format<VerificationToken>(verificationToken)
|
||||
},
|
||||
}
|
||||
}
|
||||
44
packages/adapter-supabase/supabase/config.toml
Normal file
44
packages/adapter-supabase/supabase/config.toml
Normal file
@@ -0,0 +1,44 @@
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
|
||||
# directory name when running `supabase init`.
|
||||
project_id = "nextauth"
|
||||
|
||||
[api]
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. public and storage are always included.
|
||||
schemas = ["next_auth"]
|
||||
# Extra schemas to add to the search_path of every request.
|
||||
extra_search_path = ["extensions"]
|
||||
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
||||
# for accidental or malicious requests.
|
||||
max_rows = 1000
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 14
|
||||
|
||||
[studio]
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
|
||||
[auth]
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://localhost:3000"
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://localhost:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
|
||||
# week).
|
||||
jwt_expiry = 3600
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
@@ -0,0 +1,101 @@
|
||||
--
|
||||
-- Name: next_auth; Type: SCHEMA;
|
||||
--
|
||||
CREATE SCHEMA next_auth;
|
||||
|
||||
GRANT USAGE ON SCHEMA next_auth TO service_role;
|
||||
GRANT ALL ON SCHEMA next_auth TO postgres;
|
||||
|
||||
--
|
||||
-- Create users table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.users
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
name text,
|
||||
email text,
|
||||
"emailVerified" timestamp with time zone,
|
||||
image text,
|
||||
CONSTRAINT users_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT email_unique UNIQUE (email)
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.users TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.users TO service_role;
|
||||
|
||||
--- uid() function to be used in RLS policies
|
||||
CREATE FUNCTION next_auth.uid() RETURNS uuid
|
||||
LANGUAGE sql STABLE
|
||||
AS $$
|
||||
select
|
||||
coalesce(
|
||||
nullif(current_setting('request.jwt.claim.sub', true), ''),
|
||||
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
|
||||
)::uuid
|
||||
$$;
|
||||
|
||||
--
|
||||
-- Create sessions table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.sessions
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
expires timestamp with time zone NOT NULL,
|
||||
"sessionToken" text NOT NULL,
|
||||
"userId" uuid,
|
||||
CONSTRAINT sessions_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"),
|
||||
CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId")
|
||||
REFERENCES next_auth.users (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.sessions TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.sessions TO service_role;
|
||||
|
||||
--
|
||||
-- Create accounts table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.accounts
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
type text NOT NULL,
|
||||
provider text NOT NULL,
|
||||
"providerAccountId" text NOT NULL,
|
||||
refresh_token text,
|
||||
access_token text,
|
||||
expires_at bigint,
|
||||
token_type text,
|
||||
scope text,
|
||||
id_token text,
|
||||
session_state text,
|
||||
oauth_token_secret text,
|
||||
oauth_token text,
|
||||
"userId" uuid,
|
||||
CONSTRAINT accounts_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"),
|
||||
CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId")
|
||||
REFERENCES next_auth.users (id) MATCH SIMPLE
|
||||
ON UPDATE NO ACTION
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.accounts TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.accounts TO service_role;
|
||||
|
||||
--
|
||||
-- Create verification_tokens table
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS next_auth.verification_tokens
|
||||
(
|
||||
identifier text,
|
||||
token text,
|
||||
expires timestamp with time zone NOT NULL,
|
||||
CONSTRAINT verification_tokens_pkey PRIMARY KEY (token),
|
||||
CONSTRAINT token_unique UNIQUE (token),
|
||||
CONSTRAINT token_identifier_unique UNIQUE (token, identifier)
|
||||
);
|
||||
|
||||
GRANT ALL ON TABLE next_auth.verification_tokens TO postgres;
|
||||
GRANT ALL ON TABLE next_auth.verification_tokens TO service_role;
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* USERS
|
||||
* Note: This table contains user data. Users should only be able to view and update their own data.
|
||||
*/
|
||||
create table users (
|
||||
-- UUID from next_auth.users
|
||||
id uuid not null primary key,
|
||||
name text,
|
||||
email text,
|
||||
image text,
|
||||
constraint "users_id_fkey" foreign key ("id")
|
||||
references next_auth.users (id) match simple
|
||||
on update no action
|
||||
on delete cascade -- if user is deleted in NextAuth they will also be deleted in our public table.
|
||||
);
|
||||
alter table users enable row level security;
|
||||
create policy "Can view own user data." on users for select using (next_auth.uid() = id);
|
||||
create policy "Can update own user data." on users for update using (next_auth.uid() = id);
|
||||
|
||||
/**
|
||||
* This trigger automatically creates a user entry when a new user signs up via NextAuth.
|
||||
*/
|
||||
create function public.handle_new_user()
|
||||
returns trigger as $$
|
||||
begin
|
||||
insert into public.users (id, name, email, image)
|
||||
values (new.id, new.name, new.email, new.image);
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
create trigger on_auth_user_created
|
||||
after insert on next_auth.users
|
||||
for each row execute procedure public.handle_new_user();
|
||||
76
packages/adapter-supabase/tests/index.test.ts
Normal file
76
packages/adapter-supabase/tests/index.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { runBasicTests } from "@next-auth/adapter-test"
|
||||
import { format, SupabaseAdapter } from "../src"
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
import type {
|
||||
AdapterSession,
|
||||
AdapterUser,
|
||||
VerificationToken,
|
||||
} from "next-auth/adapters"
|
||||
import type { Account } from "next-auth"
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL ?? "http://localhost:54321",
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY ??
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU",
|
||||
{ db: { schema: "next_auth" } }
|
||||
)
|
||||
|
||||
runBasicTests({
|
||||
adapter: SupabaseAdapter({
|
||||
url: process.env.SUPABASE_URL ?? "http://localhost:54321",
|
||||
secret:
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY ??
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU",
|
||||
}),
|
||||
db: {
|
||||
async session(sessionToken) {
|
||||
const { data, error } = await supabase
|
||||
.from("sessions")
|
||||
.select()
|
||||
.eq("sessionToken", sessionToken)
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) return null
|
||||
|
||||
return format<AdapterSession>(data)
|
||||
},
|
||||
async user(id) {
|
||||
const { data, error } = await supabase
|
||||
.from("users")
|
||||
.select()
|
||||
.eq("id", id)
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) return null
|
||||
|
||||
return format<AdapterUser>(data)
|
||||
},
|
||||
async account({ provider, providerAccountId }) {
|
||||
const { data, error } = await supabase
|
||||
.from("accounts")
|
||||
.select()
|
||||
.match({ provider, providerAccountId })
|
||||
.maybeSingle()
|
||||
|
||||
if (error) throw error
|
||||
if (!data) return null
|
||||
|
||||
return format<Account>(data)
|
||||
},
|
||||
async verificationToken({ identifier, token }) {
|
||||
const { data, error } = await supabase
|
||||
.from("verification_tokens")
|
||||
.select()
|
||||
.match({ identifier, token })
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
const { id, ...verificationToken } = data
|
||||
|
||||
return format<VerificationToken>(verificationToken)
|
||||
},
|
||||
},
|
||||
})
|
||||
19
packages/adapter-supabase/tests/test.sh
Executable file
19
packages/adapter-supabase/tests/test.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# install Supabase CLI when run on CI
|
||||
if [ "$CI" = true ]; then
|
||||
wget -O supabase.deb https://github.com/supabase/cli/releases/download/v0.29.0/supabase_0.29.0_linux_amd64.deb
|
||||
sudo dpkg -i supabase.deb
|
||||
fi
|
||||
|
||||
# Start Supabase, grep key and set it as SUPABASE_SERVICE_ROLE_KEY environment variable
|
||||
line=$(supabase start | grep 'service_role key')
|
||||
IFS=':'; arr=($line); unset IFS;
|
||||
export SUPABASE_SERVICE_ROLE_KEY=${arr[1]}
|
||||
|
||||
# Always stop Supabase, but exit with 1 when tests are failing
|
||||
if npx jest; then
|
||||
supabase stop
|
||||
else
|
||||
supabase stop && exit 1
|
||||
fi
|
||||
8
packages/adapter-supabase/tsconfig.json
Normal file
8
packages/adapter-supabase/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"exclude": ["tests", "dist", "jest.config.js"]
|
||||
}
|
||||
3
packages/core/README.md
Normal file
3
packages/core/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Auth.js
|
||||
|
||||
Authentication for the web.
|
||||
75
packages/core/package.json
Normal file
75
packages/core/package.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "@auth/core",
|
||||
"version": "0.1.1",
|
||||
"description": "Authentication for the web.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
"author": "Balázs Orbán <info@balazsorban.com>",
|
||||
"contributors": [
|
||||
"Balázs Orbán <info@balazsorban.com>",
|
||||
"Nico Domino <yo@ndo.dev>",
|
||||
"Lluis Agusti <hi@llu.lu>",
|
||||
"Thang Huu Vu <thvu@hey.com>",
|
||||
"Iain Collins <me@iaincollins.com"
|
||||
],
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"files": [
|
||||
"adapters.*",
|
||||
"index.*",
|
||||
"jwt",
|
||||
"lib",
|
||||
"providers",
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
},
|
||||
"./adapters": {
|
||||
"types": "./adapters.d.ts"
|
||||
},
|
||||
"./jwt": {
|
||||
"types": "./jwt/index.d.ts",
|
||||
"import": "./jwt/index.js"
|
||||
},
|
||||
"./providers/*": {
|
||||
"types": "./providers/*.d.ts",
|
||||
"import": "./providers/*.js"
|
||||
}
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@panva/hkdf": "1.0.2",
|
||||
"cookie": "0.5.0",
|
||||
"jose": "4.11.1",
|
||||
"oauth4webapi": "2.0.4",
|
||||
"preact": "10.11.3",
|
||||
"preact-render-to-string": "5.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"nodemailer": "6.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"nodemailer": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "pnpm clean && tsc && pnpm css",
|
||||
"clean": "rm -rf adapters.* index.* jwt lib providers",
|
||||
"css": "node ./scripts/generate-css.js",
|
||||
"dev": "pnpm css && tsc -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"@types/node": "18.11.10",
|
||||
"@types/nodemailer": "6.4.6",
|
||||
"@types/react": "18.0.26",
|
||||
"autoprefixer": "10.4.13",
|
||||
"cssnano": "5.1.14",
|
||||
"postcss": "8.4.19",
|
||||
"postcss-nested": "6.0.0"
|
||||
}
|
||||
}
|
||||
22
packages/core/scripts/generate-css.js
Normal file
22
packages/core/scripts/generate-css.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import postcss from "postcss"
|
||||
|
||||
import autoprefixer from "autoprefixer"
|
||||
import postCssNested from "postcss-nested"
|
||||
import cssNano from "cssnano"
|
||||
|
||||
const from = path.join(process.cwd(), "src/lib/styles/index.css")
|
||||
const css = fs.readFileSync(from)
|
||||
|
||||
const processedCss = await postcss([
|
||||
autoprefixer,
|
||||
postCssNested,
|
||||
cssNano({ preset: "default" }),
|
||||
]).process(css, { from })
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), "src/lib/styles/index.ts"),
|
||||
`export default \`${processedCss.css}\`
|
||||
// Generated by \`pnpm css\``
|
||||
)
|
||||
130
packages/core/src/adapters.ts
Normal file
130
packages/core/src/adapters.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { Account, Awaitable, User } from "."
|
||||
|
||||
export interface AdapterUser extends User {
|
||||
id: string
|
||||
email: string
|
||||
emailVerified: Date | null
|
||||
}
|
||||
|
||||
export interface AdapterAccount extends Account {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface AdapterSession {
|
||||
/** A randomly generated value that is used to get hold of the session. */
|
||||
sessionToken: string
|
||||
/** Used to connect the session to a particular user */
|
||||
userId: string
|
||||
expires: Date
|
||||
}
|
||||
|
||||
export interface VerificationToken {
|
||||
identifier: string
|
||||
expires: Date
|
||||
token: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Using a custom adapter you can connect to any database backend or even several different databases.
|
||||
* Custom adapters created and maintained by our community can be found in the adapters repository.
|
||||
* Feel free to add a custom adapter from your project to the repository,
|
||||
* or even become a maintainer of a certain adapter.
|
||||
* Custom adapters can still be created and used in a project without being added to the repository.
|
||||
*
|
||||
* **Required methods**
|
||||
*
|
||||
* _(These methods are required for all sign in flows)_
|
||||
* - `createUser`
|
||||
* - `getUser`
|
||||
* - `getUserByEmail`
|
||||
* - `getUserByAccount`
|
||||
* - `linkAccount`
|
||||
* - `createSession`
|
||||
* - `getSessionAndUser`
|
||||
* - `updateSession`
|
||||
* - `deleteSession`
|
||||
* - `updateUser`
|
||||
*
|
||||
* _(Required to support email / passwordless sign in)_
|
||||
*
|
||||
* - `createVerificationToken`
|
||||
* - `useVerificationToken`
|
||||
*
|
||||
* **Unimplemented methods**
|
||||
*
|
||||
* _(These methods will be required in a future release, but are not yet invoked)_
|
||||
* - `deleteUser`
|
||||
* - `unlinkAccount`
|
||||
*
|
||||
* [Adapters Overview](https://next-auth.js.org/adapters/overview) |
|
||||
* [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
|
||||
*/
|
||||
export type Adapter<WithVerificationToken = boolean> = DefaultAdapter &
|
||||
(WithVerificationToken extends true
|
||||
? {
|
||||
createVerificationToken: (
|
||||
verificationToken: VerificationToken
|
||||
) => Awaitable<VerificationToken | null | undefined>
|
||||
/**
|
||||
* Return verification token from the database
|
||||
* and delete it so it cannot be used again.
|
||||
*/
|
||||
useVerificationToken: (params: {
|
||||
identifier: string
|
||||
token: string
|
||||
}) => Awaitable<VerificationToken | null>
|
||||
}
|
||||
: {})
|
||||
|
||||
export interface DefaultAdapter {
|
||||
createUser: (user: Omit<AdapterUser, "id">) => Awaitable<AdapterUser>
|
||||
getUser: (id: string) => Awaitable<AdapterUser | null>
|
||||
getUserByEmail: (email: string) => Awaitable<AdapterUser | null>
|
||||
/** Using the provider id and the id of the user for a specific account, get the user. */
|
||||
getUserByAccount: (
|
||||
providerAccountId: Pick<AdapterAccount, "provider" | "providerAccountId">
|
||||
) => Awaitable<AdapterUser | null>
|
||||
updateUser: (user: Partial<AdapterUser>) => Awaitable<AdapterUser>
|
||||
/** @todo Implement */
|
||||
deleteUser?: (
|
||||
userId: string
|
||||
) => Promise<void> | Awaitable<AdapterUser | null | undefined>
|
||||
linkAccount: (
|
||||
account: AdapterAccount
|
||||
) => Promise<void> | Awaitable<AdapterAccount | null | undefined>
|
||||
/** @todo Implement */
|
||||
unlinkAccount?: (
|
||||
providerAccountId: Pick<AdapterAccount, "provider" | "providerAccountId">
|
||||
) => Promise<void> | Awaitable<AdapterAccount | undefined>
|
||||
/** Creates a session for the user and returns it. */
|
||||
createSession: (session: {
|
||||
sessionToken: string
|
||||
userId: string
|
||||
expires: Date
|
||||
}) => Awaitable<AdapterSession>
|
||||
getSessionAndUser: (
|
||||
sessionToken: string
|
||||
) => Awaitable<{ session: AdapterSession; user: AdapterUser } | null>
|
||||
updateSession: (
|
||||
session: Partial<AdapterSession> & Pick<AdapterSession, "sessionToken">
|
||||
) => Awaitable<AdapterSession | null | undefined>
|
||||
/**
|
||||
* Deletes a session from the database.
|
||||
* It is preferred that this method also returns the session
|
||||
* that is being deleted for logging purposes.
|
||||
*/
|
||||
deleteSession: (
|
||||
sessionToken: string
|
||||
) => Promise<void> | Awaitable<AdapterSession | null | undefined>
|
||||
createVerificationToken?: (
|
||||
verificationToken: VerificationToken
|
||||
) => Awaitable<VerificationToken | null | undefined>
|
||||
/**
|
||||
* Return verification token from the database
|
||||
* and delete it so it cannot be used again.
|
||||
*/
|
||||
useVerificationToken?: (params: {
|
||||
identifier: string
|
||||
token: string
|
||||
}) => Awaitable<VerificationToken | null>
|
||||
}
|
||||
287
packages/core/src/index.ts
Normal file
287
packages/core/src/index.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
import { init } from "./lib/init"
|
||||
import { assertConfig } from "./lib/assert"
|
||||
import { SessionStore } from "./lib/cookie"
|
||||
import { toInternalRequest, toResponse } from "./lib/web"
|
||||
import renderPage from "./lib/pages"
|
||||
import * as routes from "./lib/routes"
|
||||
import logger, { setLogger } from "./lib/utils/logger"
|
||||
|
||||
import type { ErrorType } from "./lib/pages/error"
|
||||
import type {
|
||||
AuthOptions,
|
||||
RequestInternal,
|
||||
ResponseInternal,
|
||||
} from "./lib/types"
|
||||
import { UntrustedHost } from "./lib/errors"
|
||||
|
||||
export * from "./lib/types"
|
||||
|
||||
const configErrorMessage =
|
||||
"There is a problem with the server configuration. Check the server logs for more information."
|
||||
|
||||
async function AuthHandlerInternal<
|
||||
Body extends string | Record<string, any> | any[]
|
||||
>(params: {
|
||||
req: RequestInternal
|
||||
options: AuthOptions
|
||||
/** REVIEW: Is this the best way to skip parsing the body in Node.js? */
|
||||
parsedBody?: any
|
||||
}): Promise<ResponseInternal<Body>> {
|
||||
const { options: authOptions, req } = params
|
||||
|
||||
const assertionResult = assertConfig({ options: authOptions, req })
|
||||
|
||||
if (Array.isArray(assertionResult)) {
|
||||
assertionResult.forEach(logger.warn)
|
||||
} else if (assertionResult instanceof Error) {
|
||||
// Bail out early if there's an error in the user config
|
||||
logger.error(assertionResult.code, assertionResult)
|
||||
|
||||
const htmlPages = ["signin", "signout", "error", "verify-request"]
|
||||
if (!htmlPages.includes(req.action) || req.method !== "GET") {
|
||||
return {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: { message: configErrorMessage } as any,
|
||||
}
|
||||
}
|
||||
const { pages, theme } = authOptions
|
||||
|
||||
const authOnErrorPage =
|
||||
pages?.error && req.query?.callbackUrl?.startsWith(pages.error)
|
||||
|
||||
if (!pages?.error || authOnErrorPage) {
|
||||
if (authOnErrorPage) {
|
||||
logger.error(
|
||||
"AUTH_ON_ERROR_PAGE_ERROR",
|
||||
new Error(
|
||||
`The error page ${pages?.error} should not require authentication`
|
||||
)
|
||||
)
|
||||
}
|
||||
const render = renderPage({ theme })
|
||||
return render.error({ error: "configuration" })
|
||||
}
|
||||
|
||||
return {
|
||||
redirect: `${pages.error}?error=Configuration`,
|
||||
}
|
||||
}
|
||||
|
||||
const { action, providerId, error, method } = req
|
||||
|
||||
const { options, cookies } = await init({
|
||||
authOptions,
|
||||
action,
|
||||
providerId,
|
||||
url: req.url,
|
||||
callbackUrl: req.body?.callbackUrl ?? req.query?.callbackUrl,
|
||||
csrfToken: req.body?.csrfToken,
|
||||
cookies: req.cookies,
|
||||
isPost: method === "POST",
|
||||
})
|
||||
|
||||
const sessionStore = new SessionStore(
|
||||
options.cookies.sessionToken,
|
||||
req,
|
||||
options.logger
|
||||
)
|
||||
|
||||
if (method === "GET") {
|
||||
const render = renderPage({ ...options, query: req.query, cookies })
|
||||
const { pages } = options
|
||||
switch (action) {
|
||||
case "providers":
|
||||
return (await routes.providers(options.providers)) as any
|
||||
case "session": {
|
||||
const session = await routes.session({ options, sessionStore })
|
||||
if (session.cookies) cookies.push(...session.cookies)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
return { ...session, cookies } as any
|
||||
}
|
||||
case "csrf":
|
||||
return {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: { csrfToken: options.csrfToken } as any,
|
||||
cookies,
|
||||
}
|
||||
case "signin":
|
||||
if (pages.signIn) {
|
||||
let signinUrl = `${pages.signIn}${
|
||||
pages.signIn.includes("?") ? "&" : "?"
|
||||
}callbackUrl=${encodeURIComponent(options.callbackUrl)}`
|
||||
if (error)
|
||||
signinUrl = `${signinUrl}&error=${encodeURIComponent(error)}`
|
||||
return { redirect: signinUrl, cookies }
|
||||
}
|
||||
|
||||
return render.signin()
|
||||
case "signout":
|
||||
if (pages.signOut) return { redirect: pages.signOut, cookies }
|
||||
|
||||
return render.signout()
|
||||
case "callback":
|
||||
if (options.provider) {
|
||||
const callback = await routes.callback({
|
||||
body: req.body,
|
||||
query: req.query,
|
||||
headers: req.headers,
|
||||
cookies: req.cookies,
|
||||
method,
|
||||
options,
|
||||
sessionStore,
|
||||
})
|
||||
if (callback.cookies) cookies.push(...callback.cookies)
|
||||
return { ...callback, cookies }
|
||||
}
|
||||
break
|
||||
case "verify-request":
|
||||
if (pages.verifyRequest) {
|
||||
return { redirect: pages.verifyRequest, cookies }
|
||||
}
|
||||
return render.verifyRequest()
|
||||
case "error":
|
||||
// These error messages are displayed in line on the sign in page
|
||||
if (
|
||||
[
|
||||
"Signin",
|
||||
"OAuthSignin",
|
||||
"OAuthCallback",
|
||||
"OAuthCreateAccount",
|
||||
"EmailCreateAccount",
|
||||
"Callback",
|
||||
"OAuthAccountNotLinked",
|
||||
"EmailSignin",
|
||||
"CredentialsSignin",
|
||||
"SessionRequired",
|
||||
].includes(error as string)
|
||||
) {
|
||||
return { redirect: `${options.url}/signin?error=${error}`, cookies }
|
||||
}
|
||||
|
||||
if (pages.error) {
|
||||
return {
|
||||
redirect: `${pages.error}${
|
||||
pages.error.includes("?") ? "&" : "?"
|
||||
}error=${error}`,
|
||||
cookies,
|
||||
}
|
||||
}
|
||||
|
||||
return render.error({ error: error as ErrorType })
|
||||
default:
|
||||
}
|
||||
} else if (method === "POST") {
|
||||
switch (action) {
|
||||
case "signin":
|
||||
// Verified CSRF Token required for all sign in routes
|
||||
if (options.csrfTokenVerified && options.provider) {
|
||||
const signin = await routes.signin({
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
options,
|
||||
})
|
||||
if (signin.cookies) cookies.push(...signin.cookies)
|
||||
return { ...signin, cookies }
|
||||
}
|
||||
|
||||
return { redirect: `${options.url}/signin?csrf=true`, cookies }
|
||||
case "signout":
|
||||
// Verified CSRF Token required for signout
|
||||
if (options.csrfTokenVerified) {
|
||||
const signout = await routes.signout({ options, sessionStore })
|
||||
if (signout.cookies) cookies.push(...signout.cookies)
|
||||
return { ...signout, cookies }
|
||||
}
|
||||
return { redirect: `${options.url}/signout?csrf=true`, cookies }
|
||||
case "callback":
|
||||
if (options.provider) {
|
||||
// Verified CSRF Token required for credentials providers only
|
||||
if (
|
||||
options.provider.type === "credentials" &&
|
||||
!options.csrfTokenVerified
|
||||
) {
|
||||
return { redirect: `${options.url}/signin?csrf=true`, cookies }
|
||||
}
|
||||
|
||||
const callback = await routes.callback({
|
||||
body: req.body,
|
||||
query: req.query,
|
||||
headers: req.headers,
|
||||
cookies: req.cookies,
|
||||
method,
|
||||
options,
|
||||
sessionStore,
|
||||
})
|
||||
if (callback.cookies) cookies.push(...callback.cookies)
|
||||
return { ...callback, cookies }
|
||||
}
|
||||
break
|
||||
case "_log":
|
||||
if (authOptions.logger) {
|
||||
try {
|
||||
const { code, level, ...metadata } = req.body ?? {}
|
||||
logger[level](code, metadata)
|
||||
} catch (error) {
|
||||
// If logging itself failed...
|
||||
logger.error("LOGGER_ERROR", error as Error)
|
||||
}
|
||||
}
|
||||
return {}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 400,
|
||||
body: `Error: This action with HTTP ${method} is not supported by NextAuth.js` as any,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The core functionality of Auth.js.
|
||||
* It receives a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
|
||||
* and returns a standard [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
||||
*/
|
||||
export async function AuthHandler(
|
||||
request: Request,
|
||||
options: AuthOptions
|
||||
): Promise<Response> {
|
||||
setLogger(options.logger, options.debug)
|
||||
|
||||
if (!options.trustHost) {
|
||||
const error = new UntrustedHost(
|
||||
`Host must be trusted. URL was: ${request.url}`
|
||||
)
|
||||
logger.error(error.code, error)
|
||||
|
||||
return new Response(JSON.stringify({ message: configErrorMessage }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
}
|
||||
|
||||
const req = await toInternalRequest(request)
|
||||
if (req instanceof Error) {
|
||||
logger.error((req as any).code, req)
|
||||
return new Response(
|
||||
`Error: This action with HTTP ${request.method} is not supported.`,
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
const internalResponse = await AuthHandlerInternal({ req, options })
|
||||
|
||||
const response = await toResponse(internalResponse)
|
||||
|
||||
// If the request expects a return URL, send it as JSON
|
||||
// instead of doing an actual redirect.
|
||||
const redirect = response.headers.get("Location")
|
||||
if (request.headers.has("X-Auth-Return-Redirect") && redirect) {
|
||||
response.headers.delete("Location")
|
||||
response.headers.set("Content-Type", "application/json")
|
||||
return new Response(JSON.stringify({ url: redirect }), {
|
||||
headers: response.headers,
|
||||
})
|
||||
}
|
||||
return response
|
||||
}
|
||||
127
packages/core/src/jwt/index.ts
Normal file
127
packages/core/src/jwt/index.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { EncryptJWT, jwtDecrypt } from "jose"
|
||||
import hkdf from "@panva/hkdf"
|
||||
import { SessionStore } from "../lib/cookie"
|
||||
import type { JWT, JWTDecodeParams, JWTEncodeParams, JWTOptions } from "./types"
|
||||
import type { LoggerInstance } from ".."
|
||||
|
||||
export * from "./types"
|
||||
|
||||
const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days
|
||||
|
||||
const now = () => (Date.now() / 1000) | 0
|
||||
|
||||
/** Issues a JWT. By default, the JWT is encrypted using "A256GCM". */
|
||||
export async function encode(params: JWTEncodeParams) {
|
||||
const { token = {}, secret, maxAge = DEFAULT_MAX_AGE } = params
|
||||
const encryptionSecret = await getDerivedEncryptionKey(secret)
|
||||
return await new EncryptJWT(token)
|
||||
.setProtectedHeader({ alg: "dir", enc: "A256GCM" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(now() + maxAge)
|
||||
.setJti(crypto.randomUUID())
|
||||
.encrypt(encryptionSecret)
|
||||
}
|
||||
|
||||
/** Decodes a NextAuth.js issued JWT. */
|
||||
export async function decode(params: JWTDecodeParams): Promise<JWT | null> {
|
||||
const { token, secret } = params
|
||||
if (!token) return null
|
||||
const encryptionSecret = await getDerivedEncryptionKey(secret)
|
||||
const { payload } = await jwtDecrypt(token, encryptionSecret, {
|
||||
clockTolerance: 15,
|
||||
})
|
||||
return payload
|
||||
}
|
||||
|
||||
export interface GetTokenParams<R extends boolean = false> {
|
||||
/** The request containing the JWT either in the cookies or in the `Authorization` header. */
|
||||
req:
|
||||
| Request
|
||||
| { cookies: Record<string, string>; headers: Record<string, string> }
|
||||
/**
|
||||
* Use secure prefix for cookie name, unless URL in `NEXTAUTH_URL` is http://
|
||||
* or not set (e.g. development or test instance) case use unprefixed name
|
||||
*/
|
||||
secureCookie?: boolean
|
||||
/** If the JWT is in the cookie, what name `getToken()` should look for. */
|
||||
cookieName?: string
|
||||
/**
|
||||
* `getToken()` will return the raw JWT if this is set to `true`
|
||||
* @default false
|
||||
*/
|
||||
raw?: R
|
||||
/**
|
||||
* The same `secret` used in the `NextAuth` configuration.
|
||||
* Defaults to the `NEXTAUTH_SECRET` environment variable.
|
||||
*/
|
||||
secret?: string
|
||||
decode?: JWTOptions["decode"]
|
||||
logger?: LoggerInstance | Console
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a NextAuth.js request (`req`) and returns either the NextAuth.js issued JWT's payload,
|
||||
* or the raw JWT string. We look for the JWT in the either the cookies, or the `Authorization` header.
|
||||
* [Documentation](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken)
|
||||
*/
|
||||
export async function getToken<R extends boolean = false>(
|
||||
params: GetTokenParams<R>
|
||||
): Promise<R extends true ? string : JWT | null> {
|
||||
const {
|
||||
req,
|
||||
secureCookie = process.env.NEXTAUTH_URL?.startsWith("https://") ??
|
||||
!!process.env.VERCEL,
|
||||
cookieName = secureCookie
|
||||
? "__Secure-next-auth.session-token"
|
||||
: "next-auth.session-token",
|
||||
raw,
|
||||
decode: _decode = decode,
|
||||
logger = console,
|
||||
secret = process.env.NEXTAUTH_SECRET,
|
||||
} = params
|
||||
|
||||
if (!req) throw new Error("Must pass `req` to JWT getToken()")
|
||||
|
||||
const sessionStore = new SessionStore(
|
||||
{ name: cookieName, options: { secure: secureCookie } },
|
||||
// @ts-expect-error
|
||||
{ cookies: req.cookies, headers: req.headers },
|
||||
logger
|
||||
)
|
||||
|
||||
let token = sessionStore.value
|
||||
|
||||
const authorizationHeader =
|
||||
req.headers instanceof Headers
|
||||
? req.headers.get("authorization")
|
||||
: req.headers.authorization
|
||||
|
||||
if (!token && authorizationHeader?.split(" ")[0] === "Bearer") {
|
||||
const urlEncodedToken = authorizationHeader.split(" ")[1]
|
||||
token = decodeURIComponent(urlEncodedToken)
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
if (!token) return null
|
||||
|
||||
// @ts-expect-error
|
||||
if (raw) return token
|
||||
|
||||
try {
|
||||
// @ts-expect-error
|
||||
return await _decode({ token, secret })
|
||||
} catch {
|
||||
// @ts-expect-error
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function getDerivedEncryptionKey(secret: string | Buffer) {
|
||||
return await hkdf(
|
||||
"sha256",
|
||||
secret,
|
||||
"",
|
||||
"NextAuth.js Generated Encryption Key",
|
||||
32
|
||||
)
|
||||
}
|
||||
54
packages/core/src/jwt/types.ts
Normal file
54
packages/core/src/jwt/types.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { Awaitable } from ".."
|
||||
|
||||
export interface DefaultJWT extends Record<string, unknown> {
|
||||
name?: string | null
|
||||
email?: string | null
|
||||
picture?: string | null
|
||||
sub?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by the `jwt` callback and `getToken`, when using JWT sessions
|
||||
*
|
||||
* [`jwt` callback](https://next-auth.js.org/configuration/callbacks#jwt-callback) | [`getToken`](https://next-auth.js.org/tutorials/securing-pages-and-api-routes#using-gettoken)
|
||||
*/
|
||||
export interface JWT extends Record<string, unknown>, DefaultJWT {}
|
||||
|
||||
export interface JWTEncodeParams {
|
||||
/** The JWT payload. */
|
||||
token?: JWT
|
||||
/** The secret used to encode the NextAuth.js issued JWT. */
|
||||
secret: string | Buffer
|
||||
/**
|
||||
* The maximum age of the NextAuth.js issued JWT in seconds.
|
||||
* @default 30 * 24 * 30 * 60 // 30 days
|
||||
*/
|
||||
maxAge?: number
|
||||
}
|
||||
|
||||
export interface JWTDecodeParams {
|
||||
/** The NextAuth.js issued JWT to be decoded */
|
||||
token?: string
|
||||
/** The secret used to decode the NextAuth.js issued JWT. */
|
||||
secret: string | Buffer
|
||||
}
|
||||
|
||||
export interface JWTOptions {
|
||||
/**
|
||||
* The secret used to encode/decode the NextAuth.js issued JWT.
|
||||
* @deprecated Set the `NEXTAUTH_SECRET` environment vairable or
|
||||
* use the top-level `secret` option instead
|
||||
*/
|
||||
secret: string
|
||||
/**
|
||||
* The maximum age of the NextAuth.js issued JWT in seconds.
|
||||
* @default 30 * 24 * 30 * 60 // 30 days
|
||||
*/
|
||||
maxAge: number
|
||||
/** Override this method to control the NextAuth.js issued JWT encoding. */
|
||||
encode: (params: JWTEncodeParams) => Awaitable<string>
|
||||
/** Override this method to control the NextAuth.js issued JWT decoding. */
|
||||
decode: (params: JWTDecodeParams) => Awaitable<JWT | null>
|
||||
}
|
||||
|
||||
export type Secret = string | Buffer
|
||||
157
packages/core/src/lib/assert.ts
Normal file
157
packages/core/src/lib/assert.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
InvalidCallbackUrl,
|
||||
InvalidEndpoints,
|
||||
MissingAdapter,
|
||||
MissingAdapterMethods,
|
||||
MissingAPIRoute,
|
||||
MissingAuthorize,
|
||||
MissingSecret,
|
||||
UnsupportedStrategy,
|
||||
} from "./errors"
|
||||
import { defaultCookies } from "./cookie"
|
||||
|
||||
import type { AuthOptions, RequestInternal } from ".."
|
||||
import type { WarningCode } from "./utils/logger"
|
||||
|
||||
type ConfigError =
|
||||
| MissingAdapter
|
||||
| MissingAdapterMethods
|
||||
| MissingAPIRoute
|
||||
| MissingAuthorize
|
||||
| MissingSecret
|
||||
| InvalidCallbackUrl
|
||||
| UnsupportedStrategy
|
||||
| InvalidEndpoints
|
||||
| UnsupportedStrategy
|
||||
|
||||
let warned = false
|
||||
|
||||
function isValidHttpUrl(url: string, baseUrl: string) {
|
||||
try {
|
||||
return /^https?:/.test(
|
||||
new URL(url, url.startsWith("/") ? baseUrl : undefined).protocol
|
||||
)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the user configured Auth.js correctly.
|
||||
* Good place to mention deprecations as well.
|
||||
*
|
||||
* REVIEW: Make some of these and corresponding docs less Next.js specific?
|
||||
*/
|
||||
export function assertConfig(params: {
|
||||
options: AuthOptions
|
||||
req: RequestInternal
|
||||
}): ConfigError | WarningCode[] {
|
||||
const { options, req } = params
|
||||
const { url } = req
|
||||
const warnings: WarningCode[] = []
|
||||
|
||||
if (!warned) {
|
||||
if (!url.origin) warnings.push("NEXTAUTH_URL")
|
||||
if (options.debug) warnings.push("DEBUG_ENABLED")
|
||||
}
|
||||
|
||||
if (!options.secret) {
|
||||
return new MissingSecret("Please define a `secret` in production.")
|
||||
}
|
||||
|
||||
// req.query isn't defined when asserting `unstable_getServerSession` for example
|
||||
if (!req.query?.nextauth && !req.action) {
|
||||
return new MissingAPIRoute(
|
||||
"Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly."
|
||||
)
|
||||
}
|
||||
|
||||
const callbackUrlParam = req.query?.callbackUrl as string | undefined
|
||||
|
||||
if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, url.origin)) {
|
||||
return new InvalidCallbackUrl(
|
||||
`Invalid callback URL. Received: ${callbackUrlParam}`
|
||||
)
|
||||
}
|
||||
|
||||
const { callbackUrl: defaultCallbackUrl } = defaultCookies(
|
||||
options.useSecureCookies ?? url.protocol === "https://"
|
||||
)
|
||||
const callbackUrlCookie =
|
||||
req.cookies?.[options.cookies?.callbackUrl?.name ?? defaultCallbackUrl.name]
|
||||
|
||||
if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, url.origin)) {
|
||||
return new InvalidCallbackUrl(
|
||||
`Invalid callback URL. Received: ${callbackUrlCookie}`
|
||||
)
|
||||
}
|
||||
|
||||
let hasCredentials, hasEmail
|
||||
|
||||
for (const provider of options.providers) {
|
||||
if (
|
||||
(provider.type === "oauth" || provider.type === "oidc") &&
|
||||
!(provider.issuer ?? provider.options?.issuer)
|
||||
) {
|
||||
const { authorization: a, token: t, userinfo: u } = provider
|
||||
|
||||
let key
|
||||
if (typeof a !== "string" && !a?.url) key = "authorization"
|
||||
else if (typeof t !== "string" && !t?.url) key = "token"
|
||||
else if (typeof u !== "string" && !u?.url) key = "userinfo"
|
||||
|
||||
if (key) {
|
||||
return new InvalidEndpoints(
|
||||
`Provider "${provider.id}" is missing both \`issuer\` and \`${key}\` endpoint config. At least one of them is required.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (provider.type === "credentials") hasCredentials = true
|
||||
else if (provider.type === "email") hasEmail = true
|
||||
}
|
||||
|
||||
if (hasCredentials) {
|
||||
const dbStrategy = options.session?.strategy === "database"
|
||||
const onlyCredentials = !options.providers.some(
|
||||
(p) => p.type !== "credentials"
|
||||
)
|
||||
if (dbStrategy && onlyCredentials) {
|
||||
return new UnsupportedStrategy(
|
||||
"Signin in with credentials only supported if JWT strategy is enabled"
|
||||
)
|
||||
}
|
||||
|
||||
const credentialsNoAuthorize = options.providers.some(
|
||||
(p) => p.type === "credentials" && !p.authorize
|
||||
)
|
||||
if (credentialsNoAuthorize) {
|
||||
return new MissingAuthorize(
|
||||
"Must define an authorize() handler to use credentials authentication provider"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEmail) {
|
||||
const { adapter } = options
|
||||
if (!adapter) {
|
||||
return new MissingAdapter("E-mail login requires an adapter.")
|
||||
}
|
||||
|
||||
const missingMethods = [
|
||||
"createVerificationToken",
|
||||
"useVerificationToken",
|
||||
"getUserByEmail",
|
||||
].filter((method) => !adapter[method])
|
||||
|
||||
if (missingMethods.length) {
|
||||
return new MissingAdapterMethods(
|
||||
`Required adapter methods were missing: ${missingMethods.join(", ")}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!warned) warned = true
|
||||
|
||||
return warnings
|
||||
}
|
||||
228
packages/core/src/lib/callback-handler.ts
Normal file
228
packages/core/src/lib/callback-handler.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import { AccountNotLinkedError } from "./errors"
|
||||
import { fromDate } from "./utils/date"
|
||||
|
||||
import type { Account, InternalOptions, User } from ".."
|
||||
import type { AdapterSession, AdapterUser } from "../adapters"
|
||||
import type { JWT } from "../jwt"
|
||||
import type { OAuthConfig } from "../providers"
|
||||
import type { SessionToken } from "./cookie"
|
||||
|
||||
/**
|
||||
* This function handles the complex flow of signing users in, and either creating,
|
||||
* linking (or not linking) accounts depending on if the user is currently logged
|
||||
* in, if they have account already and the authentication mechanism they are using.
|
||||
*
|
||||
* It prevents insecure behaviour, such as linking OAuth accounts unless a user is
|
||||
* signed in and authenticated with an existing valid account.
|
||||
*
|
||||
* All verification (e.g. OAuth flows or email address verificaiton flows) are
|
||||
* done prior to this handler being called to avoid additonal complexity in this
|
||||
* handler.
|
||||
*/
|
||||
export default async function callbackHandler(params: {
|
||||
sessionToken?: SessionToken
|
||||
profile: User | AdapterUser | { email: string }
|
||||
account: Account | null
|
||||
options: InternalOptions
|
||||
}) {
|
||||
const { sessionToken, profile: _profile, account, options } = params
|
||||
// Input validation
|
||||
if (!account?.providerAccountId || !account.type)
|
||||
throw new Error("Missing or invalid provider account")
|
||||
if (!["email", "oauth", "oidc"].includes(account.type))
|
||||
throw new Error("Provider not supported")
|
||||
|
||||
const {
|
||||
adapter,
|
||||
jwt,
|
||||
events,
|
||||
session: { strategy: sessionStrategy, generateSessionToken },
|
||||
} = options
|
||||
|
||||
// If no adapter is configured then we don't have a database and cannot
|
||||
// persist data; in this mode we just return a dummy session object.
|
||||
if (!adapter) {
|
||||
return { user: _profile as User, account }
|
||||
}
|
||||
|
||||
const profile = _profile as AdapterUser
|
||||
|
||||
const {
|
||||
createUser,
|
||||
updateUser,
|
||||
getUser,
|
||||
getUserByAccount,
|
||||
getUserByEmail,
|
||||
linkAccount,
|
||||
createSession,
|
||||
getSessionAndUser,
|
||||
deleteSession,
|
||||
} = adapter
|
||||
|
||||
let session: AdapterSession | JWT | null = null
|
||||
let user: AdapterUser | null = null
|
||||
let isNewUser = false
|
||||
|
||||
const useJwtSession = sessionStrategy === "jwt"
|
||||
|
||||
if (sessionToken) {
|
||||
if (useJwtSession) {
|
||||
try {
|
||||
session = await jwt.decode({ ...jwt, token: sessionToken })
|
||||
if (session && "sub" in session && session.sub) {
|
||||
user = await getUser(session.sub)
|
||||
}
|
||||
} catch {
|
||||
// If session can't be verified, treat as no session
|
||||
}
|
||||
} else {
|
||||
const userAndSession = await getSessionAndUser(sessionToken)
|
||||
if (userAndSession) {
|
||||
session = userAndSession.session
|
||||
user = userAndSession.user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (account.type === "email") {
|
||||
// If signing in with an email, check if an account with the same email address exists already
|
||||
const userByEmail = await getUserByEmail(profile.email)
|
||||
if (userByEmail) {
|
||||
// If they are not already signed in as the same user, this flow will
|
||||
// sign them out of the current session and sign them in as the new user
|
||||
if (user?.id !== userByEmail.id && !useJwtSession && sessionToken) {
|
||||
// Delete existing session if they are currently signed in as another user.
|
||||
// This will switch user accounts for the session in cases where the user was
|
||||
// already logged in with a different account.
|
||||
await deleteSession(sessionToken)
|
||||
}
|
||||
|
||||
// Update emailVerified property on the user object
|
||||
user = await updateUser({ id: userByEmail.id, emailVerified: new Date() })
|
||||
await events.updateUser?.({ user })
|
||||
} else {
|
||||
const { id: _, ...newUser } = { ...profile, emailVerified: new Date() }
|
||||
// Create user account if there isn't one for the email address already
|
||||
user = await createUser(newUser)
|
||||
await events.createUser?.({ user })
|
||||
isNewUser = true
|
||||
}
|
||||
|
||||
// Create new session
|
||||
session = useJwtSession
|
||||
? {}
|
||||
: await createSession({
|
||||
sessionToken: generateSessionToken(),
|
||||
userId: user.id,
|
||||
expires: fromDate(options.session.maxAge),
|
||||
})
|
||||
|
||||
return { session, user, isNewUser }
|
||||
} else if (account.type === "oauth") {
|
||||
// If signing in with OAuth account, check to see if the account exists already
|
||||
const userByAccount = await getUserByAccount({
|
||||
providerAccountId: account.providerAccountId,
|
||||
provider: account.provider,
|
||||
})
|
||||
if (userByAccount) {
|
||||
if (user) {
|
||||
// If the user is already signed in with this account, we don't need to do anything
|
||||
if (userByAccount.id === user.id) {
|
||||
return { session, user, isNewUser }
|
||||
}
|
||||
// If the user is currently signed in, but the new account they are signing in
|
||||
// with is already associated with another user, then we cannot link them
|
||||
// and need to return an error.
|
||||
throw new AccountNotLinkedError(
|
||||
"The account is already associated with another user"
|
||||
)
|
||||
}
|
||||
// If there is no active session, but the account being signed in with is already
|
||||
// associated with a valid user then create session to sign the user in.
|
||||
session = useJwtSession
|
||||
? {}
|
||||
: await createSession({
|
||||
sessionToken: generateSessionToken(),
|
||||
userId: userByAccount.id,
|
||||
expires: fromDate(options.session.maxAge),
|
||||
})
|
||||
|
||||
return { session, user: userByAccount, isNewUser }
|
||||
} else {
|
||||
if (user) {
|
||||
// If the user is already signed in and the OAuth account isn't already associated
|
||||
// with another user account then we can go ahead and link the accounts safely.
|
||||
await linkAccount({ ...account, userId: user.id })
|
||||
await events.linkAccount?.({ user, account, profile })
|
||||
|
||||
// As they are already signed in, we don't need to do anything after linking them
|
||||
return { session, user, isNewUser }
|
||||
}
|
||||
|
||||
// If the user is not signed in and it looks like a new OAuth account then we
|
||||
// check there also isn't an user account already associated with the same
|
||||
// email address as the one in the OAuth profile.
|
||||
//
|
||||
// This step is often overlooked in OAuth implementations, but covers the following cases:
|
||||
//
|
||||
// 1. It makes it harder for someone to accidentally create two accounts.
|
||||
// e.g. by signin in with email, then again with an oauth account connected to the same email.
|
||||
// 2. It makes it harder to hijack a user account using a 3rd party OAuth account.
|
||||
// e.g. by creating an oauth account then changing the email address associated with it.
|
||||
//
|
||||
// It's quite common for services to automatically link accounts in this case, but it's
|
||||
// better practice to require the user to sign in *then* link accounts to be sure
|
||||
// someone is not exploiting a problem with a third party OAuth service.
|
||||
//
|
||||
// OAuth providers should require email address verification to prevent this, but in
|
||||
// practice that is not always the case; this helps protect against that.
|
||||
const userByEmail = profile.email
|
||||
? await getUserByEmail(profile.email)
|
||||
: null
|
||||
if (userByEmail) {
|
||||
const provider = options.provider as OAuthConfig<any>
|
||||
if (provider?.allowDangerousEmailAccountLinking) {
|
||||
// If you trust the oauth provider to correctly verify email addresses, you can opt-in to
|
||||
// account linking even when the user is not signed-in.
|
||||
user = userByEmail
|
||||
} else {
|
||||
// We end up here when we don't have an account with the same [provider].id *BUT*
|
||||
// we do already have an account with the same email address as the one in the
|
||||
// OAuth profile the user has just tried to sign in with.
|
||||
//
|
||||
// We don't want to have two accounts with the same email address, and we don't
|
||||
// want to link them in case it's not safe to do so, so instead we prompt the user
|
||||
// to sign in via email to verify their identity and then link the accounts.
|
||||
throw new AccountNotLinkedError(
|
||||
"Another account already exists with the same e-mail address"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// If the current user is not logged in and the profile isn't linked to any user
|
||||
// accounts (by email or provider account id)...
|
||||
//
|
||||
// If no account matching the same [provider].id or .email exists, we can
|
||||
// create a new account for the user, link it to the OAuth acccount and
|
||||
// create a new session for them so they are signed in with it.
|
||||
const { id: _, ...newUser } = { ...profile, emailVerified: null }
|
||||
user = await createUser(newUser)
|
||||
}
|
||||
await events.createUser?.({ user })
|
||||
|
||||
await linkAccount({ ...account, userId: user.id })
|
||||
await events.linkAccount?.({ user, account, profile })
|
||||
|
||||
session = useJwtSession
|
||||
? {}
|
||||
: await createSession({
|
||||
sessionToken: generateSessionToken(),
|
||||
userId: user.id,
|
||||
expires: fromDate(options.session.maxAge),
|
||||
})
|
||||
|
||||
return { session, user, isNewUser: true }
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unsupported account type")
|
||||
}
|
||||
42
packages/core/src/lib/callback-url.ts
Normal file
42
packages/core/src/lib/callback-url.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { InternalOptions } from ".."
|
||||
|
||||
interface CreateCallbackUrlParams {
|
||||
options: InternalOptions
|
||||
/** Try reading value from request body (POST) then from query param (GET) */
|
||||
paramValue?: string
|
||||
cookieValue?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get callback URL based on query param / cookie + validation,
|
||||
* and add it to `req.options.callbackUrl`.
|
||||
*/
|
||||
export async function createCallbackUrl({
|
||||
options,
|
||||
paramValue,
|
||||
cookieValue,
|
||||
}: CreateCallbackUrlParams) {
|
||||
const { url, callbacks } = options
|
||||
|
||||
let callbackUrl = url.origin
|
||||
|
||||
if (paramValue) {
|
||||
// If callbackUrl form field or query parameter is passed try to use it if allowed
|
||||
callbackUrl = await callbacks.redirect({
|
||||
url: paramValue,
|
||||
baseUrl: url.origin,
|
||||
})
|
||||
} else if (cookieValue) {
|
||||
// If no callbackUrl specified, try using the value from the cookie if allowed
|
||||
callbackUrl = await callbacks.redirect({
|
||||
url: cookieValue,
|
||||
baseUrl: url.origin,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
callbackUrl,
|
||||
// Save callback URL in a cookie so that it can be used for subsequent requests in signin/signout/callback flow
|
||||
callbackUrlCookie: callbackUrl !== cookieValue ? callbackUrl : undefined,
|
||||
}
|
||||
}
|
||||
236
packages/core/src/lib/cookie.ts
Normal file
236
packages/core/src/lib/cookie.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
import type {
|
||||
CookieOption,
|
||||
CookiesOptions,
|
||||
LoggerInstance,
|
||||
SessionStrategy,
|
||||
} from ".."
|
||||
|
||||
// Uncomment to recalculate the estimated size
|
||||
// of an empty session cookie
|
||||
// import { serialize } from "cookie"
|
||||
// console.log(
|
||||
// "Cookie estimated to be ",
|
||||
// serialize(`__Secure.next-auth.session-token.0`, "", {
|
||||
// expires: new Date(),
|
||||
// httpOnly: true,
|
||||
// maxAge: Number.MAX_SAFE_INTEGER,
|
||||
// path: "/",
|
||||
// sameSite: "strict",
|
||||
// secure: true,
|
||||
// domain: "example.com",
|
||||
// }).length,
|
||||
// " bytes"
|
||||
// )
|
||||
|
||||
const ALLOWED_COOKIE_SIZE = 4096
|
||||
// Based on commented out section above
|
||||
const ESTIMATED_EMPTY_COOKIE_SIZE = 163
|
||||
const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE
|
||||
|
||||
// REVIEW: Is there any way to defer two types of strings?
|
||||
|
||||
/** Stringified form of `JWT`. Extract the content with `jwt.decode` */
|
||||
export type JWTString = string
|
||||
|
||||
export type SetCookieOptions = Partial<CookieOption["options"]> & {
|
||||
expires?: Date | string
|
||||
encode?: (val: unknown) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* If `options.session.strategy` is set to `jwt`, this is a stringified `JWT`.
|
||||
* In case of `strategy: "database"`, this is the `sessionToken` of the session in the database.
|
||||
*/
|
||||
export type SessionToken<T extends SessionStrategy = "jwt"> = T extends "jwt"
|
||||
? JWTString
|
||||
: string
|
||||
|
||||
/**
|
||||
* Use secure cookies if the site uses HTTPS
|
||||
* This being conditional allows cookies to work non-HTTPS development URLs
|
||||
* Honour secure cookie option, which sets 'secure' and also adds '__Secure-'
|
||||
* prefix, but enable them by default if the site URL is HTTPS; but not for
|
||||
* non-HTTPS URLs like http://localhost which are used in development).
|
||||
* For more on prefixes see https://googlechrome.github.io/samples/cookie-prefixes/
|
||||
*
|
||||
* @TODO Review cookie settings (names, options)
|
||||
*/
|
||||
export function defaultCookies(useSecureCookies: boolean): CookiesOptions {
|
||||
const cookiePrefix = useSecureCookies ? "__Secure-" : ""
|
||||
return {
|
||||
// default cookie options
|
||||
sessionToken: {
|
||||
name: `${cookiePrefix}next-auth.session-token`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: useSecureCookies,
|
||||
},
|
||||
},
|
||||
callbackUrl: {
|
||||
name: `${cookiePrefix}next-auth.callback-url`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: useSecureCookies,
|
||||
},
|
||||
},
|
||||
csrfToken: {
|
||||
// Default to __Host- for CSRF token for additional protection if using useSecureCookies
|
||||
// NB: The `__Host-` prefix is stricter than the `__Secure-` prefix.
|
||||
name: `${useSecureCookies ? "__Host-" : ""}next-auth.csrf-token`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: useSecureCookies,
|
||||
},
|
||||
},
|
||||
pkceCodeVerifier: {
|
||||
name: `${cookiePrefix}next-auth.pkce.code_verifier`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: useSecureCookies,
|
||||
maxAge: 60 * 15, // 15 minutes in seconds
|
||||
},
|
||||
},
|
||||
state: {
|
||||
name: `${cookiePrefix}next-auth.state`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: useSecureCookies,
|
||||
maxAge: 60 * 15, // 15 minutes in seconds
|
||||
},
|
||||
},
|
||||
nonce: {
|
||||
name: `${cookiePrefix}next-auth.nonce`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: useSecureCookies,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export interface Cookie extends CookieOption {
|
||||
value: string
|
||||
}
|
||||
|
||||
type Chunks = Record<string, string>
|
||||
|
||||
export class SessionStore {
|
||||
#chunks: Chunks = {}
|
||||
#option: CookieOption
|
||||
#logger: LoggerInstance | Console
|
||||
|
||||
constructor(
|
||||
option: CookieOption,
|
||||
req: Partial<{ cookies: any; headers: any }>,
|
||||
logger: LoggerInstance | Console
|
||||
) {
|
||||
this.#logger = logger
|
||||
this.#option = option
|
||||
|
||||
const { cookies } = req
|
||||
const { name: cookieName } = option
|
||||
|
||||
if (typeof cookies?.getAll === "function") {
|
||||
// Next.js ^v13.0.1 (Edge Env)
|
||||
for (const { name, value } of cookies.getAll()) {
|
||||
if (name.startsWith(cookieName)) {
|
||||
this.#chunks[name] = value
|
||||
}
|
||||
}
|
||||
} else if (cookies instanceof Map) {
|
||||
for (const name of cookies.keys()) {
|
||||
if (name.startsWith(cookieName)) this.#chunks[name] = cookies.get(name)
|
||||
}
|
||||
} else {
|
||||
for (const name in cookies) {
|
||||
if (name.startsWith(cookieName)) this.#chunks[name] = cookies[name]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get value() {
|
||||
return Object.values(this.#chunks)?.join("")
|
||||
}
|
||||
|
||||
/** Given a cookie, return a list of cookies, chunked to fit the allowed cookie size. */
|
||||
#chunk(cookie: Cookie): Cookie[] {
|
||||
const chunkCount = Math.ceil(cookie.value.length / CHUNK_SIZE)
|
||||
|
||||
if (chunkCount === 1) {
|
||||
this.#chunks[cookie.name] = cookie.value
|
||||
return [cookie]
|
||||
}
|
||||
|
||||
const cookies: Cookie[] = []
|
||||
for (let i = 0; i < chunkCount; i++) {
|
||||
const name = `${cookie.name}.${i}`
|
||||
const value = cookie.value.substr(i * CHUNK_SIZE, CHUNK_SIZE)
|
||||
cookies.push({ ...cookie, name, value })
|
||||
this.#chunks[name] = value
|
||||
}
|
||||
|
||||
this.#logger.debug("CHUNKING_SESSION_COOKIE", {
|
||||
message: `Session cookie exceeds allowed ${ALLOWED_COOKIE_SIZE} bytes.`,
|
||||
emptyCookieSize: ESTIMATED_EMPTY_COOKIE_SIZE,
|
||||
valueSize: cookie.value.length,
|
||||
chunks: cookies.map((c) => c.value.length + ESTIMATED_EMPTY_COOKIE_SIZE),
|
||||
})
|
||||
|
||||
return cookies
|
||||
}
|
||||
|
||||
/** Returns cleaned cookie chunks. */
|
||||
#clean(): Record<string, Cookie> {
|
||||
const cleanedChunks: Record<string, Cookie> = {}
|
||||
for (const name in this.#chunks) {
|
||||
delete this.#chunks?.[name]
|
||||
cleanedChunks[name] = {
|
||||
name,
|
||||
value: "",
|
||||
options: { ...this.#option.options, maxAge: 0 },
|
||||
}
|
||||
}
|
||||
return cleanedChunks
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a cookie value, return new cookies, chunked, to fit the allowed cookie size.
|
||||
* If the cookie has changed from chunked to unchunked or vice versa,
|
||||
* it deletes the old cookies as well.
|
||||
*/
|
||||
chunk(value: string, options: Partial<Cookie["options"]>): Cookie[] {
|
||||
// Assume all cookies should be cleaned by default
|
||||
const cookies: Record<string, Cookie> = this.#clean()
|
||||
|
||||
// Calculate new chunks
|
||||
const chunked = this.#chunk({
|
||||
name: this.#option.name,
|
||||
value,
|
||||
options: { ...this.#option.options, ...options },
|
||||
})
|
||||
|
||||
// Update stored chunks / cookies
|
||||
for (const chunk of chunked) {
|
||||
cookies[chunk.name] = chunk
|
||||
}
|
||||
|
||||
return Object.values(cookies)
|
||||
}
|
||||
|
||||
/** Returns a list of cookies that should be cleaned. */
|
||||
clean(): Cookie[] {
|
||||
return Object.values(this.#clean())
|
||||
}
|
||||
}
|
||||
54
packages/core/src/lib/csrf-token.ts
Normal file
54
packages/core/src/lib/csrf-token.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { createHash, randomString } from "./web"
|
||||
import type { InternalOptions } from "./types"
|
||||
|
||||
interface CreateCSRFTokenParams {
|
||||
options: InternalOptions
|
||||
cookieValue?: string
|
||||
isPost: boolean
|
||||
bodyValue?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure CSRF Token cookie is set for any subsequent requests.
|
||||
* Used as part of the strategy for mitigation for CSRF tokens.
|
||||
*
|
||||
* Creates a cookie like 'next-auth.csrf-token' with the value 'token|hash',
|
||||
* where 'token' is the CSRF token and 'hash' is a hash made of the token and
|
||||
* the secret, and the two values are joined by a pipe '|'. By storing the
|
||||
* value and the hash of the value (with the secret used as a salt) we can
|
||||
* verify the cookie was set by the server and not by a malicous attacker.
|
||||
*
|
||||
* For more details, see the following OWASP links:
|
||||
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
|
||||
* https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf
|
||||
*/
|
||||
export async function createCSRFToken({
|
||||
options,
|
||||
cookieValue,
|
||||
isPost,
|
||||
bodyValue,
|
||||
}: CreateCSRFTokenParams) {
|
||||
if (cookieValue) {
|
||||
const [csrfToken, csrfTokenHash] = cookieValue.split("|")
|
||||
|
||||
const expectedCsrfTokenHash = await createHash(
|
||||
`${csrfToken}${options.secret}`
|
||||
)
|
||||
|
||||
if (csrfTokenHash === expectedCsrfTokenHash) {
|
||||
// If hash matches then we trust the CSRF token value
|
||||
// If this is a POST request and the CSRF Token in the POST request matches
|
||||
// the cookie we have already verified is the one we have set, then the token is verified!
|
||||
const csrfTokenVerified = isPost && csrfToken === bodyValue
|
||||
|
||||
return { csrfTokenVerified, csrfToken }
|
||||
}
|
||||
}
|
||||
|
||||
// New CSRF token
|
||||
const csrfToken = randomString(32)
|
||||
const csrfTokenHash = await createHash(`${csrfToken}${options.secret}`)
|
||||
const cookie = `${csrfToken}|${csrfTokenHash}`
|
||||
|
||||
return { cookie, csrfToken }
|
||||
}
|
||||
18
packages/core/src/lib/default-callbacks.ts
Normal file
18
packages/core/src/lib/default-callbacks.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { CallbacksOptions } from ".."
|
||||
|
||||
export const defaultCallbacks: CallbacksOptions = {
|
||||
signIn() {
|
||||
return true
|
||||
},
|
||||
redirect({ url, baseUrl }) {
|
||||
if (url.startsWith("/")) return `${baseUrl}${url}`
|
||||
else if (new URL(url).origin === baseUrl) return url
|
||||
return baseUrl
|
||||
},
|
||||
session({ session }) {
|
||||
return session
|
||||
},
|
||||
jwt({ token }) {
|
||||
return token
|
||||
},
|
||||
}
|
||||
20
packages/core/src/lib/email/getUserFromEmail.ts
Normal file
20
packages/core/src/lib/email/getUserFromEmail.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { AdapterUser } from "../../adapters"
|
||||
import type { InternalOptions } from "../.."
|
||||
|
||||
/**
|
||||
* Query the database for a user by email address.
|
||||
* If is an existing user return a user object (otherwise use placeholder).
|
||||
*/
|
||||
export default async function getAdapterUserFromEmail({
|
||||
email,
|
||||
adapter,
|
||||
}: {
|
||||
email: string
|
||||
adapter: InternalOptions<"email">["adapter"]
|
||||
}): Promise<AdapterUser> {
|
||||
const { getUserByEmail } = adapter
|
||||
const adapterUser = email ? await getUserByEmail(email) : null
|
||||
if (adapterUser) return adapterUser
|
||||
|
||||
return { id: email, email, emailVerified: null }
|
||||
}
|
||||
49
packages/core/src/lib/email/signin.ts
Normal file
49
packages/core/src/lib/email/signin.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { randomString, createHash } from "../web"
|
||||
import type { InternalOptions } from "../.."
|
||||
|
||||
/**
|
||||
* Starts an e-mail login flow, by generating a token,
|
||||
* and sending it to the user's e-mail (with the help of a DB adapter)
|
||||
*/
|
||||
export default async function email(
|
||||
identifier: string,
|
||||
options: InternalOptions<"email">
|
||||
): Promise<string> {
|
||||
const { url, adapter, provider, callbackUrl, theme } = options
|
||||
// Generate token
|
||||
const token =
|
||||
(await provider.generateVerificationToken?.()) ?? randomString(32)
|
||||
|
||||
const ONE_DAY_IN_SECONDS = 86400
|
||||
const expires = new Date(
|
||||
Date.now() + (provider.maxAge ?? ONE_DAY_IN_SECONDS) * 1000
|
||||
)
|
||||
|
||||
// Generate a link with email, unhashed token and callback url
|
||||
const params = new URLSearchParams({ callbackUrl, token, email: identifier })
|
||||
const _url = `${url}/callback/${provider.id}?${params}`
|
||||
|
||||
const secret = provider.secret ?? options.secret
|
||||
await Promise.all([
|
||||
// Send to user
|
||||
provider.sendVerificationRequest({
|
||||
identifier,
|
||||
token,
|
||||
expires,
|
||||
url: _url,
|
||||
provider,
|
||||
theme,
|
||||
}),
|
||||
// Save in database
|
||||
adapter.createVerificationToken({
|
||||
identifier,
|
||||
token: await createHash(`${token}${secret}`),
|
||||
expires,
|
||||
}),
|
||||
])
|
||||
|
||||
return `${url}/verify-request?${new URLSearchParams({
|
||||
provider: provider.id,
|
||||
type: provider.type,
|
||||
})}`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user