mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
5 Commits
docs/api-r
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f4d43e9ad | ||
|
|
28583b8ab0 | ||
|
|
1e7538a955 | ||
|
|
4258857e52 | ||
|
|
e9d8805609 |
@@ -23,8 +23,8 @@ pnpm-lock.yaml
|
|||||||
|
|
||||||
.docusaurus
|
.docusaurus
|
||||||
build
|
build
|
||||||
docs/docs/reference/03-core
|
docs/docs/reference/core
|
||||||
docs/docs/reference/04-sveltekit
|
docs/docs/reference/sveltekit
|
||||||
static
|
static
|
||||||
|
|
||||||
# --------------- Packages ---------------
|
# --------------- Packages ---------------
|
||||||
|
|||||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -12,6 +12,7 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
firebase-debug.log
|
firebase-debug.log
|
||||||
|
ui-debug.log
|
||||||
.pnpm-debug.log
|
.pnpm-debug.log
|
||||||
|
|
||||||
|
|
||||||
@@ -34,13 +35,10 @@ packages/next-auth/utils
|
|||||||
packages/next-auth/core
|
packages/next-auth/core
|
||||||
packages/next-auth/jwt
|
packages/next-auth/jwt
|
||||||
packages/next-auth/react
|
packages/next-auth/react
|
||||||
packages/next-auth/adapters.d.ts
|
|
||||||
packages/next-auth/adapters.js
|
|
||||||
packages/next-auth/index.d.ts
|
|
||||||
packages/next-auth/index.js
|
|
||||||
packages/next-auth/next
|
packages/next-auth/next
|
||||||
packages/next-auth/middleware.d.ts
|
packages/*/*.js
|
||||||
packages/next-auth/middleware.js
|
packages/*/*.d.ts
|
||||||
|
packages/*/*.d.ts.map
|
||||||
|
|
||||||
# Development app
|
# Development app
|
||||||
apps/dev/src/css
|
apps/dev/src/css
|
||||||
@@ -81,15 +79,12 @@ docs/.docusaurus
|
|||||||
docs/providers.json
|
docs/providers.json
|
||||||
|
|
||||||
# Core
|
# Core
|
||||||
packages/core/*.js
|
|
||||||
packages/core/*.d.ts
|
|
||||||
packages/core/*.d.ts.map
|
|
||||||
packages/core/src/providers/oauth-types.ts
|
packages/core/src/providers/oauth-types.ts
|
||||||
packages/core/lib
|
packages/core/lib
|
||||||
packages/core/providers
|
packages/core/providers
|
||||||
packages/core/src/lib/pages/styles.ts
|
packages/core/src/lib/pages/styles.ts
|
||||||
docs/docs/reference/03-core
|
docs/docs/reference/core
|
||||||
docs/docs/reference/04-sveltekit
|
docs/docs/reference/sveltekit
|
||||||
|
|
||||||
|
|
||||||
# SvelteKit
|
# SvelteKit
|
||||||
@@ -99,3 +94,7 @@ packages/frameworks-sveltekit/.svelte-kit
|
|||||||
packages/frameworks-sveltekit/package
|
packages/frameworks-sveltekit/package
|
||||||
packages/frameworks-sveltekit/vite.config.js.timestamp-*
|
packages/frameworks-sveltekit/vite.config.js.timestamp-*
|
||||||
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
|
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
# Adapters
|
||||||
|
|
||||||
|
docs/docs/reference/adapter
|
||||||
@@ -20,8 +20,8 @@ pnpm-lock.yaml
|
|||||||
|
|
||||||
.docusaurus
|
.docusaurus
|
||||||
build
|
build
|
||||||
docs/docs/reference/03-core
|
docs/docs/reference/core
|
||||||
docs/docs/reference/04-sveltekit
|
docs/docs/reference/sveltekit
|
||||||
static
|
static
|
||||||
docs/providers.json
|
docs/providers.json
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
"name": "playground-nuxt",
|
"name": "playground-nuxt",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt prepare && nuxt build",
|
||||||
"dev": "export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
|
"dev": "nuxt prepare && export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview"
|
||||||
"postinstall": "nuxt prepare"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint-config": "^0.1.1",
|
"@nuxt/eslint-config": "^0.1.1",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Using a JWT to store the `refresh_token` is less secure than saving it in a data
|
|||||||
|
|
||||||
#### JWT strategy
|
#### JWT strategy
|
||||||
|
|
||||||
Using the [jwt](../../reference/03-core/interfaces/types.CallbacksOptions.md#jwt) and [session](../../reference/03-core/interfaces/types.CallbacksOptions.md#session) callbacks, we can persist OAuth tokens and refresh them when they expire.
|
Using the [jwt](../../reference/core/types#jwt) and [session](../../reference/core/types#session) callbacks, we can persist OAuth tokens and refresh them when they expire.
|
||||||
|
|
||||||
Below is a sample implementation using Google's Identity Provider. Please note that the OAuth 2.0 request in the `refreshAccessToken()` function will vary between different providers, but the core logic should remain similar.
|
Below is a sample implementation using Google's Identity Provider. Please note that the OAuth 2.0 request in the `refreshAccessToken()` function will vary between different providers, but the core logic should remain similar.
|
||||||
|
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
---
|
|
||||||
id: firebase
|
|
||||||
title: Firebase
|
|
||||||
---
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
This adapter is still experimental and does not work with Auth.js 4 or newer. If you would like to help out upgrading it, please visit [this PR](https://github.com/nextauthjs/next-auth/pull/3873)
|
|
||||||
:::
|
|
||||||
|
|
||||||
This is the Firebase Adapter for [`next-auth`](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
1. Install the necessary packages
|
|
||||||
|
|
||||||
```bash npm2yarn
|
|
||||||
npm install next-auth @next-auth/firebase-adapter@experimental
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
|
|
||||||
|
|
||||||
```javascript title="pages/api/auth/[...nextauth].js"
|
|
||||||
import NextAuth from "next-auth"
|
|
||||||
import GoogleProvider from "next-auth/providers/google"
|
|
||||||
import { FirebaseAdapter } from "@next-auth/firebase-adapter"
|
|
||||||
|
|
||||||
import firebase from "firebase/app"
|
|
||||||
import "firebase/firestore"
|
|
||||||
|
|
||||||
const firestore = (
|
|
||||||
firebase.apps[0] ?? firebase.initializeApp(/* your config */)
|
|
||||||
).firestore()
|
|
||||||
|
|
||||||
// For more information on each option (and a full list of options) go to
|
|
||||||
// https://authjs.dev/reference/configuration/auth-options
|
|
||||||
export default NextAuth({
|
|
||||||
// https://authjs.dev/reference/providers/
|
|
||||||
providers: [
|
|
||||||
GoogleProvider({
|
|
||||||
clientId: process.env.GOOGLE_ID,
|
|
||||||
clientSecret: process.env.GOOGLE_SECRET,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
adapter: FirebaseAdapter(firestore),
|
|
||||||
...
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
When initializing the firestore adapter, you must pass in the firebase config object with the details from your project. More details on how to obtain that config object can be found [here](https://support.google.com/firebase/answer/7015592).
|
|
||||||
|
|
||||||
An example firebase config looks like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const firebaseConfig = {
|
|
||||||
apiKey: "AIzaSyDOCAbC123dEf456GhI789jKl01-MnO",
|
|
||||||
authDomain: "myapp-project-123.firebaseapp.com",
|
|
||||||
databaseURL: "https://myapp-project-123.firebaseio.com",
|
|
||||||
projectId: "myapp-project-123",
|
|
||||||
storageBucket: "myapp-project-123.appspot.com",
|
|
||||||
messagingSenderId: "65211879809",
|
|
||||||
appId: "1:65211879909:web:3ae38ef1cdcb2e01fe5f0c",
|
|
||||||
measurementId: "G-8GSGZQ44ST",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See [firebase.google.com/docs/web/setup](https://firebase.google.com/docs/web/setup) for more details.
|
|
||||||
|
|
||||||
:::tip **From Firebase**
|
|
||||||
|
|
||||||
**Caution**: We do not recommend manually modifying an app's Firebase config file or object. If you initialize an app with invalid or missing values for any of these required "Firebase options", then your end users may experience serious issues.
|
|
||||||
|
|
||||||
For open source projects, we generally do not recommend including the app's Firebase config file or object in source control because, in most cases, your users should create their own Firebase projects and point their apps to their own Firebase resources (via their own Firebase config file or object).
|
|
||||||
:::
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
title: Overview
|
|
||||||
sidebar_label: Overview
|
|
||||||
sidebar_position: 0
|
|
||||||
---
|
|
||||||
|
|
||||||
## Core
|
|
||||||
|
|
||||||
## Providers
|
|
||||||
|
|
||||||
- OAuth/OIDC
|
|
||||||
- Email/Passwordless
|
|
||||||
- Credentials
|
|
||||||
|
|
||||||
## Database Adapters
|
|
||||||
|
|
||||||
## Frameworks
|
|
||||||
|
|
||||||
- Next.js
|
|
||||||
- SvelteKit
|
|
||||||
- SolidStart
|
|
||||||
- Remix
|
|
||||||
- Nuxt
|
|
||||||
- Gatsby
|
|
||||||
- etc.
|
|
||||||
@@ -62,7 +62,7 @@ const docusaurusConfig = {
|
|||||||
position: "left",
|
position: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/reference/core/modules/main",
|
to: "/reference/core",
|
||||||
// TODO: change to this when the overview page looks better.
|
// TODO: change to this when the overview page looks better.
|
||||||
// to: "/reference",
|
// to: "/reference",
|
||||||
activeBasePath: "/reference",
|
activeBasePath: "/reference",
|
||||||
@@ -101,7 +101,7 @@ const docusaurusConfig = {
|
|||||||
announcementBar: {
|
announcementBar: {
|
||||||
id: "new-major-announcement",
|
id: "new-major-announcement",
|
||||||
content:
|
content:
|
||||||
"<a target='_blank' rel='noopener noreferrer' href='https://next-auth.js.org'>NextAuth.js</a> is becoming Auth.js! 🎉 We're creating Authentication for the Web. Everyone included. Starting with SvelteKit, check out <a href='/reference/sveltekit'>the docs</a>.",
|
"<a target='_blank' rel='noopener noreferrer' href='https://next-auth.js.org'>NextAuth.js</a> is becoming Auth.js! 🎉 We're creating Authentication for the Web. Everyone included. Starting with SvelteKit, check out <a href='/reference/sveltekit'>the docs</a>. Note, this site is under active development.",
|
||||||
backgroundColor: "#000",
|
backgroundColor: "#000",
|
||||||
textColor: "#fff",
|
textColor: "#fff",
|
||||||
},
|
},
|
||||||
@@ -182,10 +182,7 @@ const docusaurusConfig = {
|
|||||||
lastVersion: "current",
|
lastVersion: "current",
|
||||||
showLastUpdateAuthor: true,
|
showLastUpdateAuthor: true,
|
||||||
showLastUpdateTime: true,
|
showLastUpdateTime: true,
|
||||||
remarkPlugins: [
|
remarkPlugins: [require("@sapphire/docusaurus-plugin-npm2yarn2pnpm").npm2yarn2pnpm],
|
||||||
require("@sapphire/docusaurus-plugin-npm2yarn2pnpm").npm2yarn2pnpm,
|
|
||||||
require("remark-github"),
|
|
||||||
],
|
|
||||||
versions: {
|
versions: {
|
||||||
current: {
|
current: {
|
||||||
label: "experimental",
|
label: "experimental",
|
||||||
@@ -204,20 +201,14 @@ const docusaurusConfig = {
|
|||||||
{
|
{
|
||||||
...typedocConfig,
|
...typedocConfig,
|
||||||
id: "core",
|
id: "core",
|
||||||
plugin: ["./tyepdoc"],
|
plugin: [require.resolve("./typedoc-mdn-links")],
|
||||||
entryPoints: [
|
|
||||||
"index.ts",
|
|
||||||
"adapters.ts",
|
|
||||||
"errors.ts",
|
|
||||||
"jwt.ts",
|
|
||||||
"types.ts",
|
|
||||||
]
|
|
||||||
.map((e) => `${coreSrc}/${e}`)
|
|
||||||
.concat(providers),
|
|
||||||
tsconfig: "../packages/core/tsconfig.json",
|
|
||||||
out: "reference/03-core",
|
|
||||||
watch: process.env.TYPEDOC_WATCH,
|
watch: process.env.TYPEDOC_WATCH,
|
||||||
includeExtension: false,
|
entryPoints: ["index.ts", "adapters.ts", "errors.ts", "jwt.ts", "types.ts"].map((e) => `${coreSrc}/${e}`).concat(providers),
|
||||||
|
tsconfig: "../packages/core/tsconfig.json",
|
||||||
|
out: "reference/core",
|
||||||
|
sidebar: {
|
||||||
|
indexLabel: "index",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -225,14 +216,29 @@ const docusaurusConfig = {
|
|||||||
{
|
{
|
||||||
...typedocConfig,
|
...typedocConfig,
|
||||||
id: "sveltekit",
|
id: "sveltekit",
|
||||||
plugin: ["./tyepdoc"],
|
plugin: [require.resolve("./typedoc-mdn-links")],
|
||||||
entryPoints: ["index.ts", "client.ts"].map(
|
|
||||||
(e) => `../packages/frameworks-sveltekit/src/lib/${e}`
|
|
||||||
),
|
|
||||||
tsconfig: "../packages/frameworks-sveltekit/tsconfig.json",
|
|
||||||
out: "reference/04-sveltekit",
|
|
||||||
watch: process.env.TYPEDOC_WATCH,
|
watch: process.env.TYPEDOC_WATCH,
|
||||||
includeExtension: false,
|
entryPoints: ["index.ts", "client.ts"].map((e) => `../packages/frameworks-sveltekit/src/lib/${e}`),
|
||||||
|
tsconfig: "../packages/frameworks-sveltekit/tsconfig.json",
|
||||||
|
out: "reference/sveltekit",
|
||||||
|
sidebar: {
|
||||||
|
indexLabel: "index",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"docusaurus-plugin-typedoc",
|
||||||
|
{
|
||||||
|
...typedocConfig,
|
||||||
|
id: "firebase-adapter",
|
||||||
|
plugin: [require.resolve("./typedoc-mdn-links")],
|
||||||
|
watch: process.env.TYPEDOC_WATCH,
|
||||||
|
entryPoints: ["../packages/adapter-firebase/src/index.ts"],
|
||||||
|
tsconfig: "../packages/adapter-firebase/tsconfig.json",
|
||||||
|
out: "reference/adapter/firebase",
|
||||||
|
sidebar: {
|
||||||
|
indexLabel: "Firebase",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"repository": "https://github.com/nextauthjs/next-auth",
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
"name": "docs",
|
"name": "docs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "TYPEDOC_WATCH=true docusaurus start --no-open --port 8000",
|
"start": "TYPEDOC_WATCH=true docusaurus start --no-open",
|
||||||
"dev": "pnpm providers && pnpm snippets && pnpm start",
|
"dev": "pnpm providers && pnpm snippets && pnpm start",
|
||||||
"build": "pnpm providers && docusaurus build",
|
"build": "pnpm providers && docusaurus build",
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
@@ -27,7 +27,6 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-marquee-slider": "^1.1.5",
|
"react-marquee-slider": "^1.1.5",
|
||||||
"remark-github": "10.1.0",
|
|
||||||
"styled-components": "5.3.6"
|
"styled-components": "5.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -37,7 +36,9 @@
|
|||||||
"@docusaurus/preset-classic": "2.2.0",
|
"@docusaurus/preset-classic": "2.2.0",
|
||||||
"@docusaurus/theme-common": "2.2.0",
|
"@docusaurus/theme-common": "2.2.0",
|
||||||
"@docusaurus/types": "2.2.0",
|
"@docusaurus/types": "2.2.0",
|
||||||
"docusaurus-plugin-typedoc": "^0.18.0"
|
"docusaurus-plugin-typedoc": "1.0.0-next.2",
|
||||||
|
"typedoc": "^0.23.24",
|
||||||
|
"typedoc-plugin-markdown": "4.0.0-next.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
@@ -14,61 +14,28 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
referenceSidebar: [
|
referenceSidebar: [
|
||||||
"reference/index",
|
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "@auth/core",
|
label: "@auth/core",
|
||||||
link: {
|
link: { type: "doc", id: "reference/core/index" },
|
||||||
type: "doc",
|
items: [{ type: "autogenerated", dirName: "reference/core" }],
|
||||||
id: "reference/core/modules/main",
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
type: "autogenerated",
|
|
||||||
dirName: "reference/03-core/modules",
|
|
||||||
// See: https://github.com/facebook/docusaurus/issues/5689
|
|
||||||
// exclude: ["index"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "category",
|
|
||||||
label: "Reflections",
|
|
||||||
collapsed: true,
|
|
||||||
className: "reflection-category", // See src/index.css
|
|
||||||
items: [{ type: "autogenerated", dirName: "reference/03-core" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "@auth/sveltekit",
|
label: "@auth/sveltekit",
|
||||||
link: { type: "doc", id: "reference/sveltekit/modules/main" },
|
link: { type: "doc", id: "reference/sveltekit/index" },
|
||||||
items: [
|
items: [{ type: "autogenerated", dirName: "reference/sveltekit" }],
|
||||||
{ type: "autogenerated", dirName: "reference/04-sveltekit/modules" },
|
|
||||||
{
|
|
||||||
type: "category",
|
|
||||||
label: "Reflections",
|
|
||||||
collapsed: true,
|
|
||||||
className: "reflection-category", // See src/index.css
|
|
||||||
items: [{ type: "autogenerated", dirName: "reference/04-sveltekit" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "@auth/solid-start",
|
label: "@auth/solid-start",
|
||||||
link: {
|
link: { type: "doc", id: "reference/solidstart/index" },
|
||||||
type: "doc",
|
items: [{ type: "autogenerated", dirName: "reference/04-solidstart" }],
|
||||||
id: "reference/solidstart/index",
|
|
||||||
},
|
|
||||||
items: ["reference/solidstart/client", "reference/solidstart/protected"],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "@auth/nextjs",
|
label: "@auth/nextjs",
|
||||||
link: {
|
link: { type: "doc", id: "reference/nextjs/index" },
|
||||||
type: "doc",
|
|
||||||
id: "reference/nextjs/index",
|
|
||||||
},
|
|
||||||
items: [
|
items: [
|
||||||
"reference/nextjs/client",
|
"reference/nextjs/client",
|
||||||
{
|
{
|
||||||
@@ -83,12 +50,8 @@ module.exports = {
|
|||||||
label: "Database Adapters",
|
label: "Database Adapters",
|
||||||
link: { type: "doc", id: "reference/adapters/overview" },
|
link: { type: "doc", id: "reference/adapters/overview" },
|
||||||
items: [
|
items: [
|
||||||
{
|
{ type: "doc", id: "reference/adapter/firebase/index" },
|
||||||
type: "autogenerated",
|
{ type: "autogenerated", dirName: "reference/06-adapters" },
|
||||||
dirName: "reference/06-adapters",
|
|
||||||
// See: https://github.com/facebook/docusaurus/issues/5689
|
|
||||||
// exclude: ["index"],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -272,27 +272,4 @@ html[data-theme="dark"] #carbonads > span {
|
|||||||
html[data-theme="dark"] #carbonads .carbon-poweredby {
|
html[data-theme="dark"] #carbonads .carbon-poweredby {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
background: #1e2021;
|
background: #1e2021;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
This is a hack to hide the "Reflection" category and "main" module from the sidebar.
|
|
||||||
This is because:
|
|
||||||
1. opening any page under the "Reflection" category would hide the entire sidebar.
|
|
||||||
2. the "main" module would show up twice.
|
|
||||||
See sidebars.js
|
|
||||||
*/
|
|
||||||
.reflection-category,
|
|
||||||
.theme-doc-sidebar-item-link-level-2 [href="/reference/core/modules/main"],
|
|
||||||
.theme-doc-sidebar-item-link-level-2
|
|
||||||
[href="/reference/sveltekit/modules/main"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
HACK: to hide the "Classes" header and duplicate items together with the "typedoc-plugin-markdown" patch.
|
|
||||||
See: https://github.com/TypeStrong/typedoc/issues/2006
|
|
||||||
*/
|
|
||||||
/* h3.anchor + p:has(code, strong), */ /** hack did not work as it hides property types elsewhere */
|
|
||||||
#classes {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,16 @@
|
|||||||
{
|
{
|
||||||
"excludeNotDocumented": true,
|
|
||||||
"$schema": "https://typedoc.org/schema.json",
|
"$schema": "https://typedoc.org/schema.json",
|
||||||
"allReflectionsHaveOwnDocument": true,
|
"cleanOutputDir": true,
|
||||||
"disableSources": true,
|
"disableSources": true,
|
||||||
"hideBreadcrumbs": true,
|
|
||||||
"excludeExternals": true,
|
"excludeExternals": true,
|
||||||
"excludeInternal": true,
|
"excludeInternal": true,
|
||||||
|
"excludeNotDocumented": true,
|
||||||
"excludePrivate": true,
|
"excludePrivate": true,
|
||||||
"cleanOutputDir": true,
|
|
||||||
"excludeProtected": true,
|
"excludeProtected": true,
|
||||||
|
"hideHierarchy": true,
|
||||||
"gitRevision": "main",
|
"gitRevision": "main",
|
||||||
|
"hideBreadcrumbs": true,
|
||||||
"hideGenerator": true,
|
"hideGenerator": true,
|
||||||
"intentionallyNotExported": [
|
|
||||||
"ReturnTypes",
|
|
||||||
"CallbackParameters",
|
|
||||||
"JsonValue"
|
|
||||||
],
|
|
||||||
"readme": "none",
|
|
||||||
"sort": ["kind", "static-first", "required-first", "alphabetical"],
|
|
||||||
"kindSortOrder": [
|
"kindSortOrder": [
|
||||||
"Function",
|
"Function",
|
||||||
"TypeAlias",
|
"TypeAlias",
|
||||||
@@ -41,5 +34,13 @@
|
|||||||
"IndexSignature",
|
"IndexSignature",
|
||||||
"GetSignature",
|
"GetSignature",
|
||||||
"SetSignature"
|
"SetSignature"
|
||||||
]
|
],
|
||||||
}
|
"readme": "none",
|
||||||
|
"sort": [
|
||||||
|
"kind",
|
||||||
|
"static-first",
|
||||||
|
"required-first",
|
||||||
|
"alphabetical"
|
||||||
|
],
|
||||||
|
"symbolsWithOwnFile": "none"
|
||||||
|
}
|
||||||
@@ -41,8 +41,6 @@
|
|||||||
"prettier": "2.8.1",
|
"prettier": "2.8.1",
|
||||||
"prettier-plugin-svelte": "^2.8.1",
|
"prettier-plugin-svelte": "^2.8.1",
|
||||||
"turbo": "1.6.3",
|
"turbo": "1.6.3",
|
||||||
"typedoc": "^0.23.22",
|
|
||||||
"typedoc-plugin-markdown": "^3.14.0",
|
|
||||||
"typescript": "4.9.4"
|
"typescript": "4.9.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -64,7 +62,6 @@
|
|||||||
"undici": "5.11.0"
|
"undici": "5.11.0"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"typedoc-plugin-markdown@3.14.0": "patches/typedoc-plugin-markdown@3.14.0.patch",
|
|
||||||
"@balazsorban/monorepo-release@0.1.8": "patches/@balazsorban__monorepo-release@0.1.8.patch"
|
"@balazsorban/monorepo-release@0.1.8": "patches/@balazsorban__monorepo-release@0.1.8.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@next-auth/dynamodb-adapter",
|
"name": "@next-auth/dynamodb-adapter",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth",
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
"version": "1.2.0",
|
"version": "3.0.0",
|
||||||
"description": "AWS DynamoDB adapter for next-auth.",
|
"description": "AWS DynamoDB adapter for next-auth.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"next-auth",
|
"next-auth",
|
||||||
@@ -9,11 +9,18 @@
|
|||||||
"oauth",
|
"oauth",
|
||||||
"dynamodb"
|
"dynamodb"
|
||||||
],
|
],
|
||||||
|
"type": "module",
|
||||||
|
"types": "./index.d.ts",
|
||||||
"homepage": "https://authjs.dev",
|
"homepage": "https://authjs.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"import": "./index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"private": false,
|
"private": false,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
@@ -26,7 +33,10 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"README.md",
|
"README.md",
|
||||||
"dist"
|
"index.js",
|
||||||
|
"index.d.ts",
|
||||||
|
"index.d.ts.map",
|
||||||
|
"src"
|
||||||
],
|
],
|
||||||
"author": "Pol Marnette",
|
"author": "Pol Marnette",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@@ -41,7 +51,11 @@
|
|||||||
"@next-auth/adapter-test": "workspace:*",
|
"@next-auth/adapter-test": "workspace:*",
|
||||||
"@next-auth/tsconfig": "workspace:*",
|
"@next-auth/tsconfig": "workspace:*",
|
||||||
"@shelf/jest-dynamodb": "^2.1.0",
|
"@shelf/jest-dynamodb": "^2.1.0",
|
||||||
|
"@types/uuid": "^9.0.0",
|
||||||
"jest": "^27.4.3",
|
"jest": "^27.4.3",
|
||||||
"next-auth": "workspace:*"
|
"next-auth": "workspace:*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "^9.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { randomBytes } from "crypto"
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BatchWriteCommandInput,
|
BatchWriteCommandInput,
|
||||||
@@ -12,16 +12,12 @@ import type {
|
|||||||
VerificationToken,
|
VerificationToken,
|
||||||
} from "next-auth/adapters"
|
} from "next-auth/adapters"
|
||||||
|
|
||||||
import { format, generateUpdateExpression } from "./utils"
|
|
||||||
|
|
||||||
export { format, generateUpdateExpression }
|
|
||||||
|
|
||||||
export interface DynamoDBAdapterOptions {
|
export interface DynamoDBAdapterOptions {
|
||||||
tableName?: string,
|
tableName?: string
|
||||||
partitionKey?: string,
|
partitionKey?: string
|
||||||
sortKey?: string,
|
sortKey?: string
|
||||||
indexName?: string,
|
indexName?: string
|
||||||
indexPartitionKey?: string,
|
indexPartitionKey?: string
|
||||||
indexSortKey?: string
|
indexSortKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,17 +26,17 @@ export function DynamoDBAdapter(
|
|||||||
options?: DynamoDBAdapterOptions
|
options?: DynamoDBAdapterOptions
|
||||||
): Adapter {
|
): Adapter {
|
||||||
const TableName = options?.tableName ?? "next-auth"
|
const TableName = options?.tableName ?? "next-auth"
|
||||||
const pk = options?.partitionKey ?? 'pk'
|
const pk = options?.partitionKey ?? "pk"
|
||||||
const sk = options?.sortKey ?? 'sk'
|
const sk = options?.sortKey ?? "sk"
|
||||||
const IndexName = options?.indexName ?? 'GSI1'
|
const IndexName = options?.indexName ?? "GSI1"
|
||||||
const GSI1PK = options?.indexPartitionKey ?? 'GSI1PK'
|
const GSI1PK = options?.indexPartitionKey ?? "GSI1PK"
|
||||||
const GSI1SK = options?.indexSortKey ?? 'GSI1SK'
|
const GSI1SK = options?.indexSortKey ?? "GSI1SK"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async createUser(data) {
|
async createUser(data) {
|
||||||
const user: AdapterUser = {
|
const user: AdapterUser = {
|
||||||
...(data as any),
|
...(data as any),
|
||||||
id: randomBytes(16).toString("hex"),
|
id: uuid(),
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.put({
|
await client.put({
|
||||||
@@ -50,8 +46,8 @@ export function DynamoDBAdapter(
|
|||||||
[pk]: `USER#${user.id}`,
|
[pk]: `USER#${user.id}`,
|
||||||
[sk]: `USER#${user.id}`,
|
[sk]: `USER#${user.id}`,
|
||||||
type: "USER",
|
type: "USER",
|
||||||
[GSI1PK]: `USER#${user.email as string}`,
|
[GSI1PK]: `USER#${user.email}`,
|
||||||
[GSI1SK]: `USER#${user.email as string}`,
|
[GSI1SK]: `USER#${user.email}`,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -165,7 +161,7 @@ export function DynamoDBAdapter(
|
|||||||
async linkAccount(data) {
|
async linkAccount(data) {
|
||||||
const item = {
|
const item = {
|
||||||
...data,
|
...data,
|
||||||
id: randomBytes(16).toString("hex"),
|
id: uuid(),
|
||||||
[pk]: `USER#${data.userId}`,
|
[pk]: `USER#${data.userId}`,
|
||||||
[sk]: `ACCOUNT#${data.provider}#${data.providerAccountId}`,
|
[sk]: `ACCOUNT#${data.provider}#${data.providerAccountId}`,
|
||||||
[GSI1PK]: `ACCOUNT#${data.provider}`,
|
[GSI1PK]: `ACCOUNT#${data.provider}`,
|
||||||
@@ -229,7 +225,7 @@ export function DynamoDBAdapter(
|
|||||||
},
|
},
|
||||||
async createSession(data) {
|
async createSession(data) {
|
||||||
const session = {
|
const session = {
|
||||||
id: randomBytes(16).toString("hex"),
|
id: uuid(),
|
||||||
...data,
|
...data,
|
||||||
}
|
}
|
||||||
await client.put({
|
await client.put({
|
||||||
@@ -327,3 +323,73 @@ export function DynamoDBAdapter(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/honeinc/is-iso-date/blob/master/index.js
|
||||||
|
const isoDateRE =
|
||||||
|
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
|
||||||
|
function isDate(value: any) {
|
||||||
|
return value && isoDateRE.test(value) && !isNaN(Date.parse(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const format = {
|
||||||
|
/** Takes a plain old JavaScript object and turns it into a Dynamodb object */
|
||||||
|
to(object: Record<string, any>) {
|
||||||
|
const newObject: Record<string, unknown> = {}
|
||||||
|
for (const key in object) {
|
||||||
|
const value = object[key]
|
||||||
|
if (value instanceof Date) {
|
||||||
|
// DynamoDB requires the TTL attribute be a UNIX timestamp (in secs).
|
||||||
|
if (key === "expires") newObject[key] = value.getTime() / 1000
|
||||||
|
else newObject[key] = value.toISOString()
|
||||||
|
} else newObject[key] = value
|
||||||
|
}
|
||||||
|
return newObject
|
||||||
|
},
|
||||||
|
/** Takes a Dynamo object and returns a plain old JavaScript object */
|
||||||
|
from<T = Record<string, unknown>>(object?: Record<string, any>): T | null {
|
||||||
|
if (!object) return null
|
||||||
|
const newObject: Record<string, unknown> = {}
|
||||||
|
for (const key in object) {
|
||||||
|
// Filter DynamoDB specific attributes so it doesn't get passed to core,
|
||||||
|
// to avoid revealing the type of database
|
||||||
|
if (["pk", "sk", "GSI1PK", "GSI1SK"].includes(key)) continue
|
||||||
|
|
||||||
|
const value = object[key]
|
||||||
|
|
||||||
|
if (isDate(value)) newObject[key] = new Date(value)
|
||||||
|
// hack to keep type property in account
|
||||||
|
else if (key === "type" && ["SESSION", "VT", "USER"].includes(value))
|
||||||
|
continue
|
||||||
|
// The expires property is stored as a UNIX timestamp in seconds, but
|
||||||
|
// JavaScript needs it in milliseconds, so multiply by 1000.
|
||||||
|
else if (key === "expires" && typeof value === "number")
|
||||||
|
newObject[key] = new Date(value * 1000)
|
||||||
|
else newObject[key] = value
|
||||||
|
}
|
||||||
|
return newObject as T
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateUpdateExpression(object: Record<string, any>): {
|
||||||
|
UpdateExpression: string
|
||||||
|
ExpressionAttributeNames: Record<string, string>
|
||||||
|
ExpressionAttributeValues: Record<string, unknown>
|
||||||
|
} {
|
||||||
|
const formatedSession = format.to(object)
|
||||||
|
let UpdateExpression = "set"
|
||||||
|
const ExpressionAttributeNames: Record<string, string> = {}
|
||||||
|
const ExpressionAttributeValues: Record<string, unknown> = {}
|
||||||
|
for (const property in formatedSession) {
|
||||||
|
UpdateExpression += ` #${property} = :${property},`
|
||||||
|
ExpressionAttributeNames["#" + property] = property
|
||||||
|
ExpressionAttributeValues[":" + property] = formatedSession[property]
|
||||||
|
}
|
||||||
|
UpdateExpression = UpdateExpression.slice(0, -1)
|
||||||
|
return {
|
||||||
|
UpdateExpression,
|
||||||
|
ExpressionAttributeNames,
|
||||||
|
ExpressionAttributeValues,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { format, generateUpdateExpression }
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// https://github.com/honeinc/is-iso-date/blob/master/index.js
|
|
||||||
const isoDateRE =
|
|
||||||
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
|
|
||||||
function isDate(value: any) {
|
|
||||||
return value && isoDateRE.test(value) && !isNaN(Date.parse(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const format = {
|
|
||||||
/** Takes a plain old JavaScript object and turns it into a Dynamodb object */
|
|
||||||
to(object: Record<string, any>) {
|
|
||||||
const newObject: Record<string, unknown> = {}
|
|
||||||
for (const key in object) {
|
|
||||||
const value = object[key]
|
|
||||||
if (value instanceof Date) {
|
|
||||||
// DynamoDB requires the TTL attribute be a UNIX timestamp (in secs).
|
|
||||||
if (key === "expires") newObject[key] = value.getTime() / 1000
|
|
||||||
else newObject[key] = value.toISOString()
|
|
||||||
} else newObject[key] = value
|
|
||||||
}
|
|
||||||
return newObject
|
|
||||||
},
|
|
||||||
/** Takes a Dynamo object and returns a plain old JavaScript object */
|
|
||||||
from<T = Record<string, unknown>>(object?: Record<string, any>): T | null {
|
|
||||||
if (!object) return null
|
|
||||||
const newObject: Record<string, unknown> = {}
|
|
||||||
for (const key in object) {
|
|
||||||
// Filter DynamoDB specific attributes so it doesn't get passed to core,
|
|
||||||
// to avoid revealing the type of database
|
|
||||||
if (["pk", "sk", "GSI1PK", "GSI1SK"].includes(key)) continue
|
|
||||||
|
|
||||||
const value = object[key]
|
|
||||||
|
|
||||||
if (isDate(value)) newObject[key] = new Date(value)
|
|
||||||
// hack to keep type property in account
|
|
||||||
else if (key === "type" && ["SESSION", "VT", "USER"].includes(value))
|
|
||||||
continue
|
|
||||||
// The expires property is stored as a UNIX timestamp in seconds, but
|
|
||||||
// JavaScript needs it in milliseconds, so multiply by 1000.
|
|
||||||
else if (key === "expires" && typeof value === "number")
|
|
||||||
newObject[key] = new Date(value * 1000)
|
|
||||||
else newObject[key] = value
|
|
||||||
}
|
|
||||||
return newObject as T
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateUpdateExpression(object: Record<string, any>): {
|
|
||||||
UpdateExpression: string
|
|
||||||
ExpressionAttributeNames: Record<string, string>
|
|
||||||
ExpressionAttributeValues: Record<string, unknown>
|
|
||||||
} {
|
|
||||||
const formatedSession = format.to(object)
|
|
||||||
let UpdateExpression = "set"
|
|
||||||
const ExpressionAttributeNames: Record<string, string> = {}
|
|
||||||
const ExpressionAttributeValues: Record<string, unknown> = {}
|
|
||||||
for (const property in formatedSession) {
|
|
||||||
UpdateExpression += ` #${property} = :${property},`
|
|
||||||
ExpressionAttributeNames["#" + property] = property
|
|
||||||
ExpressionAttributeValues[":" + property] = formatedSession[property]
|
|
||||||
}
|
|
||||||
UpdateExpression = UpdateExpression.slice(0, -1)
|
|
||||||
return {
|
|
||||||
UpdateExpression,
|
|
||||||
ExpressionAttributeNames,
|
|
||||||
ExpressionAttributeValues,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { format } from "../src/utils"
|
import { format } from "../src/"
|
||||||
|
|
||||||
describe("dynamodb utils.format", () => {
|
describe("dynamodb utils.format", () => {
|
||||||
it("format.to() preserves non-Date non-expires properties", () => {
|
it("format.to() preserves non-Date non-expires properties", () => {
|
||||||
|
|||||||
@@ -2,7 +2,15 @@
|
|||||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "dist"
|
"outDir": ".",
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"skipDefaultLibCheck": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"declaration": true
|
||||||
},
|
},
|
||||||
"exclude": ["tests", "dist", "jest.config.js", "jest-dynamodb-config.js"]
|
"exclude": ["tests", "dist", "jest.config.js", "jest-dynamodb-config.js"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
||||||
|
|
||||||
## [0.1.3](https://github.com/nextauthjs/adapters/compare/@next-auth/firebase-adapter@0.1.2...@next-auth/firebase-adapter@0.1.3) (2021-08-17)
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @next-auth/firebase-adapter
|
|
||||||
|
|
||||||
## [0.1.2](https://github.com/nextauthjs/adapters/compare/@next-auth/firebase-adapter@0.1.1...@next-auth/firebase-adapter@0.1.2) (2021-07-02)
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @next-auth/firebase-adapter
|
|
||||||
|
|
||||||
## [0.1.1](https://github.com/nextauthjs/adapters/compare/@next-auth/firebase-adapter@0.1.0...@next-auth/firebase-adapter@0.1.1) (2021-06-30)
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @next-auth/firebase-adapter
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<br/>
|
<br/>
|
||||||
<a href="https://authjs.dev" target="_blank">
|
<a href="https://authjs.dev" target="_blank">
|
||||||
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" /></a><img height="64px" src="https://raw.githubusercontent.com/nextauthjs/adapters/main/packages/firebase/logo.svg" />
|
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" /></a><img height="64px" src="https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/adapter-firebase/logo.svg" />
|
||||||
<h3 align="center"><b>Firebase Adapter</b> - NextAuth.js</h3>
|
<h3 align="center"><b>Firebase Adapter</b> - Auth.js</h3>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Open Source. Full Stack. Own Your Data.
|
Open Source. Full Stack. Own Your Data.
|
||||||
</p>
|
</p>
|
||||||
@@ -13,72 +13,12 @@
|
|||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This is the Firebase Adapter for [`auth.js`](https://authjs.dev). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
|
This is the official Firebase Adapter for [Auth.js](https://authjs.dev) / [NextAuth.js](https://next-auth.js.org/), using the [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup) and [Firestore](https://firebase.google.com/docs/firestore).
|
||||||
|
|
||||||
You can find more Firebase information in the docs at [authjs.dev/reference/adapters/firebase](https://authjs.dev/reference/adapters/firebase).
|
## Documentation
|
||||||
|
|
||||||
## Getting Started
|
Check out the [documentation](https://authjs.dev/reference/adapter/firebase) to learn how to use this adapter in your project.
|
||||||
|
|
||||||
1. Install `next-auth` and `@next-auth/firebase-adapter`.
|
|
||||||
|
|
||||||
```js
|
|
||||||
npm install next-auth @next-auth/firebase-adapter
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import NextAuth from "next-auth"
|
|
||||||
import Providers from "next-auth/providers"
|
|
||||||
import { FirestoreAdapter } from "@next-auth/firebase-adapter"
|
|
||||||
|
|
||||||
import { initializeApp } from "firebase/app";
|
|
||||||
import { getFirestore } from "firebase/firestore"
|
|
||||||
|
|
||||||
const app = initializeApp({ projectId: "next-auth-test" });
|
|
||||||
const firestore = getFirestore(app);
|
|
||||||
|
|
||||||
// For more information on each option (and a full list of options) go to
|
|
||||||
// https://authjs.dev/reference/configuration/auth-options
|
|
||||||
export default NextAuth({
|
|
||||||
// https://authjs.dev/reference/providers/oauth-builtin
|
|
||||||
providers: [
|
|
||||||
Providers.Google({
|
|
||||||
clientId: process.env.GOOGLE_ID,
|
|
||||||
clientSecret: process.env.GOOGLE_SECRET,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
adapter: FirestoreAdapter(firestore),
|
|
||||||
...
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
When initializing the firestore adapter, you must pass in the firebase config object with the details from your project. More details on how to obtain that config object can be found [here](https://support.google.com/firebase/answer/7015592).
|
|
||||||
|
|
||||||
An example firebase config looks like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const firebaseConfig = {
|
|
||||||
apiKey: "AIzaSyDOCAbC123dEf456GhI789jKl01-MnO",
|
|
||||||
authDomain: "myapp-project-123.firebaseapp.com",
|
|
||||||
databaseURL: "https://myapp-project-123.firebaseio.com",
|
|
||||||
projectId: "myapp-project-123",
|
|
||||||
storageBucket: "myapp-project-123.appspot.com",
|
|
||||||
messagingSenderId: "65211879809",
|
|
||||||
appId: "1:65211879909:web:3ae38ef1cdcb2e01fe5f0c",
|
|
||||||
measurementId: "G-8GSGZQ44ST",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See [firebase.google.com/docs/web/setup](https://firebase.google.com/docs/web/setup) for more details.
|
|
||||||
|
|
||||||
> **From Firebase - Caution**: We do not recommend manually modifying an app's Firebase config file or object. If you initialize an app with invalid or missing values for any of these required "Firebase options", then your end users may experience serious issues.
|
|
||||||
>
|
|
||||||
> For open source projects, we generally do not recommend including the app's Firebase config file or object in source control because, in most cases, your users should create their own Firebase projects and point their apps to their own Firebase resources (via their own Firebase config file or object).
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"emulators": {
|
"firestore": {
|
||||||
|
"rules": "firestore.rules"
|
||||||
|
},
|
||||||
|
"emulator": {
|
||||||
"firestore": {
|
"firestore": {
|
||||||
"port": 8080
|
"port": 8080
|
||||||
}
|
}
|
||||||
|
|||||||
10
packages/adapter-firebase/firestore.rules
Normal file
10
packages/adapter-firebase/firestore.rules
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
rules_version = '2';
|
||||||
|
|
||||||
|
// Deny read/write access to all users under any conditions
|
||||||
|
service cloud.firestore {
|
||||||
|
match /databases/{database}/documents {
|
||||||
|
match /{document=**} {
|
||||||
|
allow read, write: if false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = require("@next-auth/adapter-test/jest/jest-preset")
|
|
||||||
@@ -33,4 +33,4 @@
|
|||||||
<circle cx="144" cy="144" r="40" fill="#757575"/>
|
<circle cx="144" cy="144" r="40" fill="#757575"/>
|
||||||
<path d="M144 146l-18 8v-8l18-8 18 8v7-1.5 2.5zm0-22l18 8v8l-18-8-18 8v-8zm6.75 29l9 4-15.75 7v-8z" fill="#fff" fill-rule="evenodd"/>
|
<path d="M144 146l-18 8v-8l18-8 18 8v7-1.5 2.5zm0-22l18 8v8l-18-8-18 8v-8zm6.75 29l9 4-15.75 7v-8z" fill="#fff" fill-rule="evenodd"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@next-auth/firebase-adapter",
|
"name": "@next-auth/firebase-adapter",
|
||||||
"version": "1.0.3",
|
"version": "2.0.0",
|
||||||
"description": "Firebase adapter for next-auth.",
|
"description": "Firebase adapter for next-auth.",
|
||||||
"homepage": "https://authjs.dev",
|
"homepage": "https://authjs.dev",
|
||||||
"repository": "https://github.com/nextauthjs/next-auth",
|
"repository": "https://github.com/nextauthjs/next-auth",
|
||||||
@@ -12,35 +12,44 @@
|
|||||||
"Nico Domino <yo@ndo.dev>",
|
"Nico Domino <yo@ndo.dev>",
|
||||||
"Alex Meuer <github@alexmeuer.com>"
|
"Alex Meuer <github@alexmeuer.com>"
|
||||||
],
|
],
|
||||||
"main": "dist/index.js",
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"import": "./index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"src",
|
||||||
"index.d.ts"
|
"*.js",
|
||||||
|
"*.d.ts*"
|
||||||
],
|
],
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"next-auth",
|
"next-auth",
|
||||||
"next.js",
|
"next.js",
|
||||||
"firebase"
|
"firebase",
|
||||||
|
"firebase-admin"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"dev": "tsc -w",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase --token '$FIREBASE_TOKEN' emulators:exec --only firestore --project next-auth-test jest"
|
"test": "firebase emulators:exec --only firestore --project next-auth-test 'jest -c tests/jest.config.js'"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"firebase": "^9.7.0",
|
"firebase-admin": "^11.4.1",
|
||||||
"next-auth": "^4"
|
"next-auth": "^4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next-auth/adapter-test": "workspace:*",
|
"@next-auth/adapter-test": "workspace:*",
|
||||||
"@next-auth/tsconfig": "workspace:*",
|
"@next-auth/tsconfig": "workspace:*",
|
||||||
"firebase": "^9.14.0",
|
"firebase-admin": "^11.4.1",
|
||||||
"firebase-tools": "^11.16.1",
|
"firebase-tools": "^11.16.1",
|
||||||
"jest": "^27.4.3",
|
"jest": "^29.3.1",
|
||||||
"next-auth": "workspace:*"
|
"next-auth": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { Timestamp } from "firebase/firestore"
|
|
||||||
import type {
|
|
||||||
FirestoreDataConverter,
|
|
||||||
QueryDocumentSnapshot,
|
|
||||||
WithFieldValue,
|
|
||||||
} from "firebase/firestore"
|
|
||||||
|
|
||||||
const isTimestamp = (value: unknown): value is Timestamp =>
|
|
||||||
typeof value === "object" && value !== null && value instanceof Timestamp
|
|
||||||
|
|
||||||
interface GetConverterOptions {
|
|
||||||
excludeId?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getConverter = <Document extends Record<string, unknown>>(
|
|
||||||
options?: GetConverterOptions
|
|
||||||
): FirestoreDataConverter<Document> => ({
|
|
||||||
// `PartialWithFieldValue` implicitly types `object` as `any`, so we want to explicitly type it
|
|
||||||
toFirestore(object: WithFieldValue<Document>) {
|
|
||||||
const document: Record<string, unknown> = {}
|
|
||||||
|
|
||||||
Object.keys(object).forEach((key) => {
|
|
||||||
if (object[key] !== undefined) {
|
|
||||||
document[key] = object[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return document
|
|
||||||
},
|
|
||||||
// We need to explicitly type `snapshot` since it uses `DocumentData` for generic type
|
|
||||||
fromFirestore(snapshot: QueryDocumentSnapshot<Document>) {
|
|
||||||
if (!snapshot.exists()) {
|
|
||||||
return snapshot
|
|
||||||
}
|
|
||||||
|
|
||||||
let document: Document = snapshot.data()
|
|
||||||
|
|
||||||
if (!options?.excludeId) {
|
|
||||||
document = {
|
|
||||||
...document,
|
|
||||||
id: snapshot.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in document) {
|
|
||||||
const value = document[key]
|
|
||||||
|
|
||||||
if (isTimestamp(value)) {
|
|
||||||
document = {
|
|
||||||
...document,
|
|
||||||
[key]: value.toDate(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return document
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { initializeApp, getApps, FirebaseOptions } from "firebase/app"
|
|
||||||
|
|
||||||
export default function getFirebase(firebaseOptions: FirebaseOptions) {
|
|
||||||
const apps = getApps()
|
|
||||||
const app = apps.find((app) => app.name === firebaseOptions.projectId)
|
|
||||||
if (app) {
|
|
||||||
return app
|
|
||||||
} else {
|
|
||||||
return initializeApp(firebaseOptions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,283 +1,302 @@
|
|||||||
import { initializeApp } from "firebase/app"
|
/**
|
||||||
import type { FirebaseOptions } from "firebase/app"
|
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||||
|
* <span>
|
||||||
|
* Official <b>Firebase</b> adapter for Auth.js / NextAuth.js,
|
||||||
|
* using the <a href="https://firebase.google.com/docs/admin/setup">Firebase Admin SDK</a>
|
||||||
|
* and <a href="https://firebase.google.com/docs/firestore">Firestore</a>.</span>
|
||||||
|
* <a href="https://firebase.google.com/">
|
||||||
|
* <img style={{display: "block"}} src="https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/adapter-firebase/logo.svg" height="48" width="48"/>
|
||||||
|
* </a>
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* ## Installation
|
||||||
|
*
|
||||||
|
* ```bash npm2yarn2pnpm
|
||||||
|
* npm install next-auth @next-auth/firebase-admin-adapter firebase-admin
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ## References
|
||||||
|
* - [`GOOGLE_APPLICATION_CREDENTIALS` environment variable](https://cloud.google.com/docs/authentication/application-default-credentials#GAC)
|
||||||
|
* - [Firebase Admin SDK setup](https://firebase.google.com/docs/admin/setup#initialize-sdk)
|
||||||
|
*
|
||||||
|
* @module @next-auth/firebase-adapter
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type AppOptions } from "firebase-admin"
|
||||||
|
import { Firestore } from "firebase-admin/firestore"
|
||||||
|
|
||||||
|
import type { Adapter, AdapterUser } from "next-auth/adapters"
|
||||||
import {
|
import {
|
||||||
addDoc,
|
collestionsFactory,
|
||||||
collection,
|
deleteDocs,
|
||||||
deleteDoc,
|
initFirestore,
|
||||||
doc,
|
|
||||||
getDoc,
|
getDoc,
|
||||||
getDocs,
|
getOneDoc,
|
||||||
getFirestore,
|
mapFieldsFactory,
|
||||||
limit,
|
} from "./utils"
|
||||||
query,
|
|
||||||
runTransaction,
|
|
||||||
setDoc,
|
|
||||||
where,
|
|
||||||
connectFirestoreEmulator,
|
|
||||||
} from "firebase/firestore"
|
|
||||||
|
|
||||||
import type {
|
export { initFirestore } from "./utils"
|
||||||
Adapter,
|
|
||||||
AdapterUser,
|
|
||||||
AdapterAccount,
|
|
||||||
AdapterSession,
|
|
||||||
VerificationToken,
|
|
||||||
} from "next-auth/adapters"
|
|
||||||
|
|
||||||
import { getConverter } from "./converter"
|
/** Configure the Firebase Adapter. */
|
||||||
import getFirebase from "./getFirebase"
|
export interface FirebaseAdapterConfig extends AppOptions {
|
||||||
|
/**
|
||||||
export type IndexableObject = Record<string, unknown>
|
* The name of the app passed to {@link https://firebase.google.com/docs/reference/admin/node/firebase-admin.md#initializeapp `initializeApp()`}.
|
||||||
|
*/
|
||||||
export interface FirestoreAdapterOptions {
|
name?: string
|
||||||
emulator?: {
|
firestore?: Firestore
|
||||||
host?: string
|
/**
|
||||||
port?: number
|
* Use this option if mixed `snake_case` and `camelCase` field names in the database is an issue for you.
|
||||||
}
|
* Passing `snake_case` will convert all field and collection names to `snake_case`.
|
||||||
|
* E.g. the collection `verificationTokens` will be `verification_tokens`,
|
||||||
|
* and fields like `emailVerified` will be `email_verified` instead.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts title="pages/api/auth/[...nextauth].ts"
|
||||||
|
* import NextAuth from "next-auth"
|
||||||
|
* import { FirestoreAdapter } from "@next-auth/firebase-adapter"
|
||||||
|
*
|
||||||
|
* export default NextAuth({
|
||||||
|
* adapter: FirestoreAdapter({ namingStrategy: "snake_case" })
|
||||||
|
* // ...
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
namingStrategy?: "snake_case"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FirestoreAdapter({
|
/**
|
||||||
emulator,
|
* #### Usage
|
||||||
...firebaseOptions
|
*
|
||||||
}: FirebaseOptions & FirestoreAdapterOptions): Adapter {
|
* First, create a Firebase project and generate a service account key.
|
||||||
const firebaseApp = getFirebase(firebaseOptions)
|
* Visit: `https://console.firebase.google.com/u/0/project/{project-id}/settings/serviceaccounts/adminsdk` (replace `{project-id}` with your project's id)
|
||||||
const db = getFirestore(firebaseApp)
|
*
|
||||||
|
* Now you have a few options to authenticate with the Firebase Admin SDK in your app:
|
||||||
|
*
|
||||||
|
* ##### 1. `GOOGLE_APPLICATION_CREDENTIALS` environment variable:
|
||||||
|
* - Download the service account key and save it in your project. (Make sure to add the file to your `.gitignore`!)
|
||||||
|
* - Add [`GOOGLE_APPLICATION_CREDENTIALS`](https://cloud.google.com/docs/authentication/application-default-credentials#GAC) to your environment variables and point it to the service account key file.
|
||||||
|
* - The adapter will automatically pick up the environment variable and use it to authenticate with the Firebase Admin SDK.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts title="pages/api/auth/[...nextauth].ts"
|
||||||
|
* import NextAuth from "next-auth"
|
||||||
|
* import { FirestoreAdapter } from "@next-auth/firebase-adapter"
|
||||||
|
*
|
||||||
|
* export default NextAuth({
|
||||||
|
* adapter: FirestoreAdapter(),
|
||||||
|
* // ...
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ##### 2. Service account values as environment variables
|
||||||
|
*
|
||||||
|
* - Download the service account key to a temporary location. (Make sure to not commit this file to your repository!)
|
||||||
|
* - Add the following environment variables to your project: `FIREBASE_PROJECT_ID`, `FIREBASE_CLIENT_EMAIL`, `FIREBASE_PRIVATE_KEY`.
|
||||||
|
* - Pass the config to the adapter, using the environment variables as shown in the example below.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts title="pages/api/auth/[...nextauth].ts"
|
||||||
|
* import NextAuth from "next-auth"
|
||||||
|
* import { FirestoreAdapter } from "@next-auth/firebase-adapter"
|
||||||
|
* import { cert } from "firebase-admin/app"
|
||||||
|
*
|
||||||
|
* export default NextAuth({
|
||||||
|
* adapter: FirestoreAdapter({
|
||||||
|
* credential: cert({
|
||||||
|
* projectId: process.env.FIREBASE_PROJECT_ID,
|
||||||
|
* clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
||||||
|
* privateKey: process.env.FIREBASE_PRIVATE_KEY,
|
||||||
|
* })
|
||||||
|
* })
|
||||||
|
* // ...
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ##### 3. Use an existing Firestore instance
|
||||||
|
*
|
||||||
|
* If you already have a Firestore instance, you can pass that to the adapter directly instead.
|
||||||
|
*
|
||||||
|
* :::note
|
||||||
|
* When passing an instance and in a serverless environment, remember to handle duplicate app initialization.
|
||||||
|
* :::
|
||||||
|
*
|
||||||
|
* :::tip
|
||||||
|
* You can use the {@link initFirestore} utility to initialize the app and get an instance safely.
|
||||||
|
* :::
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts title="pages/api/auth/[...nextauth].ts"
|
||||||
|
* import NextAuth from "next-auth"
|
||||||
|
* import { FirestoreAdapter } from "@next-auth/firebase-adapter"
|
||||||
|
* import { firestore } from "lib/firestore"
|
||||||
|
*
|
||||||
|
* export default NextAuth({
|
||||||
|
* adapter: FirestoreAdapter(firestore),
|
||||||
|
* // ...
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function FirestoreAdapter(
|
||||||
|
config?: FirebaseAdapterConfig | Firestore
|
||||||
|
): Adapter {
|
||||||
|
const { db, namingStrategy = "default" } =
|
||||||
|
config instanceof Firestore
|
||||||
|
? { db: config }
|
||||||
|
: { ...config, db: config?.firestore ?? initFirestore(config) }
|
||||||
|
|
||||||
if (emulator) {
|
const preferSnakeCase = namingStrategy === "snake_case"
|
||||||
connectFirestoreEmulator(
|
const C = collestionsFactory(db, preferSnakeCase)
|
||||||
db,
|
const mapper = mapFieldsFactory(preferSnakeCase)
|
||||||
emulator?.host ?? "localhost",
|
|
||||||
emulator?.port ?? 3001
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Users = collection(db, "users").withConverter(
|
|
||||||
getConverter<AdapterUser & IndexableObject>()
|
|
||||||
)
|
|
||||||
const Sessions = collection(db, "sessions").withConverter(
|
|
||||||
getConverter<AdapterSession & IndexableObject>()
|
|
||||||
)
|
|
||||||
const Accounts = collection(db, "accounts").withConverter(
|
|
||||||
getConverter<AdapterAccount>()
|
|
||||||
)
|
|
||||||
const VerificationTokens = collection(db, "verificationTokens").withConverter(
|
|
||||||
getConverter<VerificationToken & IndexableObject>({ excludeId: true })
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async createUser(newUser) {
|
async createUser(userInit) {
|
||||||
const userRef = await addDoc(Users, newUser)
|
const { id: userId } = await C.users.add(userInit as AdapterUser)
|
||||||
const userSnapshot = await getDoc(userRef)
|
|
||||||
|
|
||||||
if (userSnapshot.exists() && Users.converter) {
|
const user = await getDoc(C.users.doc(userId))
|
||||||
return Users.converter.fromFirestore(userSnapshot)
|
if (!user) throw new Error("[createUser] Failed to fetch created user")
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("[createUser] Failed to create user")
|
return user
|
||||||
},
|
},
|
||||||
|
|
||||||
async getUser(id) {
|
async getUser(id) {
|
||||||
const userSnapshot = await getDoc(doc(Users, id))
|
return await getDoc(C.users.doc(id))
|
||||||
|
|
||||||
if (userSnapshot.exists() && Users.converter) {
|
|
||||||
return Users.converter.fromFirestore(userSnapshot)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async getUserByEmail(email) {
|
async getUserByEmail(email) {
|
||||||
const userQuery = query(Users, where("email", "==", email), limit(1))
|
return await getOneDoc(C.users.where("email", "==", email))
|
||||||
const userSnapshots = await getDocs(userQuery)
|
|
||||||
const userSnapshot = userSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (userSnapshot?.exists() && Users.converter) {
|
|
||||||
return Users.converter.fromFirestore(userSnapshot)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async getUserByAccount({ provider, providerAccountId }) {
|
async getUserByAccount({ provider, providerAccountId }) {
|
||||||
const accountQuery = query(
|
const account = await getOneDoc(
|
||||||
Accounts,
|
C.accounts
|
||||||
where("provider", "==", provider),
|
.where("provider", "==", provider)
|
||||||
where("providerAccountId", "==", providerAccountId),
|
.where(mapper.toDb("providerAccountId"), "==", providerAccountId)
|
||||||
limit(1)
|
|
||||||
)
|
)
|
||||||
const accountSnapshots = await getDocs(accountQuery)
|
if (!account) return null
|
||||||
const accountSnapshot = accountSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (accountSnapshot?.exists()) {
|
return await getDoc(C.users.doc(account.userId))
|
||||||
const { userId } = accountSnapshot.data()
|
|
||||||
const userDoc = await getDoc(doc(Users, userId))
|
|
||||||
|
|
||||||
if (userDoc.exists() && Users.converter) {
|
|
||||||
return Users.converter.fromFirestore(userDoc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateUser(partialUser) {
|
async updateUser(partialUser) {
|
||||||
const userRef = doc(Users, partialUser.id)
|
if (!partialUser.id) throw new Error("[updateUser] Missing id")
|
||||||
|
|
||||||
await setDoc(userRef, partialUser, { merge: true })
|
const userRef = C.users.doc(partialUser.id)
|
||||||
|
|
||||||
const userSnapshot = await getDoc(userRef)
|
await userRef.set(partialUser, { merge: true })
|
||||||
|
|
||||||
if (userSnapshot.exists() && Users.converter) {
|
const user = await getDoc(userRef)
|
||||||
return Users.converter.fromFirestore(userSnapshot)
|
if (!user) throw new Error("[updateUser] Failed to fetch updated user")
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("[updateUser] Failed to update user")
|
return user
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteUser(userId) {
|
async deleteUser(userId) {
|
||||||
const userRef = doc(Users, userId)
|
await db.runTransaction(async (transaction) => {
|
||||||
const accountsQuery = query(Accounts, where("userId", "==", userId))
|
const accounts = await C.accounts
|
||||||
const sessionsQuery = query(Sessions, where("userId", "==", userId))
|
.where(mapper.toDb("userId"), "==", userId)
|
||||||
|
.get()
|
||||||
|
const sessions = await C.sessions
|
||||||
|
.where(mapper.toDb("userId"), "==", userId)
|
||||||
|
.get()
|
||||||
|
|
||||||
// TODO: May be better to use events instead of transactions?
|
transaction.delete(C.users.doc(userId))
|
||||||
await runTransaction(db, async (transaction) => {
|
|
||||||
const accounts = await getDocs(accountsQuery)
|
|
||||||
const sessions = await getDocs(sessionsQuery)
|
|
||||||
|
|
||||||
transaction.delete(userRef)
|
|
||||||
accounts.forEach((account) => transaction.delete(account.ref))
|
accounts.forEach((account) => transaction.delete(account.ref))
|
||||||
sessions.forEach((session) => transaction.delete(session.ref))
|
sessions.forEach((session) => transaction.delete(session.ref))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async linkAccount(account) {
|
async linkAccount(accountInit) {
|
||||||
const accountRef = await addDoc(Accounts, account)
|
const ref = await C.accounts.add(accountInit)
|
||||||
const accountSnapshot = await getDoc(accountRef)
|
const account = await ref.get().then((doc) => doc.data())
|
||||||
|
return account ?? null
|
||||||
if (accountSnapshot.exists() && Accounts.converter) {
|
|
||||||
return Accounts.converter.fromFirestore(accountSnapshot)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async unlinkAccount({ provider, providerAccountId }) {
|
async unlinkAccount({ provider, providerAccountId }) {
|
||||||
const accountQuery = query(
|
await deleteDocs(
|
||||||
Accounts,
|
C.accounts
|
||||||
where("provider", "==", provider),
|
.where("provider", "==", provider)
|
||||||
where("providerAccountId", "==", providerAccountId),
|
.where(mapper.toDb("providerAccountId"), "==", providerAccountId)
|
||||||
limit(1)
|
.limit(1)
|
||||||
)
|
)
|
||||||
const accountSnapshots = await getDocs(accountQuery)
|
|
||||||
const accountSnapshot = accountSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (accountSnapshot?.exists()) {
|
|
||||||
await deleteDoc(accountSnapshot.ref)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async createSession(session) {
|
async createSession(sessionInit) {
|
||||||
const sessionRef = await addDoc(Sessions, session)
|
const ref = await C.sessions.add(sessionInit)
|
||||||
const sessionSnapshot = await getDoc(sessionRef)
|
const session = await ref.get().then((doc) => doc.data())
|
||||||
|
|
||||||
if (sessionSnapshot.exists() && Sessions.converter) {
|
if (session) return session ?? null
|
||||||
return Sessions.converter.fromFirestore(sessionSnapshot)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("[createSession] Failed to create session")
|
throw new Error("[createSession] Failed to fetch created session")
|
||||||
},
|
},
|
||||||
|
|
||||||
async getSessionAndUser(sessionToken) {
|
async getSessionAndUser(sessionToken) {
|
||||||
const sessionQuery = query(
|
const session = await getOneDoc(
|
||||||
Sessions,
|
C.sessions.where(mapper.toDb("sessionToken"), "==", sessionToken)
|
||||||
where("sessionToken", "==", sessionToken),
|
|
||||||
limit(1)
|
|
||||||
)
|
)
|
||||||
const sessionSnapshots = await getDocs(sessionQuery)
|
if (!session) return null
|
||||||
const sessionSnapshot = sessionSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (sessionSnapshot?.exists() && Sessions.converter) {
|
const user = await getDoc(C.users.doc(session.userId))
|
||||||
const session = Sessions.converter.fromFirestore(sessionSnapshot)
|
if (!user) return null
|
||||||
const userDoc = await getDoc(doc(Users, session.userId))
|
|
||||||
|
|
||||||
if (userDoc.exists() && Users.converter) {
|
return { session, user }
|
||||||
const user = Users.converter.fromFirestore(userDoc)
|
|
||||||
|
|
||||||
return { session, user }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateSession(partialSession) {
|
async updateSession(partialSession) {
|
||||||
const sessionQuery = query(
|
const sessionId = await db.runTransaction(async (transaction) => {
|
||||||
Sessions,
|
const sessionSnapshot = (
|
||||||
where("sessionToken", "==", partialSession.sessionToken),
|
await transaction.get(
|
||||||
limit(1)
|
C.sessions
|
||||||
)
|
.where(
|
||||||
const sessionSnapshots = await getDocs(sessionQuery)
|
mapper.toDb("sessionToken"),
|
||||||
const sessionSnapshot = sessionSnapshots.docs[0]
|
"==",
|
||||||
|
partialSession.sessionToken
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
).docs[0]
|
||||||
|
if (!sessionSnapshot?.exists) return null
|
||||||
|
|
||||||
if (sessionSnapshot?.exists()) {
|
transaction.set(sessionSnapshot.ref, partialSession, { merge: true })
|
||||||
await setDoc(sessionSnapshot.ref, partialSession, { merge: true })
|
|
||||||
|
|
||||||
const sessionDoc = await getDoc(sessionSnapshot.ref)
|
return sessionSnapshot.id
|
||||||
|
})
|
||||||
|
|
||||||
if (sessionDoc?.exists() && Sessions.converter) {
|
if (!sessionId) return null
|
||||||
const session = Sessions.converter.fromFirestore(sessionDoc)
|
|
||||||
|
|
||||||
return session
|
const session = await getDoc(C.sessions.doc(sessionId))
|
||||||
}
|
if (session) return session
|
||||||
}
|
throw new Error("[updateSession] Failed to fetch updated session")
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteSession(sessionToken) {
|
async deleteSession(sessionToken) {
|
||||||
const sessionQuery = query(
|
await deleteDocs(
|
||||||
Sessions,
|
C.sessions
|
||||||
where("sessionToken", "==", sessionToken),
|
.where(mapper.toDb("sessionToken"), "==", sessionToken)
|
||||||
limit(1)
|
.limit(1)
|
||||||
)
|
)
|
||||||
const sessionSnapshots = await getDocs(sessionQuery)
|
|
||||||
const sessionSnapshot = sessionSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (sessionSnapshot?.exists()) {
|
|
||||||
await deleteDoc(sessionSnapshot.ref)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async createVerificationToken(verificationToken) {
|
async createVerificationToken(verificationToken) {
|
||||||
const verificationTokenRef = await addDoc(
|
await C.verification_tokens.add(verificationToken)
|
||||||
VerificationTokens,
|
return verificationToken
|
||||||
verificationToken
|
|
||||||
)
|
|
||||||
const verificationTokenSnapshot = await getDoc(verificationTokenRef)
|
|
||||||
|
|
||||||
if (verificationTokenSnapshot.exists() && VerificationTokens.converter) {
|
|
||||||
const { id, ...verificationToken } =
|
|
||||||
VerificationTokens.converter.fromFirestore(verificationTokenSnapshot)
|
|
||||||
|
|
||||||
return verificationToken
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async useVerificationToken({ identifier, token }) {
|
async useVerificationToken({ identifier, token }) {
|
||||||
const verificationTokensQuery = query(
|
const verificationTokenSnapshot = (
|
||||||
VerificationTokens,
|
await C.verification_tokens
|
||||||
where("identifier", "==", identifier),
|
.where("identifier", "==", identifier)
|
||||||
where("token", "==", token),
|
.where("token", "==", token)
|
||||||
limit(1)
|
.limit(1)
|
||||||
)
|
.get()
|
||||||
const verificationTokenSnapshots = await getDocs(verificationTokensQuery)
|
).docs[0]
|
||||||
const verificationTokenSnapshot = verificationTokenSnapshots.docs[0]
|
|
||||||
|
|
||||||
if (verificationTokenSnapshot?.exists() && VerificationTokens.converter) {
|
if (!verificationTokenSnapshot) return null
|
||||||
await deleteDoc(verificationTokenSnapshot.ref)
|
|
||||||
|
|
||||||
const { id, ...verificationToken } =
|
const data = verificationTokenSnapshot.data()
|
||||||
VerificationTokens.converter.fromFirestore(verificationTokenSnapshot)
|
await verificationTokenSnapshot.ref.delete()
|
||||||
|
return data
|
||||||
return verificationToken
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
168
packages/adapter-firebase/src/utils.ts
Normal file
168
packages/adapter-firebase/src/utils.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { AppOptions, getApps, initializeApp } from "firebase-admin/app"
|
||||||
|
|
||||||
|
import {
|
||||||
|
getFirestore,
|
||||||
|
initializeFirestore,
|
||||||
|
Timestamp,
|
||||||
|
} from "firebase-admin/firestore"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AdapterUser,
|
||||||
|
AdapterAccount,
|
||||||
|
AdapterSession,
|
||||||
|
VerificationToken,
|
||||||
|
} from "next-auth/adapters"
|
||||||
|
import { FirebaseAdapterConfig } from "."
|
||||||
|
|
||||||
|
// for consistency, store all fields as snake_case in the database
|
||||||
|
const MAP_TO_FIRESTORE: Record<string, string | undefined> = {
|
||||||
|
userId: "user_id",
|
||||||
|
sessionToken: "session_token",
|
||||||
|
providerAccountId: "provider_account_id",
|
||||||
|
emailVerified: "email_verified",
|
||||||
|
}
|
||||||
|
const MAP_FROM_FIRESTORE: Record<string, string | undefined> = {}
|
||||||
|
|
||||||
|
for (const key in MAP_TO_FIRESTORE) {
|
||||||
|
MAP_FROM_FIRESTORE[MAP_TO_FIRESTORE[key]!] = key
|
||||||
|
}
|
||||||
|
|
||||||
|
const identity = <T>(x: T) => x
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function mapFieldsFactory(preferSnakeCase?: boolean) {
|
||||||
|
if (preferSnakeCase) {
|
||||||
|
return {
|
||||||
|
toDb: (field: string) => MAP_TO_FIRESTORE[field] ?? field,
|
||||||
|
fromDb: (field: string) => MAP_FROM_FIRESTORE[field] ?? field,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { toDb: identity, fromDb: identity }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function getConverter<Document extends Record<string, any>>(options: {
|
||||||
|
excludeId?: boolean
|
||||||
|
preferSnakeCase?: boolean
|
||||||
|
}): FirebaseFirestore.FirestoreDataConverter<Document> {
|
||||||
|
const mapper = mapFieldsFactory(options?.preferSnakeCase ?? false)
|
||||||
|
|
||||||
|
return {
|
||||||
|
toFirestore(object) {
|
||||||
|
const document: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
for (const key in object) {
|
||||||
|
if (key === "id") continue
|
||||||
|
const value = object[key]
|
||||||
|
if (value !== undefined) {
|
||||||
|
document[mapper.toDb(key)] = value
|
||||||
|
} else {
|
||||||
|
console.warn(`FirebaseAdapter: value for key "${key}" is undefined`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return document
|
||||||
|
},
|
||||||
|
|
||||||
|
fromFirestore(
|
||||||
|
snapshot: FirebaseFirestore.QueryDocumentSnapshot<Document>
|
||||||
|
): Document {
|
||||||
|
const document = snapshot.data()! // we can guarentee it exists
|
||||||
|
|
||||||
|
const object: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
if (!options?.excludeId) {
|
||||||
|
object.id = snapshot.id
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in document) {
|
||||||
|
let value: any = document[key]
|
||||||
|
if (value instanceof Timestamp) value = value.toDate()
|
||||||
|
|
||||||
|
object[mapper.fromDb(key)] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return object as Document
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export async function getOneDoc<T>(
|
||||||
|
querySnapshot: FirebaseFirestore.Query<T>
|
||||||
|
): Promise<T | null> {
|
||||||
|
const querySnap = await querySnapshot.limit(1).get()
|
||||||
|
return querySnap.docs[0]?.data() ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export async function deleteDocs<T>(
|
||||||
|
querySnapshot: FirebaseFirestore.Query<T>
|
||||||
|
): Promise<void> {
|
||||||
|
const querySnap = await querySnapshot.get()
|
||||||
|
for (const doc of querySnap.docs) {
|
||||||
|
await doc.ref.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export async function getDoc<T>(
|
||||||
|
docRef: FirebaseFirestore.DocumentReference<T>
|
||||||
|
): Promise<T | null> {
|
||||||
|
const docSnap = await docRef.get()
|
||||||
|
return docSnap.data() ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
export function collestionsFactory(
|
||||||
|
db: FirebaseFirestore.Firestore,
|
||||||
|
preferSnakeCase = false
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
users: db
|
||||||
|
.collection("users")
|
||||||
|
.withConverter(getConverter<AdapterUser>({ preferSnakeCase })),
|
||||||
|
sessions: db
|
||||||
|
.collection("sessions")
|
||||||
|
.withConverter(getConverter<AdapterSession>({ preferSnakeCase })),
|
||||||
|
accounts: db
|
||||||
|
.collection("accounts")
|
||||||
|
.withConverter(getConverter<AdapterAccount>({ preferSnakeCase })),
|
||||||
|
verification_tokens: db
|
||||||
|
.collection(
|
||||||
|
preferSnakeCase ? "verification_tokens" : "verificationTokens"
|
||||||
|
)
|
||||||
|
.withConverter(
|
||||||
|
getConverter<VerificationToken>({ preferSnakeCase, excludeId: true })
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function that helps making sure that there is no duplicate app initialization issues in serverless environments.
|
||||||
|
* If no parameter is passed, it will use the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to initialize a Firestore instance.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts title="lib/firestore.ts"
|
||||||
|
* import { initFirestore } from "@next-auth/firebase-adapter"
|
||||||
|
* import { cert } from "firebase-admin/app"
|
||||||
|
*
|
||||||
|
* export const firestore = initFirestore({
|
||||||
|
* credential: cert({
|
||||||
|
* projectId: process.env.FIREBASE_PROJECT_ID,
|
||||||
|
* clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
||||||
|
* privateKey: process.env.FIREBASE_PRIVATE_KEY,
|
||||||
|
* })
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function initFirestore(
|
||||||
|
options: AppOptions & { name?: FirebaseAdapterConfig["name"] } = {}
|
||||||
|
) {
|
||||||
|
const apps = getApps()
|
||||||
|
const app = options.name ? apps.find((a) => a.name === options.name) : apps[0]
|
||||||
|
|
||||||
|
if (app) return getFirestore(app)
|
||||||
|
|
||||||
|
return initializeFirestore(initializeApp(options, options.name))
|
||||||
|
}
|
||||||
@@ -1,118 +1,57 @@
|
|||||||
import { runBasicTests } from "@next-auth/adapter-test"
|
import { runBasicTests } from "@next-auth/adapter-test"
|
||||||
import { FirestoreAdapter } from "../src"
|
|
||||||
|
|
||||||
|
import { FirestoreAdapter, type FirebaseAdapterConfig } from "../src"
|
||||||
import {
|
import {
|
||||||
getFirestore,
|
collestionsFactory,
|
||||||
connectFirestoreEmulator,
|
initFirestore,
|
||||||
terminate,
|
|
||||||
collection,
|
|
||||||
query,
|
|
||||||
where,
|
|
||||||
limit,
|
|
||||||
getDocs,
|
|
||||||
getDoc,
|
getDoc,
|
||||||
doc,
|
getOneDoc,
|
||||||
} from "firebase/firestore"
|
mapFieldsFactory,
|
||||||
import { initializeApp } from "firebase/app"
|
} from "../src/utils"
|
||||||
import { getConverter } from "../src/converter"
|
|
||||||
import type {
|
|
||||||
AdapterSession,
|
|
||||||
AdapterUser,
|
|
||||||
VerificationToken,
|
|
||||||
} from "next-auth/adapters"
|
|
||||||
import type { Account } from "next-auth"
|
|
||||||
|
|
||||||
const app = initializeApp({ projectId: "next-auth-test" })
|
describe.each([
|
||||||
const firestore = getFirestore(app)
|
{ namingStrategy: "snake_case" },
|
||||||
|
{ namingStrategy: "default" },
|
||||||
|
] as Partial<FirebaseAdapterConfig>[])(
|
||||||
|
"FirebaseAdapter with config: %s",
|
||||||
|
(config) => {
|
||||||
|
config.name = `next-auth-test-${config.namingStrategy}`
|
||||||
|
config.projectId = "next-auth-test"
|
||||||
|
config.databaseURL = "http://localhost:8080"
|
||||||
|
|
||||||
connectFirestoreEmulator(firestore, "localhost", 8080)
|
const db = initFirestore(config)
|
||||||
|
const preferSnakeCase = config.namingStrategy === "snake_case"
|
||||||
|
const mapper = mapFieldsFactory(preferSnakeCase)
|
||||||
|
const C = collestionsFactory(db, preferSnakeCase)
|
||||||
|
|
||||||
type IndexableObject = Record<string, unknown>
|
for (const [name, collection] of Object.entries(C)) {
|
||||||
|
test(`collection "${name}" should be empty`, async () => {
|
||||||
|
expect((await collection.count().get()).data().count).toBe(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const Users = collection(firestore, "users").withConverter(
|
runBasicTests({
|
||||||
getConverter<AdapterUser & IndexableObject>()
|
adapter: FirestoreAdapter(config),
|
||||||
|
db: {
|
||||||
|
disconnect: async () => await db.terminate(),
|
||||||
|
session: (sessionToken) =>
|
||||||
|
getOneDoc(
|
||||||
|
C.sessions.where(mapper.toDb("sessionToken"), "==", sessionToken)
|
||||||
|
),
|
||||||
|
user: (userId) => getDoc(C.users.doc(userId)),
|
||||||
|
account: ({ provider, providerAccountId }) =>
|
||||||
|
getOneDoc(
|
||||||
|
C.accounts
|
||||||
|
.where("provider", "==", provider)
|
||||||
|
.where(mapper.toDb("providerAccountId"), "==", providerAccountId)
|
||||||
|
),
|
||||||
|
verificationToken: ({ identifier, token }) =>
|
||||||
|
getOneDoc(
|
||||||
|
C.verification_tokens
|
||||||
|
.where("identifier", "==", identifier)
|
||||||
|
.where("token", "==", token)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
)
|
)
|
||||||
const Sessions = collection(firestore, "sessions").withConverter(
|
|
||||||
getConverter<AdapterSession & IndexableObject>()
|
|
||||||
)
|
|
||||||
const Accounts = collection(firestore, "accounts").withConverter(
|
|
||||||
getConverter<Account>()
|
|
||||||
)
|
|
||||||
const VerificationTokens = collection(
|
|
||||||
firestore,
|
|
||||||
"verificationTokens"
|
|
||||||
).withConverter(
|
|
||||||
getConverter<VerificationToken & IndexableObject>({ excludeId: true })
|
|
||||||
)
|
|
||||||
|
|
||||||
runBasicTests({
|
|
||||||
adapter: FirestoreAdapter({ projectId: "next-auth-test" }),
|
|
||||||
db: {
|
|
||||||
async disconnect() {
|
|
||||||
await terminate(firestore)
|
|
||||||
},
|
|
||||||
async session(sessionToken) {
|
|
||||||
const snapshotQuery = query(
|
|
||||||
Sessions,
|
|
||||||
where("sessionToken", "==", sessionToken),
|
|
||||||
limit(1)
|
|
||||||
)
|
|
||||||
const snapshots = await getDocs(snapshotQuery)
|
|
||||||
const snapshot = snapshots.docs[0]
|
|
||||||
|
|
||||||
if (snapshot?.exists() && Sessions.converter) {
|
|
||||||
const session = Sessions.converter.fromFirestore(snapshot)
|
|
||||||
|
|
||||||
return session
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
async user(id) {
|
|
||||||
const snapshot = await getDoc(doc(Users, id))
|
|
||||||
|
|
||||||
if (snapshot?.exists() && Users.converter) {
|
|
||||||
const user = Users.converter.fromFirestore(snapshot)
|
|
||||||
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
async account({ provider, providerAccountId }) {
|
|
||||||
const snapshotQuery = query(
|
|
||||||
Accounts,
|
|
||||||
where("provider", "==", provider),
|
|
||||||
where("providerAccountId", "==", providerAccountId),
|
|
||||||
limit(1)
|
|
||||||
)
|
|
||||||
const snapshots = await getDocs(snapshotQuery)
|
|
||||||
const snapshot = snapshots.docs[0]
|
|
||||||
|
|
||||||
if (snapshot?.exists() && Accounts.converter) {
|
|
||||||
const account = Accounts.converter.fromFirestore(snapshot)
|
|
||||||
|
|
||||||
return account
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
async verificationToken({ identifier, token }) {
|
|
||||||
const snapshotQuery = query(
|
|
||||||
VerificationTokens,
|
|
||||||
where("identifier", "==", identifier),
|
|
||||||
where("token", "==", token),
|
|
||||||
limit(1)
|
|
||||||
)
|
|
||||||
const snapshots = await getDocs(snapshotQuery)
|
|
||||||
const snapshot = snapshots.docs[0]
|
|
||||||
|
|
||||||
if (snapshot?.exists() && VerificationTokens.converter) {
|
|
||||||
const verificationToken =
|
|
||||||
VerificationTokens.converter.fromFirestore(snapshot)
|
|
||||||
|
|
||||||
return verificationToken
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|||||||
11
packages/adapter-firebase/tests/jest.config.js
Normal file
11
packages/adapter-firebase/tests/jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import config from "@next-auth/adapter-test/jest/jest-preset.js"
|
||||||
|
|
||||||
|
//TODO: update rest of the packages to Jest 29+
|
||||||
|
const {testURL, ...rest} = config
|
||||||
|
export default {
|
||||||
|
...rest,
|
||||||
|
testEnvironmentOptions: {
|
||||||
|
url: testURL
|
||||||
|
},
|
||||||
|
rootDir: ".."
|
||||||
|
}
|
||||||
@@ -1,11 +1,23 @@
|
|||||||
{
|
{
|
||||||
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src",
|
|
||||||
"outDir": "dist",
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"moduleResolution": "node"
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": ".",
|
||||||
|
"rootDir": "src",
|
||||||
|
"skipDefaultLibCheck": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"declaration": true
|
||||||
},
|
},
|
||||||
"exclude": ["tests", "dist", "jest.config.js"]
|
"include": [
|
||||||
}
|
"src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@auth/core",
|
"name": "@auth/core",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"description": "Authentication for the Web.",
|
"description": "Authentication for the Web.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"authentication",
|
"authentication",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"types": "./index.d.ts",
|
"types": "./index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"*.js",
|
"*.js",
|
||||||
"*.d.ts",
|
"*.d.ts*",
|
||||||
"lib",
|
"lib",
|
||||||
"providers",
|
"providers",
|
||||||
"src"
|
"src"
|
||||||
@@ -93,4 +93,4 @@
|
|||||||
"postcss": "8.4.19",
|
"postcss": "8.4.19",
|
||||||
"postcss-nested": "6.0.0"
|
"postcss-nested": "6.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
|
*
|
||||||
|
* :::warning Experimental
|
||||||
|
* `@auth/core` is under active development.
|
||||||
|
* :::
|
||||||
*
|
*
|
||||||
* This is the main entry point to the Auth.js library.
|
* This is the main entry point to the Auth.js library.
|
||||||
*
|
*
|
||||||
@@ -29,7 +33,7 @@
|
|||||||
* - [Getting started](https://authjs.dev/getting-started/introduction)
|
* - [Getting started](https://authjs.dev/getting-started/introduction)
|
||||||
* - [Most common use case guides](https://authjs.dev/guides)
|
* - [Most common use case guides](https://authjs.dev/guides)
|
||||||
*
|
*
|
||||||
* @module main
|
* @module index
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assertConfig } from "./lib/assert.js"
|
import { assertConfig } from "./lib/assert.js"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@auth/sveltekit",
|
"name": "@auth/sveltekit",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"description": "Authentication for SvelteKit.",
|
"description": "Authentication for SvelteKit.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"authentication",
|
"authentication",
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
* return {
|
* return {
|
||||||
* session: await event.locals.getSession()
|
* session: await event.locals.getSession()
|
||||||
* };
|
* };
|
||||||
* };
|
* };
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* What you return in the function `LayoutServerLoad` will be available inside the `$page` store, in the `data` property: `$page.data`.
|
* What you return in the function `LayoutServerLoad` will be available inside the `$page` store, in the `data` property: `$page.data`.
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
* return {};
|
* return {};
|
||||||
* };
|
* };
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* :::danger
|
* :::danger
|
||||||
* Make sure to ALWAYS grab the session information from the parent instead of using the store in the case of a `PageLoad`.
|
* Make sure to ALWAYS grab the session information from the parent instead of using the store in the case of a `PageLoad`.
|
||||||
* Not doing so can lead to users being able to incorrectly access protected information in the case the `+layout.server.ts` does not run for that page load.
|
* Not doing so can lead to users being able to incorrectly access protected information in the case the `+layout.server.ts` does not run for that page load.
|
||||||
@@ -130,14 +130,14 @@
|
|||||||
* The handle hook, available in `hooks.server.ts`, is a function that receives ALL requests sent to your SvelteKit webapp.
|
* The handle hook, available in `hooks.server.ts`, is a function that receives ALL requests sent to your SvelteKit webapp.
|
||||||
* You may intercept them inside the handle hook, add and modify things in the request, block requests, etc.
|
* You may intercept them inside the handle hook, add and modify things in the request, block requests, etc.
|
||||||
* Some readers may notice we are already using this handle hook for SvelteKitAuth which returns a handle itself, so we are going to use SvelteKit's sequence to provide middleware-like functions that set the handle hook.
|
* Some readers may notice we are already using this handle hook for SvelteKitAuth which returns a handle itself, so we are going to use SvelteKit's sequence to provide middleware-like functions that set the handle hook.
|
||||||
*
|
*
|
||||||
* ```ts
|
* ```ts
|
||||||
* import { SvelteKitAuth } from '@auth/sveltekit';
|
* import { SvelteKitAuth } from '@auth/sveltekit';
|
||||||
* import GitHub from '@auth/core/providers/github';
|
* import GitHub from '@auth/core/providers/github';
|
||||||
* import { GITHUB_ID, GITHUB_SECRET } from '$env/static/private';
|
* import { GITHUB_ID, GITHUB_SECRET } from '$env/static/private';
|
||||||
* import { redirect, type Handle } from '@sveltejs/kit';
|
* import { redirect, type Handle } from '@sveltejs/kit';
|
||||||
* import { sequence } from '@sveltejs/kit/hooks';
|
* import { sequence } from '@sveltejs/kit/hooks';
|
||||||
*
|
*
|
||||||
* async function authorization({ event, resolve }) {
|
* async function authorization({ event, resolve }) {
|
||||||
* // Protect any routes under /authenticated
|
* // Protect any routes under /authenticated
|
||||||
* if (event.url.pathname.startsWith('/authenticated')) {
|
* if (event.url.pathname.startsWith('/authenticated')) {
|
||||||
@@ -146,14 +146,14 @@
|
|||||||
* throw redirect(303, '/auth');
|
* throw redirect(303, '/auth');
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* // If the request is still here, just proceed as normally
|
* // If the request is still here, just proceed as normally
|
||||||
* const result = await resolve(event, {
|
* const result = await resolve(event, {
|
||||||
* transformPageChunk: ({ html }) => html
|
* transformPageChunk: ({ html }) => html
|
||||||
* });
|
* });
|
||||||
* return result;
|
* return result;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* // First handle authentication, then authorization
|
* // First handle authentication, then authorization
|
||||||
* // Each function acts as a middleware, receiving the request handle
|
* // Each function acts as a middleware, receiving the request handle
|
||||||
* // And returning a handle which gets passed to the next function
|
* // And returning a handle which gets passed to the next function
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
* PRs to improve this documentation are welcome! See [this file](https://github.com/nextauthjs/next-auth/blob/main/packages/frameworks-sveltekit/src/lib/index.ts).
|
* PRs to improve this documentation are welcome! See [this file](https://github.com/nextauthjs/next-auth/blob/main/packages/frameworks-sveltekit/src/lib/index.ts).
|
||||||
* :::
|
* :::
|
||||||
*
|
*
|
||||||
* @module main
|
* @module index
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference types="@sveltejs/kit" />
|
/// <reference types="@sveltejs/kit" />
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
diff --git a/dist/theme.js b/dist/theme.js
|
|
||||||
index 1483a4b4ec69583aa3086eac83b2b31ae8bb6777..c30e7a4f7785fc230099e8b904040dd4aa57c38e 100644
|
|
||||||
--- a/dist/theme.js
|
|
||||||
+++ b/dist/theme.js
|
|
||||||
@@ -221,12 +221,12 @@ class MarkdownTheme extends typedoc_1.Theme {
|
|
||||||
directory: 'enums',
|
|
||||||
template: this.getReflectionTemplate(),
|
|
||||||
},
|
|
||||||
- {
|
|
||||||
- kind: [typedoc_1.ReflectionKind.Class],
|
|
||||||
- isLeaf: false,
|
|
||||||
- directory: 'classes',
|
|
||||||
- template: this.getReflectionTemplate(),
|
|
||||||
- },
|
|
||||||
+ // {
|
|
||||||
+ // kind: [typedoc_1.ReflectionKind.Class],
|
|
||||||
+ // isLeaf: false,
|
|
||||||
+ // directory: 'classes',
|
|
||||||
+ // template: this.getReflectionTemplate(),
|
|
||||||
+ // },
|
|
||||||
{
|
|
||||||
kind: [typedoc_1.ReflectionKind.Interface],
|
|
||||||
isLeaf: false,
|
|
||||||
4227
pnpm-lock.yaml
generated
4227
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,10 @@
|
|||||||
},
|
},
|
||||||
"@next-auth/upstash-redis-adapter#test": {
|
"@next-auth/upstash-redis-adapter#test": {
|
||||||
"env": ["UPSTASH_REDIS_KEY", "UPSTASH_REDIS_URL"]
|
"env": ["UPSTASH_REDIS_KEY", "UPSTASH_REDIS_URL"]
|
||||||
|
},
|
||||||
|
"docs#dev": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"cache": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user