Compare commits

...

36 Commits

Author SHA1 Message Date
Balázs Orbán
190c5044be fix(ts): mark options provider config option internal 2023-01-30 13:31:48 +01:00
Balázs Orbán
b204710b5b chore(dev): use workspace modules in Svelte app 2023-01-30 13:29:20 +01:00
Balázs Orbán
533320eb94 chore: generate oauth-types on build 2023-01-29 14:33:41 +01:00
James
dfe6509472 fix: comment Discord profile, fix @auth/sveltekit build on Windows (#6550)
* fix: discord types were inaccurate

* fix: build on windows computers

* Apply review suggestions

* Update discord.ts

---------

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-01-29 12:05:14 +00:00
Oskar
1bde7cc8df fix(core): correct docs link (#6446) 2023-01-26 12:16:47 +00:00
Balázs Orbán
cef05d5e2d Merge branch 'main' of github.com:nextauthjs/next-auth 2023-01-26 13:11:14 +01:00
Balázs Orbán
c0dea283ba fix(core): avoid circular dependency
Fixes #6508
2023-01-26 13:11:04 +01:00
Balázs Orbán
0204766e0f fix(core): don't lock nodemailer version as peer dependency 2023-01-25 14:29:16 +00:00
Jérémie Sellam
a336ba762c fix(adapters): allow already initialized firebase app 🐛 (#6230)
* 🐛 Fix already initialized firebase app

* remove comment

Co-authored-by: Jérémie Sellam <jeremie@southpigalle.io>
Co-authored-by: Thang Vu <hi@thvu.dev>
2023-01-25 19:57:52 +07:00
Balázs Orbán
681d53c2f8 chore(core): cleanup 2023-01-24 14:01:40 +01:00
Thang Vu
06e891c0ea chore: cache output for @auth/sveltekit 2023-01-24 15:22:42 +07:00
GitHub Actions
b9a84350b5 chore(release): bump package version(s) [skip ci] 2023-01-24 02:02:00 +00:00
Balázs Orbán
44c38247da chore: trigger CI 2023-01-24 02:58:49 +01:00
Balázs Orbán
9b9af4d5e5 chore: bump versions [skip ci] 2023-01-24 02:56:31 +01:00
Balázs Orbán
fd2179bdca Merge branch 'main' of github.com:nextauthjs/next-auth 2023-01-24 02:42:16 +01:00
Balázs Orbán
7bb037bb9d chore: temp. disable E2E tests 2023-01-24 02:42:13 +01:00
Robin Panta
52f70e9f4f docs: update "guide deep-dive" link (#6473)
Fixes https://github.com/nextauthjs/next-auth/issues/6466
2023-01-24 02:34:45 +01:00
Balázs Orbán
505f69b519 chore: fix pipeline 2023-01-24 02:29:10 +01:00
Balázs Orbán
b21709db40 chore: update lock file 2023-01-24 02:26:49 +01:00
Balázs Orbán
aff7b37ef9 Merge branch 'main' of github.com:nextauthjs/next-auth 2023-01-24 02:26:00 +01:00
Balázs Orbán
daa85be1ad Revert "chore(next-auth): remove engines restriction (#6428)"
This reverts commit 035836da98.
2023-01-24 02:25:45 +01:00
Balázs Orbán
c31718ca10 fix(core): sign cookies with built-in jwt methods (#6488) 2023-01-24 02:21:56 +01:00
Balázs Orbán
fbcfedf0e8 fix(providers): default image to null for Azure AD 2023-01-24 02:21:27 +01:00
Balázs Orbán
bd032335eb docs: rename file 2023-01-23 13:41:21 +01:00
Balázs Orbán
128e0f3a10 docs: update RBAC guide 2023-01-23 13:40:45 +01:00
Thang Vu
557fb9d741 chore: ignore e2e actions in forks 2023-01-23 12:28:48 +07:00
Thang Vu
b4d6ed5f5f feat(providers): add Asgardeo provider (#6452)
* implement asgardeo auth provider

* Import asgardeo provider in to providers

* Improve provider configuration

* simplify and improve the asgardeo provider

* Delete package-lock.json

* converted server origin to organization

* revamp provider configs

* update profile interface

* Remove asgardeo issuer parameter and add docs

* fixed docs

* Update asgardeo.md

* Update docs and provider branding

* Remove mistakenly added code from dev app

* move to core

* Delete asgardeo.md

Co-authored-by: Yathindra <yathindrarawya123@gmail.com>
Co-authored-by: Yathindra Kodithuwakku <32919513+yathindrakodithuwakku@users.noreply.github.com>
Co-authored-by: Yathindra Kodithuwakku <32919513+yathindrak@users.noreply.github.com>
2023-01-22 01:30:42 +07:00
Richard Shin
035836da98 chore(next-auth): remove engines restriction (#6428)
* fix: add node 19 as compatible engine

* remove engines restriction

Co-authored-by: Thang Vu <hi@thvu.dev>
2023-01-21 16:29:45 +07:00
Atila Fassina
294039a497 docs(xata-adapter): adjust json schema (#6440) 2023-01-20 11:17:00 +00:00
Balázs Orbán
b2450ef625 fix(providers): conform Twitch provider to spec with escape hatch (#6365)
* fix(core): add explicit non-conform escape hatch

* fix(core): default to first supported auth method

* fix(core): stringify `claims` authorization url param

* fix(providers): conform Twitch provider to spec with escape hatch

* configure `client_secret_post` explicitly in provider
2023-01-19 10:28:14 +00:00
Balázs Orbán
a81bb3e51e feat(core): option to opt out of CSRF checks (#6379)
* feat(core): add way to opt-out of CSRF checks

* fix logic

* add warning if CSRF endpoint used when skipped
2023-01-19 10:27:18 +00:00
Robin Panta
bb506f7eb9 docs: Fix token expiry comparision in database strategy (#6430)
* Fix token expiry comparision in database strategy

fixes the condition used for example
in database strategy

* Apply suggestions from code review

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-01-18 15:26:23 +00:00
Thang Vu
87d9cc4244 feat: e2e tests (#6380)
* feat: e2e test init

* run e2e test on CI

* Add credentials to ci

* Update pnpm-lock.yaml

* move test to dev

* add dotenv

* remove in examples

* add e2e command

* revert

* add output cache for turbo e2e

* correct path for upload artifact

* Update release.yml
2023-01-18 19:43:50 +07:00
uatemycookie22
d2e3b76031 docs: Update 02-oauth-tutorial.mdx (#6408)
Fix typos in 02-oath-tutorial.mdx
2023-01-17 00:24:48 +01:00
Jan-David-Black
c36834b3b0 docs: Updating to _app.tsx (#6398)
file should be called `_app.tsx` instead of `_app.ts`
2023-01-17 00:24:04 +01:00
khuezy
8f7145801a feat(adapters): expose DynamoDB adapter options (#6370)
* feat: add dynmodb adaption options

* fix typo

* Apply suggestions from code review

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2023-01-12 09:59:18 +00:00
58 changed files with 1157 additions and 827 deletions

View File

@@ -25,6 +25,7 @@ body:
- "Custom provider"
- "42 School"
- "Apple"
- "Asgardeo"
- "Atlassian"
- "Auth0"
- "Authentik"

View File

@@ -35,6 +35,22 @@ jobs:
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# - name: Run E2E tests
# if: github.repository == 'nextauthjs/next-auth'
# run: pnpm e2e
# timeout-minutes: 15
# env:
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# - name: Upload E2E artifacts
# if: github.repository == 'nextauthjs/next-auth'
# uses: actions/upload-artifact@v3
# with:
# name: playwright-report
# path: apps/dev/nextjs/playwright-report/
# retention-days: 30
# - name: Coverage
# uses: codecov/codecov-action@v1
# with:

1
.gitignore vendored
View File

@@ -84,6 +84,7 @@ docs/providers.json
packages/core/*.js
packages/core/*.d.ts
packages/core/*.d.ts.map
packages/core/src/providers/oauth-types.ts
packages/core/lib
packages/core/providers
packages/core/src/lib/pages/styles.ts

View File

@@ -9,6 +9,10 @@ NEXTAUTH_URL=http://localhost:3000
# and/or verification tokens.
NEXTAUTH_SECRET=secret
ASGARDEO_CLIENT_ID=
ASGARDEO_CLIENT_SECRET=
ASGARDEO_ISSUER=
AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=

4
apps/dev/nextjs/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/

View File

@@ -9,10 +9,12 @@
"build": "next build",
"start": "next start",
"email": "fake-smtp-server",
"start:email": "pnpm email"
"start:email": "pnpm email",
"e2e": "pnpm dlx playwright test"
},
"license": "ISC",
"dependencies": {
"@auth/core": "workspace:*",
"@next-auth/fauna-adapter": "workspace:*",
"@next-auth/prisma-adapter": "workspace:*",
"@next-auth/supabase-adapter": "workspace:*",
@@ -22,15 +24,16 @@
"faunadb": "^4",
"next": "13.1.1",
"next-auth": "workspace:*",
"@auth/core": "workspace:*",
"nodemailer": "^6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@playwright/test": "1.29.2",
"@types/jsonwebtoken": "^8.5.5",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"dotenv": "^16.0.3",
"fake-smtp-server": "^0.8.0",
"pg": "^8.7.3",
"prisma": "^3",

View File

@@ -2,6 +2,7 @@ import { Auth, type AuthConfig } from "@auth/core"
// Providers
import Apple from "@auth/core/providers/apple"
import Asgardeo from "@auth/core/providers/asgardeo"
import Auth0 from "@auth/core/providers/auth0"
import AzureAD from "@auth/core/providers/azure-ad"
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
@@ -82,6 +83,7 @@ export const authConfig: AuthConfig = {
},
}),
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
AzureAD({
clientId: process.env.AZURE_AD_CLIENT_ID,

View File

@@ -0,0 +1,107 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
},
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
},
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// port: 3000,
// },
};
export default config;

View File

@@ -0,0 +1,39 @@
import { test, expect } from "@playwright/test"
test("Sign in with Auth0", async ({ page }) => {
// Go to NextAuth example app
await page.goto("https://next-auth-example.vercel.app")
// Click 'Sign In'
await page.click("#__next > header > div > p > a")
// Auth0 Login Provider
await page.click('body > div > div form[action*="auth0"] > button')
// Enter Credentials (Username/Password Login) on Auth0 Widget
await page.type("#username", process.env.AUTH0_USERNAME!)
await page.type("#password", process.env.AUTH0_PASSWORD!)
// Snap a screenshot
// await page.screenshot({ path: "1-auth0-login.png", fullPage: true })
// Press submit on Auth0 form
await page.click('body > div > main > section > div button[type="submit"]')
// Wait for next-auth example page login status header to appear
await page.waitForTimeout(2000)
// Snap a screenshot
// await page.screenshot({
// path: "2-next-auth-redirect-result.png",
// fullPage: false,
// })
// Check session object after successful login
const response = await page.goto(
"https://next-auth-example.vercel.app/api/auth/session"
)
const session = await response?.json()
expect(session?.user?.email).toBe(process.env.AUTH0_USERNAME)
// TODO: Check whole object with .toEqual()
})

View File

@@ -19,8 +19,8 @@
"vite": "4.0.1"
},
"dependencies": {
"@auth/core": "0.2.5",
"@auth/sveltekit": "0.1.12"
"@auth/core": "workspace:*",
"@auth/sveltekit": "workspace:*"
},
"type": "module"
}

View File

@@ -70,7 +70,7 @@ Auth.js is extremely customizable, [our guides section](/guides/overview) will t
To be able to use `useSession` first you'll need to expose the session context, [`<SessionProvider />`](/reference/react/#sessionprovider), at the top level of your application:
```ts title="pages/_app.ts"
```ts title="pages/_app.tsx"
import { SessionProvider } from "next-auth/react"
export default function App({
@@ -186,14 +186,14 @@ http://localhost:3000/api/auth/callback/github
Auth.js will already magically create this API endpoint for you when we start the application later. Note that because we're using Next.js, locally it starts our server on the port `3000`, hence the origin is `http://localhost:3000`.
:::
Next you'll be presented with the following screen which presents all the configuration for your new OAuth app. For now, let's we need two things from it: the **Client ID** and **Client Secret** for our new OAuth app:
Next you'll be presented with the following screen which presents all the configuration for your new OAuth app. For now, we need two things from it: the **Client ID** and **Client Secret** for our new OAuth app:
<img src={gettingClientIdSecretImg} />
The Client ID is always there, a public identifier of your OAuth application within Github. Click on the **Generate a new client Secret** button and should be presented with a new string (which is just a randomized string).
:::warning
🔥 Keep both your Client ID and Client Secret secure and never expose them to the public or shared with people outside your organization. With tem a malicious actor could hijack your application and cause you and your user serious problems!
🔥 Keep both your Client ID and Client Secret secure and never expose them to the public or shared with people outside your organization. With them, a malicious actor could hijack your application and cause you and your user serious problems!
:::
Now let's copy both the Client ID and Client Secret and paste them in an environment file in the root of your project like so:

View File

@@ -5,7 +5,7 @@ title: TypeScript
Auth.js has its own type definitions to use in your TypeScript projects safely. Even if you don't use TypeScript, IDEs like VSCode will pick this up to provide you with a better developer experience. While you are typing, you will get suggestions about what certain objects/functions look like, and sometimes links to documentation, examples, and other valuable resources.
Check out the example repository showcasing how to use `next-auth` on a Next.js application with TypeScript:
https://github.com/nextauthjs/next-auth-typescript-example
https://github.com/nextauthjs/next-auth-example
---

View File

@@ -136,7 +136,7 @@ export default Auth(new Request("https://example.com"), {
const [google] = await prisma.account.findMany({
where: { userId: user.id, provider: "google" },
})
if (google.expires_at >= Date.now()) {
if (google.expires_at < Date.now()) {
// If the access token has expired, try to refresh it
try {
// https://accounts.google.com/.well-known/openid-configuration

View File

@@ -0,0 +1,153 @@
---
title: Role-based authentication
---
There are two ways to add role-based authentication (RBAC) to your application, based on the [session strategy](/concepts/session-strategies) you choose. Let's see an example for each of these.
## Getting the role
We are going to start by adding a `profile()` callback to the providers' config to determine the user role:
```ts title="/pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export default NextAuth({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ... }
},
...
})
],
})
```
:::tip
To determine the user's role, you can either add your logic or if your provider assigns roles already, use that instead.
:::
## Persisting the role
### With JWT
When you don't have a database configured, the role will be persisted in a cookie, by using the `jwt()` callback. On sign-in, the `role` property is exposed from the `profile` callback on the `user` object. Persist the `user.role` value by assigning it to `token.role`. That's it!
If you also want to use the role on the client, you can expose it via the `session` callback.
```ts title="/pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export default NextAuth({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ... }
},
...
})
],
// highlight-start
callbacks: {
jwt({ token, user }) {
if(user) token.role = user.role
return token
},
session({ session, token }) {
session.user.role = token.role
return session
}
}
// highlight-end
})
```
:::info
With this strategy, if you want to update the role, the user needs to be forced to sign in again.
:::
### With Database
When you have a database, you can save the user role on the [User model](/reference/adapters/models#user). The below example is showing you how to do this with Prisma, but the idea is the same for all adapters.
First, add a `role` column to the User model.
```ts title="/prisma/schema.prisma"
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
role String? // New column
accounts Account[]
sessions Session[]
}
```
The `profile()` callback's return value is used to create users in the database. That's it! Your newly created users will now have an assigned role.
If you also want to use the role on the client, you can expose it via the `session` callback.
```ts title="/pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
// highlight-next-line
import prisma from "lib/prisma"
export default NextAuth({
// highlight-next-line
adapter: PrismaAdapter(prisma),
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ... }
}
...
})
],
// highlight-start
callbacks: {
session({ session, user }) {
session.user.role = user.role
return session
}
}
// highlight-end
})
```
:::info
It is up to you how you want to manage to update the roles, either through direct database access or building your role update API.
:::
## Using the role
If you want to use the role in the client, for both cases above, when using the `useSession` hook, `session.user.role` will have the required role if you exposed it via the `session` callback. You can use this to render a different UI for different users.
```ts title="/pages/admin.tsx"
import { useSession } from "next-auth/react"
export default function Page() {
const session = await useSession()
if (session?.user.role === "admin") {
return <p>You are an admin, welcome!</p>
}
return <p>You are not authorized to view this page!</p>
}
```
:::tip
When using Next.js and JWT, you can alternatively also use [Middleware](https://next-auth.js.org/configuration/nextjs#wrap-middleware) to redirect the user based on their role, even before rendering the page.
:::
## Resources
- [Concepts: Session strategies](/concepts/session-strategies)
- [Next.js: Middleware](https://next-auth.js.org/configuration/nextjs#wrap-middleware)
- [Adapters: User model](/reference/adapters/models#user)
- [Adapters: Prisma adapter](/reference/adapters/prisma)
- [TypeScript](/getting-started/typescript)

View File

@@ -1,64 +0,0 @@
---
title: Role based logins
---
To add role based authentication to your application, you must do three things.
1. Update your database schema
2. Add the `role` to the session object
3. Check for `role` in your pages/components
First modify the `user` table and add a `role` column with the type of `String?`.
Below is an example Prisma schema file.
```javascript title="/prisma/schema.prisma"
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
role String? // New Column
accounts Account[]
sessions Session[]
}
```
Next, implement a custom session callback in the `[...nextauth].js` file, as shown below.
```javascript title="/pages/api/auth/[...nextauth].js"
callbacks: {
async session({ session, token, user }) {
session.user.role = user.role; // Add role value to user object so it is passed along with session
return session;
},
```
Going forward, when using the `getSession` hook, check that `session.user.role` matches the required role. The example below assumes the role `'admin'` is required.
```javascript title="/pages/admin.js"
import { getSession } from "next-auth/react"
export default function Page() {
const session = await getSession({ req })
if (session && session.user.role === "admin") {
return (
<div>
<h1>Admin</h1>
<p>Welcome to the Admin Portal!</p>
</div>
)
} else {
return (
<div>
<h1>You are not authorized to view this page!</h1>
</div>
)
}
}
```
Then it is up to you how you manage your roles, either through direct database access or building your own role update API.

View File

@@ -16,4 +16,4 @@ sidebar_label: Email options
See our guides on magic links authentication for further tips on how to customize this provider:
- [Tutorial](/getting-started/email-tutorial)
- [Guide deep-dive](guides/providers/email)
- [Guide deep-dive](/guides/providers/email)

View File

@@ -32,7 +32,6 @@ Now that we're ready, let's create a new Xata project using our next-auth schema
```json title="schema.json"
{
"formatVersion": "",
"tables": [
{
"name": "nextauth_users",

View File

@@ -3,57 +3,13 @@ id: warnings
title: Warnings
---
This is a list of warning output from Auth.js.
A list of warnings from Auth.js that need your attention.
All warnings indicate things which you should take a look at, but do not inhibit normal operation.
---
## Debug enabled
## Client
The `debug` option was evaluated to `true`. It adds extra logs in the terminal which is useful in development, but since it can print sensitive information about users, make sure to set this to `false` in production. In Node.js environments, you can for example set `debug: process.env.NODE_ENV !== "production"`. Consult with your runtime/framework on how to set this value correctly.
#### NEXTAUTH_URL
## CSRF disabled
Environment variable `NEXTAUTH_URL` missing. Please set it in your `.env` file.
:::note
On [Vercel](https://vercel.com) deployments, we will read the `VERCEL_URL` environment variable, so you won't need to define `NEXTAUTH_URL`.
:::
---
## Server
These warnings are displayed on the terminal.
#### NO_SECRET
In development, we generate a `secret` based on your configuration for convenience. This is volatile and will throw an error in production. [Read more](https://authjs.dev/reference/configuration/auth-config/#secret)
#### TWITTER_OAUTH_2_BETA
Twitter OAuth 2.0 is currently in beta as certain changes might still be necessary. This is not covered by semver. See the docs https://authjs.dev/reference/providers/twitter#oauth-2
#### EXPERIMENTAL_API
Some APIs are still experimental; they may be changed or removed in the future. Use at your own risk.
## Adapter
### ADAPTER_TYPEORM_UPDATING_ENTITIES
This warning occurs when typeorm finds that the provided entities differ from the database entities. By default while not in `production` the typeorm adapter will always synchronize changes made to the entities codefiles.
Disable this warning by setting `synchronize: false` in your typeorm config
Example:
```js title="/pages/api/auth/[...nextauth].js"
adapter: TypeORMLegacyAdapter({
type: 'mysql',
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_DB,
synchronize: false
}),
```
You were trying to get a CSRF response from Auth.js (eg.: by calling a `/csrf` endpoint), but in this setup, CSRF protection via Auth.js was turned off. This is likely if you are not directly using `@auth/core` but a framework library (like `@auth/sveltekit`) that already has CSRF protection built-in. You likely won't need the CSRF response.

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 99.881 86.449">
<g id="asgardeo-trifactor-logo-dark-16x40" transform="translate(-553.024 -388.98)">
<path id="Path_264" data-name="Path 264" d="M743.533,388.98l9.161,15.892-10.153,17.6h20.306l9.209,15.892H714.97Z" transform="translate(-119.151 0)" fill="#ff7300"/>
<path id="Path_265" data-name="Path 265" d="M705.95,438.364l9.209-15.892h20.306l-10.153-17.6,9.162-15.892,28.6,49.393Z" transform="translate(-152.926 0.009)" fill="#ff7300"/>
<path id="Path_266" data-name="Path 266" d="M749.175,446.183l-10.153-17.6-10.2,17.6H710.46l28.6-49.393,28.515,49.393Z" transform="translate(-136.043 29.246)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 99.881 86.449">
<g id="asgardeo-trifactor-logo-light-16x40" transform="translate(-553.024 -388.98)">
<path id="Path_264" data-name="Path 264" d="M743.533,388.98l9.161,15.892-10.153,17.6h20.306l9.209,15.892H714.97Z" transform="translate(-119.151)" fill="#ff7300"/>
<path id="Path_265" data-name="Path 265" d="M705.95,438.364l9.209-15.892h20.306l-10.153-17.6,9.162-15.892,28.6,49.393Z" transform="translate(-152.926 0.009)" fill="#ff7300"/>
<path id="Path_266" data-name="Path 266" d="M749.175,446.183l-10.153-17.6-10.2,17.6H710.46l28.6-49.393,28.515,49.393Z" transform="translate(-136.043 29.246)" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 722 B

View File

@@ -18,7 +18,8 @@
"lint": "prettier --check .",
"format": "prettier --write .",
"release": "release",
"version:pr": "node ./config/version-pr"
"version:pr": "node ./config/version-pr",
"e2e": "turbo run e2e --filter=next-auth-app"
},
"devDependencies": {
"@actions/core": "^1.10.0",

View File

@@ -1,7 +1,7 @@
{
"name": "@next-auth/dynamodb-adapter",
"repository": "https://github.com/nextauthjs/next-auth",
"version": "1.0.6",
"version": "1.2.0",
"description": "AWS DynamoDB adapter for next-auth.",
"keywords": [
"next-auth",

View File

@@ -16,11 +16,25 @@ import { format, generateUpdateExpression } from "./utils"
export { format, generateUpdateExpression }
export interface DynamoDBAdapterOptions {
tableName?: string,
partitionKey?: string,
sortKey?: string,
indexName?: string,
indexPartitionKey?: string,
indexSortKey?: string
}
export function DynamoDBAdapter(
client: DynamoDBDocument,
options?: { tableName: string }
options?: DynamoDBAdapterOptions
): Adapter {
const TableName = options?.tableName ?? "next-auth"
const pk = options?.partitionKey ?? 'pk'
const sk = options?.sortKey ?? 'sk'
const IndexName = options?.indexName ?? 'GSI1'
const GSI1PK = options?.indexPartitionKey ?? 'GSI1PK'
const GSI1SK = options?.indexSortKey ?? 'GSI1SK'
return {
async createUser(data) {
@@ -33,11 +47,11 @@ export function DynamoDBAdapter(
TableName,
Item: format.to({
...user,
pk: `USER#${user.id}`,
sk: `USER#${user.id}`,
[pk]: `USER#${user.id}`,
[sk]: `USER#${user.id}`,
type: "USER",
GSI1PK: `USER#${user.email as string}`,
GSI1SK: `USER#${user.email as string}`,
[GSI1PK]: `USER#${user.email as string}`,
[GSI1SK]: `USER#${user.email as string}`,
}),
})
@@ -47,8 +61,8 @@ export function DynamoDBAdapter(
const data = await client.get({
TableName,
Key: {
pk: `USER#${userId}`,
sk: `USER#${userId}`,
[pk]: `USER#${userId}`,
[sk]: `USER#${userId}`,
},
})
return format.from<AdapterUser>(data.Item)
@@ -56,11 +70,11 @@ export function DynamoDBAdapter(
async getUserByEmail(email) {
const data = await client.query({
TableName,
IndexName: "GSI1",
IndexName,
KeyConditionExpression: "#gsi1pk = :gsi1pk AND #gsi1sk = :gsi1sk",
ExpressionAttributeNames: {
"#gsi1pk": "GSI1PK",
"#gsi1sk": "GSI1SK",
"#gsi1pk": GSI1PK,
"#gsi1sk": GSI1SK,
},
ExpressionAttributeValues: {
":gsi1pk": `USER#${email}`,
@@ -73,11 +87,11 @@ export function DynamoDBAdapter(
async getUserByAccount({ provider, providerAccountId }) {
const data = await client.query({
TableName,
IndexName: "GSI1",
IndexName,
KeyConditionExpression: "#gsi1pk = :gsi1pk AND #gsi1sk = :gsi1sk",
ExpressionAttributeNames: {
"#gsi1pk": "GSI1PK",
"#gsi1sk": "GSI1SK",
"#gsi1pk": GSI1PK,
"#gsi1sk": GSI1SK,
},
ExpressionAttributeValues: {
":gsi1pk": `ACCOUNT#${provider}`,
@@ -90,8 +104,8 @@ export function DynamoDBAdapter(
const res = await client.get({
TableName,
Key: {
pk: `USER#${accounts.userId}`,
sk: `USER#${accounts.userId}`,
[pk]: `USER#${accounts.userId}`,
[sk]: `USER#${accounts.userId}`,
},
})
return format.from<AdapterUser>(res.Item)
@@ -106,8 +120,8 @@ export function DynamoDBAdapter(
TableName,
Key: {
// next-auth type is incorrect it should be Partial<AdapterUser> & {id: string} instead of just Partial<AdapterUser>
pk: `USER#${user.id as string}`,
sk: `USER#${user.id as string}`,
[pk]: `USER#${user.id as string}`,
[sk]: `USER#${user.id as string}`,
},
UpdateExpression,
ExpressionAttributeNames,
@@ -123,7 +137,7 @@ export function DynamoDBAdapter(
const res = await client.query({
TableName,
KeyConditionExpression: "#pk = :pk",
ExpressionAttributeNames: { "#pk": "pk" },
ExpressionAttributeNames: { "#pk": pk },
ExpressionAttributeValues: { ":pk": `USER#${userId}` },
})
if (!res.Items) return null
@@ -134,8 +148,8 @@ export function DynamoDBAdapter(
return {
DeleteRequest: {
Key: {
sk: item.sk,
pk: item.pk,
[sk]: item.sk,
[pk]: item.pk,
},
},
}
@@ -152,10 +166,10 @@ export function DynamoDBAdapter(
const item = {
...data,
id: randomBytes(16).toString("hex"),
pk: `USER#${data.userId}`,
sk: `ACCOUNT#${data.provider}#${data.providerAccountId}`,
GSI1PK: `ACCOUNT#${data.provider}`,
GSI1SK: `ACCOUNT#${data.providerAccountId}`,
[pk]: `USER#${data.userId}`,
[sk]: `ACCOUNT#${data.provider}#${data.providerAccountId}`,
[GSI1PK]: `ACCOUNT#${data.provider}`,
[GSI1SK]: `ACCOUNT#${data.providerAccountId}`,
}
await client.put({ TableName, Item: format.to(item) })
return data
@@ -163,11 +177,11 @@ export function DynamoDBAdapter(
async unlinkAccount({ provider, providerAccountId }) {
const data = await client.query({
TableName,
IndexName: "GSI1",
IndexName,
KeyConditionExpression: "#gsi1pk = :gsi1pk AND #gsi1sk = :gsi1sk",
ExpressionAttributeNames: {
"#gsi1pk": "GSI1PK",
"#gsi1sk": "GSI1SK",
"#gsi1pk": GSI1PK,
"#gsi1sk": GSI1SK,
},
ExpressionAttributeValues: {
":gsi1pk": `ACCOUNT#${provider}`,
@@ -179,8 +193,8 @@ export function DynamoDBAdapter(
await client.delete({
TableName,
Key: {
pk: `USER#${account.userId}`,
sk: `ACCOUNT#${provider}#${providerAccountId}`,
[pk]: `USER#${account.userId}`,
[sk]: `ACCOUNT#${provider}#${providerAccountId}`,
},
ReturnValues: "ALL_OLD",
})
@@ -189,11 +203,11 @@ export function DynamoDBAdapter(
async getSessionAndUser(sessionToken) {
const data = await client.query({
TableName,
IndexName: "GSI1",
IndexName,
KeyConditionExpression: "#gsi1pk = :gsi1pk AND #gsi1sk = :gsi1sk",
ExpressionAttributeNames: {
"#gsi1pk": "GSI1PK",
"#gsi1sk": "GSI1SK",
"#gsi1pk": GSI1PK,
"#gsi1sk": GSI1SK,
},
ExpressionAttributeValues: {
":gsi1pk": `SESSION#${sessionToken}`,
@@ -205,8 +219,8 @@ export function DynamoDBAdapter(
const res = await client.get({
TableName,
Key: {
pk: `USER#${session.userId}`,
sk: `USER#${session.userId}`,
[pk]: `USER#${session.userId}`,
[sk]: `USER#${session.userId}`,
},
})
const user = format.from<AdapterUser>(res.Item)
@@ -221,10 +235,10 @@ export function DynamoDBAdapter(
await client.put({
TableName,
Item: format.to({
pk: `USER#${data.userId}`,
sk: `SESSION#${data.sessionToken}`,
GSI1SK: `SESSION#${data.sessionToken}`,
GSI1PK: `SESSION#${data.sessionToken}`,
[pk]: `USER#${data.userId}`,
[sk]: `SESSION#${data.sessionToken}`,
[GSI1SK]: `SESSION#${data.sessionToken}`,
[GSI1PK]: `SESSION#${data.sessionToken}`,
type: "SESSION",
...data,
}),
@@ -235,11 +249,11 @@ export function DynamoDBAdapter(
const { sessionToken } = session
const data = await client.query({
TableName,
IndexName: "GSI1",
IndexName,
KeyConditionExpression: "#gsi1pk = :gsi1pk AND #gsi1sk = :gsi1sk",
ExpressionAttributeNames: {
"#gsi1pk": "GSI1PK",
"#gsi1sk": "GSI1SK",
"#gsi1pk": GSI1PK,
"#gsi1sk": GSI1SK,
},
ExpressionAttributeValues: {
":gsi1pk": `SESSION#${sessionToken}`,
@@ -266,11 +280,11 @@ export function DynamoDBAdapter(
async deleteSession(sessionToken) {
const data = await client.query({
TableName,
IndexName: "GSI1",
IndexName,
KeyConditionExpression: "#gsi1pk = :gsi1pk AND #gsi1sk = :gsi1sk",
ExpressionAttributeNames: {
"#gsi1pk": "GSI1PK",
"#gsi1sk": "GSI1SK",
"#gsi1pk": GSI1PK,
"#gsi1sk": GSI1SK,
},
ExpressionAttributeValues: {
":gsi1pk": `SESSION#${sessionToken}`,
@@ -292,8 +306,8 @@ export function DynamoDBAdapter(
await client.put({
TableName,
Item: format.to({
pk: `VT#${data.identifier}`,
sk: `VT#${data.token}`,
[pk]: `VT#${data.identifier}`,
[sk]: `VT#${data.token}`,
type: "VT",
...data,
}),
@@ -304,8 +318,8 @@ export function DynamoDBAdapter(
const data = await client.delete({
TableName,
Key: {
pk: `VT#${identifier}`,
sk: `VT#${token}`,
[pk]: `VT#${identifier}`,
[sk]: `VT#${token}`,
},
ReturnValues: "ALL_OLD",
})

View File

@@ -0,0 +1,11 @@
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)
}
}

View File

@@ -25,6 +25,7 @@ import type {
} from "next-auth/adapters"
import { getConverter } from "./converter"
import getFirebase from "./getFirebase"
export type IndexableObject = Record<string, unknown>
@@ -39,7 +40,7 @@ export function FirestoreAdapter({
emulator,
...firebaseOptions
}: FirebaseOptions & FirestoreAdapterOptions): Adapter {
const firebaseApp = initializeApp(firebaseOptions)
const firebaseApp = getFirebase(firebaseOptions)
const db = getFirestore(firebaseApp)
if (emulator) {

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/xata-adapter",
"version": "0.2.0",
"version": "0.2.2",
"description": "Xata adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -43,4 +43,4 @@
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/core",
"version": "0.2.5",
"version": "0.3.0",
"description": "Authentication for the Web.",
"keywords": [
"authentication",
@@ -69,7 +69,7 @@
"preact-render-to-string": "5.2.3"
},
"peerDependencies": {
"nodemailer": "6.8.0"
"nodemailer": "^6.8.0"
},
"peerDependenciesMeta": {
"nodemailer": {
@@ -77,10 +77,11 @@
}
},
"scripts": {
"build": "pnpm css && tsc",
"build": "pnpm css && pnpm providers && tsc",
"clean": "rm -rf *.js *.d.ts* lib providers",
"css": "node scripts/generate-css",
"dev": "pnpm css && tsc -w"
"dev": "pnpm css && pnpm providers && tsc -w",
"providers": "node scripts/generate-providers"
},
"devDependencies": {
"@next-auth/tsconfig": "workspace:*",

View File

@@ -0,0 +1,18 @@
import { join } from "path"
import { readdirSync, writeFileSync } from "fs"
const providersPath = join(process.cwd(), "src/providers")
const files = readdirSync(providersPath, "utf8")
const providers = files.map((file) => {
const strippedProviderName = file.substring(0, file.indexOf("."))
return `"${strippedProviderName}"`
}).filter((provider) => provider !== '"oauth-types"' && provider !== '"index"')
const result = `
// THIS FILE IS AUTOGENERATED. DO NOT EDIT.
export type OAuthProviderType =
| ${providers.join("\n | ")}`
writeFileSync(join(providersPath, "oauth-types.ts"), result)

View File

@@ -27,14 +27,14 @@
* ## Resources
*
* - [Getting started](https://authjs.dev/getting-started/introduction)
* - [Most common use case guides](https://authjs.dev/guides/overview)
* - [Most common use case guides](https://authjs.dev/guides)
*
* @module main
*/
import { assertConfig } from "./lib/assert.js"
import { ErrorPageLoop } from "./errors.js"
import { AuthInternal } from "./lib/index.js"
import { AuthInternal, skipCSRFCheck } from "./lib/index.js"
import renderPage from "./lib/pages/index.js"
import { logger, setLogger, type LoggerInstance } from "./lib/utils/logger.js"
import { toInternalRequest, toResponse } from "./lib/web.js"
@@ -51,6 +51,8 @@ import type {
import type { Provider } from "./providers/index.js"
import { JWTOptions } from "./jwt.js"
export { skipCSRFCheck }
/**
* Core functionality provided by Auth.js.
*
@@ -296,4 +298,5 @@ export interface AuthConfig {
cookies?: Partial<CookiesOptions>
/** @todo */
trustHost?: boolean
skipCSRFCheck?: typeof skipCSRFCheck
}

View File

@@ -45,7 +45,7 @@ export function assertConfig(
const { url } = request
const warnings: WarningCode[] = []
if (!warned && options.debug) warnings.push("debug_enabled")
if (!warned && options.debug) warnings.push("debug-enabled")
if (!options.trustHost) {
return new UntrustedHost(`Host must be trusted. URL was: ${request.url}`)

View File

@@ -1,14 +1,14 @@
import { SessionStore } from "./cookie.js"
import { UnknownAction } from "../errors.js"
import { SessionStore } from "./cookie.js"
import { init } from "./init.js"
import renderPage from "./pages/index.js"
import * as routes from "./routes/index.js"
import type {
RequestInternal,
ResponseInternal,
AuthConfig,
ErrorPageParam,
RequestInternal,
ResponseInternal,
} from "../types.js"
export async function AuthInternal<
@@ -19,6 +19,8 @@ export async function AuthInternal<
): Promise<ResponseInternal<Body>> {
const { action, providerId, error, method } = request
const csrfDisabled = authOptions.skipCSRFCheck === skipCSRFCheck
const { options, cookies } = await init({
authOptions,
action,
@@ -28,6 +30,7 @@ export async function AuthInternal<
csrfToken: request.body?.csrfToken,
cookies: request.cookies,
isPost: method === "POST",
csrfDisabled,
})
const sessionStore = new SessionStore(
@@ -48,19 +51,29 @@ export async function AuthInternal<
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return { ...session, cookies } as any
}
case "csrf":
case "csrf": {
if (csrfDisabled) {
options.logger.warn("csrf-disabled")
cookies.push({
name: options.cookies.csrfToken.name,
value: "",
options: { ...options.cookies.csrfToken.options, maxAge: 0 },
})
return { status: 404, cookies }
}
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)}`
}${new URLSearchParams({ callbackUrl: options.callbackUrl })}`
if (error)
signinUrl = `${signinUrl}&error=${encodeURIComponent(error)}`
signinUrl = `${signinUrl}&${new URLSearchParams({ error })}`
return { redirect: signinUrl, cookies }
}
@@ -125,8 +138,7 @@ export async function AuthInternal<
} else {
switch (action) {
case "signin":
// Verified CSRF Token required for all sign in routes
if (options.csrfTokenVerified && options.provider) {
if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
const signin = await routes.signin(
request.query,
request.body,
@@ -138,8 +150,7 @@ export async function AuthInternal<
return { redirect: `${options.url}/signin?csrf=true`, cookies }
case "signout":
// Verified CSRF Token required for signout
if (options.csrfTokenVerified) {
if (csrfDisabled || options.csrfTokenVerified) {
const signout = await routes.signout(sessionStore, options)
if (signout.cookies) cookies.push(...signout.cookies)
return { ...signout, cookies }
@@ -150,6 +161,7 @@ export async function AuthInternal<
// Verified CSRF Token required for credentials providers only
if (
options.provider.type === "credentials" &&
!csrfDisabled &&
!options.csrfTokenVerified
) {
return { redirect: `${options.url}/signin?csrf=true`, cookies }
@@ -173,3 +185,14 @@ export async function AuthInternal<
}
throw new UnknownAction(`Cannot handle action: ${action}`)
}
/**
* :::danger
* This option is inteded for framework authors.
* :::
*
* Auth.js comes with built-in {@link https://authjs.dev/concepts/security#csrf CSRF} protection, but
* if you are implementing a framework that is already protected against CSRF attacks, you can skip this check by
* passing this value to {@link AuthConfig.skipCSRFCheck}.
*/
export const skipCSRFCheck = Symbol("skip-csrf-check")

View File

@@ -25,6 +25,7 @@ interface InitParams {
/** CSRF token value extracted from the incoming request. From body if POST, from query if GET */
csrfToken?: string
/** Is the incoming request a POST request? */
csrfDisabled: boolean
isPost: boolean
cookies: RequestInternal["cookies"]
}
@@ -38,6 +39,7 @@ export async function init({
cookies: reqCookies,
callbackUrl: reqCallbackUrl,
csrfToken: reqCsrfToken,
csrfDisabled,
isPost,
}: InitParams): Promise<{
options: InternalOptions
@@ -117,26 +119,28 @@ export async function init({
const cookies: cookie.Cookie[] = []
const {
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
})
options.csrfToken = csrfToken
options.csrfTokenVerified = csrfTokenVerified
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
if (!csrfDisabled) {
const {
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
})
options.csrfToken = csrfToken
options.csrfTokenVerified = csrfTokenVerified
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
})
}
}
const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({

View File

@@ -1,7 +1,7 @@
import * as checks from "./checks.js"
import * as o from "oauth4webapi"
import type {
CookiesOptions,
InternalOptions,
RequestInternal,
ResponseInternal,
@@ -58,10 +58,10 @@ export async function getAuthorizationUrl(
const cookies: Cookie[] = []
if (provider.checks?.includes("state")) {
const { value, raw } = await createState(options)
authParams.set("state", raw)
cookies.push(value)
const state = await checks.state.create(options)
if (state) {
authParams.set("state", state.value)
cookies.push(state.cookie)
}
if (provider.checks?.includes("pkce")) {
@@ -70,17 +70,17 @@ export async function getAuthorizationUrl(
// a random `nonce` must be used for CSRF protection.
provider.checks = ["nonce"]
} else {
const { code_challenge, pkce } = await createPKCE(options)
authParams.set("code_challenge", code_challenge)
const { value, cookie } = await checks.pkce.create(options)
authParams.set("code_challenge", value)
authParams.set("code_challenge_method", "S256")
cookies.push(pkce)
cookies.push(cookie)
}
}
if (provider.checks?.includes("nonce")) {
const nonce = await createNonce(options)
const nonce = await checks.nonce.create(options)
if (nonce) {
authParams.set("nonce", nonce.value)
cookies.push(nonce)
cookies.push(nonce.cookie)
}
// TODO: This does not work in normalizeOAuth because authorization endpoint can come from discovery
@@ -90,54 +90,5 @@ export async function getAuthorizationUrl(
}
logger.debug("authorization url is ready", { url, cookies, provider })
return { redirect: url, cookies }
}
/** Returns a signed cookie. */
export async function signCookie(
type: keyof CookiesOptions,
value: string,
maxAge: number,
options: InternalOptions<"oauth">
): Promise<Cookie> {
const { cookies, jwt, logger } = options
logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge })
const expires = new Date()
expires.setTime(expires.getTime() + maxAge * 1000)
return {
name: cookies[type].name,
value: await jwt.encode({ ...jwt, maxAge, token: { value } }),
options: { ...cookies[type].options, expires },
}
}
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
async function createState(options: InternalOptions<"oauth">) {
const raw = o.generateRandomState()
const maxAge = STATE_MAX_AGE
const value = await signCookie("state", raw, maxAge, options)
return { value, raw }
}
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
async function createPKCE(options: InternalOptions<"oauth">) {
const code_verifier = o.generateRandomCodeVerifier()
const code_challenge = await o.calculatePKCECodeChallenge(code_verifier)
const maxAge = PKCE_MAX_AGE
const pkce = await signCookie(
"pkceCodeVerifier",
code_verifier,
maxAge,
options
)
return { code_challenge, pkce }
}
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
async function createNonce(options: InternalOptions<"oauth">) {
const raw = o.generateRandomNonce()
const maxAge = NONCE_MAX_AGE
return await signCookie("nonce", raw, maxAge, options)
return { redirect: url.toString(), cookies }
}

View File

@@ -1,8 +1,6 @@
import * as checks from "./checks.js"
import * as o from "oauth4webapi"
import { OAuthCallbackError, OAuthProfileParseError } from "../../errors.js"
import { useNonce } from "./nonce-handler.js"
import { usePKCECodeVerifier } from "./pkce-handler.js"
import { useState } from "./state-handler.js"
import type {
InternalOptions,
@@ -73,7 +71,7 @@ export async function handleOAuth(
const resCookies: Cookie[] = []
const state = await useState(cookies, resCookies, options)
const state = await checks.state.use(cookies, resCookies, options)
const parameters = o.validateAuthResponse(
as,
@@ -91,7 +89,7 @@ export async function handleOAuth(
throw new OAuthCallbackError(parameters.error)
}
const codeVerifier = await usePKCECodeVerifier(
const codeVerifier = await checks.pkce.use(
cookies?.[options.cookies.pkceCodeVerifier.name],
options
)
@@ -99,12 +97,15 @@ export async function handleOAuth(
if (codeVerifier) resCookies.push(codeVerifier.cookie)
// TODO:
const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options)
const nonce = await checks.nonce.use(
cookies?.[options.cookies.nonce.name],
options
)
if (nonce && provider.type === "oidc") {
resCookies.push(nonce.cookie)
}
const codeGrantResponse = await o.authorizationCodeGrantRequest(
let codeGrantResponse = await o.authorizationCodeGrantRequest(
as,
client,
parameters,
@@ -112,6 +113,12 @@ export async function handleOAuth(
codeVerifier?.codeVerifier ?? "auth" // TODO: review fallback code verifier
)
if (provider.token?.conform) {
codeGrantResponse =
(await provider.token.conform(codeGrantResponse.clone())) ??
codeGrantResponse
}
let challenges: o.WWWAuthenticateChallenge[] | undefined
if ((challenges = o.parseWwwAuthenticateChallenges(codeGrantResponse))) {
for (const challenge of challenges) {
@@ -196,7 +203,7 @@ async function getProfile(
// If we didn't get a response either there was a problem with the provider
// response *or* the user cancelled the action with the provider.
//
// Unfortuately, we can't tell which - at least not in a way that works for
// Unfortunately, we can't tell which - at least not in a way that works for
// all providers, so we return an empty object; the user should then be
// redirected back to the sign up page. We log the error to help developers
// who might be trying to debug this when configuring a new provider.

View File

@@ -0,0 +1,155 @@
import * as o from "oauth4webapi"
import * as jwt from "../../jwt.js"
import type {
InternalOptions,
RequestInternal,
CookiesOptions,
} from "../../types.js"
import type { Cookie } from "../cookie.js"
import { InvalidState } from "../../errors.js"
/** Returns a signed cookie. */
export async function signCookie(
type: keyof CookiesOptions,
value: string,
maxAge: number,
options: InternalOptions<"oauth">
): Promise<Cookie> {
const { cookies, logger } = options
logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge })
const expires = new Date()
expires.setTime(expires.getTime() + maxAge * 1000)
return {
name: cookies[type].name,
value: await jwt.encode({ ...options.jwt, maxAge, token: { value } }),
options: { ...cookies[type].options, expires },
}
}
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
export const pkce = {
async create(options: InternalOptions<"oauth">) {
const code_verifier = o.generateRandomCodeVerifier()
const value = await o.calculatePKCECodeChallenge(code_verifier)
const maxAge = PKCE_MAX_AGE
const cookie = await signCookie(
"pkceCodeVerifier",
code_verifier,
maxAge,
options
)
return { cookie, value }
},
/**
* Returns code_verifier if provider uses PKCE,
* and clears the container cookie afterwards.
*/
async use(
codeVerifier: string | undefined,
options: InternalOptions<"oauth">
): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> {
const { cookies, provider } = options
if (!provider?.checks?.includes("pkce") || !codeVerifier) {
return
}
const pkce = (await jwt.decode({
...options.jwt,
token: codeVerifier,
})) as any
return {
codeVerifier: pkce?.value ?? undefined,
cookie: {
name: cookies.pkceCodeVerifier.name,
value: "",
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
},
}
},
}
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
export const state = {
async create(options: InternalOptions<"oauth">) {
if (!options.provider.checks.includes("state")) return
// TODO: support customizing the state
const value = o.generateRandomState()
const maxAge = STATE_MAX_AGE
const cookie = await signCookie("state", value, maxAge, options)
return { cookie, value }
},
/**
* Returns state from the saved cookie
* if the provider supports states,
* and clears the container cookie afterwards.
*/
async use(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">
): Promise<string | undefined> {
const { provider, jwt } = options
if (!provider.checks.includes("state")) return
const state = cookies?.[options.cookies.state.name]
if (!state) throw new InvalidState("State was missing from the cookies.")
// IDEA: Let the user do something with the returned state
const value = (await jwt.decode({ ...options.jwt, token: state })) as any
if (!value?.value) throw new InvalidState("Could not parse state cookie.")
// Clear the state cookie after use
resCookies.push({
name: options.cookies.state.name,
value: "",
options: { ...options.cookies.state.options, maxAge: 0 },
})
return value.value
},
}
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
export const nonce = {
async create(options: InternalOptions<"oauth">) {
if (!options.provider.checks.includes("nonce")) return
const value = o.generateRandomNonce()
const maxAge = NONCE_MAX_AGE
const cookie = await signCookie("nonce", value, maxAge, options)
return { cookie, value }
},
/**
* Returns nonce from if the provider supports nonce,
* and clears the container cookie afterwards.
*/
async use(
nonce: string | undefined,
options: InternalOptions<"oauth">
): Promise<{ value: string; cookie: Cookie } | undefined> {
const { cookies, provider } = options
if (!provider?.checks?.includes("nonce") || !nonce) {
return
}
const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any
return {
value: value?.value ?? undefined,
cookie: {
name: cookies.nonce.name,
value: "",
options: { ...cookies.nonce.options, maxAge: 0 },
},
}
},
}

View File

@@ -1,77 +0,0 @@
import * as o from "oauth4webapi"
import * as jwt from "../../jwt.js"
import type { InternalOptions } from "../../types.js"
import type { Cookie } from "../cookie.js"
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
/**
* Returns nonce if the provider supports it
* and saves it in a cookie
*/
export async function createNonce(options: InternalOptions<"oauth">): Promise<
| undefined
| {
value: string
cookie: Cookie
}
> {
const { cookies, logger, provider } = options
if (!provider.checks?.includes("nonce")) {
// Provider does not support nonce, return nothing.
return
}
const nonce = o.generateRandomNonce()
const expires = new Date()
expires.setTime(expires.getTime() + NONCE_MAX_AGE * 1000)
// Encrypt nonce and save it to an encrypted cookie
const encryptedNonce = await jwt.encode({
...options.jwt,
maxAge: NONCE_MAX_AGE,
token: { nonce },
})
logger.debug("CREATE_ENCRYPTED_NONCE", {
nonce,
maxAge: NONCE_MAX_AGE,
})
return {
cookie: {
name: cookies.nonce.name,
value: encryptedNonce,
options: { ...cookies.nonce.options, expires },
},
value: nonce,
}
}
/**
* Returns nonce from if the provider supports nonce,
* and clears the container cookie afterwards.
*/
export async function useNonce(
nonce: string | undefined,
options: InternalOptions<"oauth">
): Promise<{ value: string; cookie: Cookie } | undefined> {
const { cookies, provider } = options
if (!provider?.checks?.includes("nonce") || !nonce) {
return
}
const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any
return {
value: value?.value ?? undefined,
cookie: {
name: cookies.nonce.name,
value: "",
options: { ...cookies.nonce.options, maxAge: 0 },
},
}
}

View File

@@ -1,87 +0,0 @@
import * as o from "oauth4webapi"
import * as jwt from "../../jwt.js"
import type { InternalOptions } from "../../types.js"
import type { Cookie } from "../cookie.js"
const PKCE_CODE_CHALLENGE_METHOD = "S256"
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
/**
* Returns `code_challenge` and `code_challenge_method`
* and saves them in a cookie.
*/
export async function createPKCE(options: InternalOptions<"oauth">): Promise<
| undefined
| {
code_challenge: string
code_challenge_method: "S256"
cookie: Cookie
}
> {
const { cookies, logger, provider } = options
if (!provider.checks?.includes("pkce")) {
// Provider does not support PKCE, return nothing.
return
}
const code_verifier = o.generateRandomCodeVerifier()
const code_challenge = await o.calculatePKCECodeChallenge(code_verifier)
const maxAge = cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE
const expires = new Date()
expires.setTime(expires.getTime() + maxAge * 1000)
// Encrypt code_verifier and save it to an encrypted cookie
const encryptedCodeVerifier = await jwt.encode({
...options.jwt,
maxAge,
token: { code_verifier },
})
logger.debug("CREATE_PKCE_CHALLENGE_VERIFIER", {
code_challenge,
code_challenge_method: PKCE_CODE_CHALLENGE_METHOD,
code_verifier,
maxAge,
})
return {
code_challenge,
code_challenge_method: PKCE_CODE_CHALLENGE_METHOD,
cookie: {
name: cookies.pkceCodeVerifier.name,
value: encryptedCodeVerifier,
options: { ...cookies.pkceCodeVerifier.options, expires },
},
}
}
/**
* Returns code_verifier if provider uses PKCE,
* and clears the container cookie afterwards.
*/
export async function usePKCECodeVerifier(
codeVerifier: string | undefined,
options: InternalOptions<"oauth">
): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> {
const { cookies, provider } = options
if (!provider?.checks?.includes("pkce") || !codeVerifier) {
return
}
const pkce = (await jwt.decode({
...options.jwt,
token: codeVerifier,
})) as any
return {
codeVerifier: pkce?.value ?? undefined,
cookie: {
name: cookies.pkceCodeVerifier.name,
value: "",
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
},
}
}

View File

@@ -1,72 +0,0 @@
import * as o from "oauth4webapi"
import type { InternalOptions, RequestInternal } from "../../types.js"
import type { Cookie } from "../cookie.js"
import { InvalidState } from "../../errors.js"
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
/** Returns state if the provider supports it */
export async function createState(
options: InternalOptions<"oauth">
): Promise<{ cookie: Cookie; value: string } | undefined> {
const { logger, provider, jwt, cookies } = options
if (!provider.checks?.includes("state")) {
// Provider does not support state, return nothing
return
}
const state = o.generateRandomState()
const maxAge = cookies.state.options.maxAge ?? STATE_MAX_AGE
const encodedState = await jwt.encode({
...jwt,
maxAge,
token: { state },
})
logger.debug("CREATE_STATE", { state, maxAge })
const expires = new Date()
expires.setTime(expires.getTime() + maxAge * 1000)
return {
value: state,
cookie: {
name: cookies.state.name,
value: encodedState,
options: { ...cookies.state.options, expires },
},
}
}
/**
* Returns state from the saved cookie
* if the provider supports states,
* and clears the container cookie afterwards.
*/
export async function useState(
cookies: RequestInternal["cookies"],
resCookies: Cookie[],
options: InternalOptions<"oauth">
): Promise<string | undefined> {
const { provider, jwt } = options
if (!provider.checks.includes("state")) return
const state = cookies?.[options.cookies.state.name]
if (!state) throw new InvalidState("State was missing from the cookies.")
// IDEA: Let the user do something with the returned state
const value = (await jwt.decode({ ...options.jwt, token: state })) as any
if (!value?.value) throw new InvalidState("Could not parse state cookie.")
// Clear the state cookie after use
resCookies.push({
name: options.cookies.state.name,
value: "",
options: { ...options.cookies.state.options, maxAge: 0 },
})
return value.value
}

View File

@@ -96,6 +96,9 @@ function normalizeEndpoint(
// NOTE: This need to be checked when constructing the URL
// for the authorization, token and userinfo endpoints.
const url = new URL(e?.url ?? "https://authjs.dev")
for (const k in e?.params) url.searchParams.set(k, e?.params[k])
return { url, request: e?.request }
for (const k in e?.params) {
if (e?.params && k === "claims") e.params[k] = JSON.stringify(e.params[k])
url.searchParams.set(k, e?.params[k])
}
return { url, request: e?.request, conform: e?.conform }
}

View File

@@ -149,7 +149,7 @@ export async function callback(params: {
return {
redirect: `${pages.newUser}${
pages.newUser.includes("?") ? "&" : "?"
}callbackUrl=${encodeURIComponent(callbackUrl)}`,
}${new URLSearchParams({ callbackUrl })}`,
cookies,
}
}
@@ -256,7 +256,7 @@ export async function callback(params: {
return {
redirect: `${pages.newUser}${
pages.newUser.includes("?") ? "&" : "?"
}callbackUrl=${encodeURIComponent(callbackUrl)}`,
}${new URLSearchParams({ callbackUrl })}`,
cookies,
}
}
@@ -350,6 +350,6 @@ export async function callback(params: {
logger.error(error)
url.searchParams.set("error", CallbackRouteError.name)
url.pathname += "/error"
return { redirect: url, cookies }
return { redirect: url.toString(), cookies }
}
}

View File

@@ -13,13 +13,13 @@ export async function handleAuthorized(
if (!authorized) {
logger.debug("User not authorized", params)
url.searchParams.set("error", "AccessDenied")
return { status: 403 as const, redirect: url }
return { status: 403 as const, redirect: url.toString() }
}
} catch (e) {
const error = new AuthorizedCallbackError(e as Error)
logger.error(error)
url.searchParams.set("error", "Configuration")
return { status: 500 as const, redirect: url }
return { status: 500 as const, redirect: url.toString() }
}
}

View File

@@ -54,7 +54,7 @@ export async function signin(
logger.error(error)
url.searchParams.set("error", error.name)
url.pathname += "/error"
return { redirect: url }
return { redirect: url.toString() }
}
}

View File

@@ -8,7 +8,7 @@ import type { SessionStore } from "../cookie.js"
* If the session strategy is database,
* The session is also deleted from the database.
* In any case, the session cookie is cleared and
* an `events.signOut` is emitted.
* {@link EventCallbacks.signOut} is emitted.
*/
export async function signout(
sessionStore: SessionStore,

View File

@@ -1,6 +1,6 @@
import { AuthError } from "../../errors.js"
export type WarningCode = "debug_enabled"
export type WarningCode = "debug-enabled" | "csrf-disabled"
/**
* Override any of the methods, and the rest will use the default logger.
@@ -38,7 +38,7 @@ export const logger: LoggerInstance = {
}
},
warn(code) {
const url = `https://errors.authjs.dev#${code}`
const url = `https://warnings.authjs.dev#${code}`
console.warn(`${yellow}[auth][warn][${code}]${reset}`, `Read more: ${url}`)
},
debug(message, metadata) {

View File

@@ -76,27 +76,22 @@ export function toResponse(res: ResponseInternal): Response {
res.cookies?.forEach((cookie) => {
const { name, value, options } = cookie
const cookieHeader = serialize(name, value, options)
if (headers.has("Set-Cookie")) {
headers.append("Set-Cookie", cookieHeader)
} else {
headers.set("Set-Cookie", cookieHeader)
}
if (headers.has("Set-Cookie")) headers.append("Set-Cookie", cookieHeader)
else headers.set("Set-Cookie", cookieHeader)
// headers.set("Set-Cookie", cookieHeader) // TODO: Remove. Seems to be a bug with Headers in the runtime
})
const body =
headers.get("content-type") === "application/json"
? JSON.stringify(res.body)
: res.body
let body = res.body
const response = new Response(body, {
headers,
status: res.redirect ? 302 : res.status ?? 200,
})
if (headers.get("content-type") === "application/json")
body = JSON.stringify(res.body)
else if (headers.get("content-type") === "application/x-www-form-urlencoded")
body = new URLSearchParams(res.body).toString()
if (res.redirect) {
response.headers.set("Location", res.redirect.toString())
}
const status = res.redirect ? 302 : res.status ?? 200
const response = new Response(body, { headers, status })
if (res.redirect) response.headers.set("Location", res.redirect)
return response
}

View File

@@ -0,0 +1,112 @@
/**
* <div style={{backgroundColor: "#24292f", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Asgardeo</b> integration.</span>
* <a href="https://wso2.com/asgardeo/">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/asgardeo-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/asgardeo
*/
import type { OIDCConfig, OIDCUserConfig } from "./index.js"
export interface AsgardeoProfile {
sub: string
given_name: string
email: string
picture: string
}
/**
* Add Asgardeo login to your page.
* ## Documentation
*
* https://wso2.com/asgardeo/docs/guides/authentication
*
*
* ## Instructions
*
* - Log into https://console.asgardeo.io.
* - Next, go to "Application" tab (More info: https://wso2.com/asgardeo/docs/guides/applications/register-oidc-web-app/).
* - Register standard based - Open id connect, application.
* - Add callback URL: http://localhost:3000/api/auth/callback/asgardeo and https://your-domain.com/api/auth/callback/asgardeo
* - After registering the application, go to protocol tab.
* - Check `code` grant type.
* - Add Authorized redirect URLs & Allowed origins fields.
* - Make Email, First Name, Photo URL user attributes mandatory from the console.
*
* Create a `.env` file in the project root add the following entries:
*
* These values can be collected from the application created.
*
* ```
* ASGARDEO_CLIENT_ID=<Copy client ID from protocol tab here>
* ASGARDEO_CLIENT_SECRET=<Copy client from protocol tab here>
* ASGARDEO_ISSUER=<Copy the issuer url from the info tab here>
* ```
*
* In `pages/api/auth/[...nextauth].js` find or add the `Asgardeo` entries:
*
* ```js
* import Asgardeo from "next-auth/providers/asgardeo";
* ...
* providers: [
* Asgardeo({
* clientId: process.env.ASGARDEO_CLIENT_ID,
* clientSecret: process.env.ASGARDEO_CLIENT_SECRET,
* issuer: process.env.ASGARDEO_ISSUER
* }),
* ],
*
* ...
* ```
*
* ## Resources
*
* @see [Asgardeo - Authentication Guide](https://wso2.com/asgardeo/docs/guides/authentication)
* @see [Learn more about OAuth](https://authjs.dev/concepts/oauth)
* @see [Source code](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/asgardeo.ts)
*
* ## Notes
*
* By default, Auth.js assumes that the Asgardeo provider is
* based on the [OAuth 2](https://www.rfc-editor.org/rfc/rfc6749.html) specification.
*
* :::tip
*
* The Asgardeo provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/asgardeo.ts).
* To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/providers/custom-provider#override-default-options).
*
* :::
*
* :::info **Disclaimer**
*
* If you think you found a bug in the default configuration, you can [open an issue](https://authjs.dev/new/provider-issue).
*
* Auth.js strictly adheres to the specification and it cannot take responsibility for any deviation from
* the spec by the provider. You can open an issue, but if the problem is non-compliance with the spec,
* we might not pursue a resolution. You can ask for more help in [Discussions](https://authjs.dev/new/github-discussions).
*
* :::
*/
export default function Asgardeo(
config: OIDCUserConfig<AsgardeoProfile>
): OIDCConfig<AsgardeoProfile> {
return {
id: "asgardeo",
name: "Asgardeo",
type: "oidc",
wellKnown: `${config?.issuer}/oauth2/token/.well-known/openid-configuration`,
style: {
logo: "/asgardeo.svg",
logoDark: "/asgardeo-dark.svg",
bg: "#fff",
text: "#000",
bgDark: "#000",
textDark: "#fff",
},
options: config,
}
}

View File

@@ -34,22 +34,21 @@ export default function AzureAD<P extends AzureADProfile>(
)
// Confirm that profile photo was returned
if (response.ok) {
const pictureBuffer = await response.arrayBuffer()
const pictureBase64 = Buffer.from(pictureBuffer).toString("base64")
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: `data:image/jpeg;base64, ${pictureBase64}`,
}
} else {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
}
let image
// TODO: Do this without Buffer
if (response.ok && typeof Buffer !== "undefined") {
try {
const pictureBuffer = await response.arrayBuffer()
const pictureBase64 = Buffer.from(pictureBuffer).toString("base64")
image = `data:image/jpeg;base64, ${pictureBase64}`
} catch {}
}
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: image ?? null,
}
},
style: {

View File

@@ -49,10 +49,6 @@ export interface CredentialsConfig<
export type CredentialsProviderType = "Credentials"
export type CredentialsConfigInternal<
C extends Record<string, CredentialInput> = Record<string, CredentialInput>
> = CredentialsConfig<C> & { options: CredentialsConfig<C> }
/**
* The Credentials provider allows you to handle signing in with arbitrary credentials,
* such as a username and password, domain, or two factor authentication or hardware device (e.g. YubiKey U2F / FIDO).

View File

@@ -1,21 +1,77 @@
import type { OAuthConfig, OAuthUserConfig } from "./index.js"
/**
* Corresponds to the user structure documented here:
* https://discord.com/developers/docs/resources/user#user-object-user-structure
*/
export interface DiscordProfile extends Record<string, any> {
accent_color: number
avatar: string
banner: string
banner_color: string
discriminator: string
email: string
flags: number
/** the user's id (i.e. the numerical snowflake) */
id: string
image_url: string
locale: string
mfa_enabled: boolean
premium_type: number
public_flags: number
/** the user's username, not unique across the platform */
username: string
/** the user's 4-digit discord-tag */
discriminator: string
/**
* the user's avatar hash:
* https://discord.com/developers/docs/reference#image-formatting
*/
avatar: string | null
/** whether the user belongs to an OAuth2 application */
bot?: boolean
/**
* whether the user is an Official Discord System user (part of the urgent
* message system)
*/
system?: boolean
/** whether the user has two factor enabled on their account */
mfa_enabled: boolean
/**
* the user's banner hash:
* https://discord.com/developers/docs/reference#image-formatting
*/
banner: string | null
/** the user's banner color encoded as an integer representation of hexadecimal color code */
accent_color: number | null
/**
* the user's chosen language option:
* https://discord.com/developers/docs/reference#locales
*/
locale: string
/** whether the email on this account has been verified */
verified: boolean
/** the user's email */
email: string | null
/**
* the flags on a user's account:
* https://discord.com/developers/docs/resources/user#user-object-user-flags
*/
flags: number
/**
* the type of Nitro subscription on a user's account:
* https://discord.com/developers/docs/resources/user#user-object-premium-types
*/
premium_type: number
/**
* the public flags on a user's account:
* https://discord.com/developers/docs/resources/user#user-object-user-flags
*/
public_flags: number
/** undocumented field; corresponds to the user's custom nickname */
display_name: string | null
/**
* undocumented field; corresponds to the Discord feature where you can e.g.
* put your avatar inside of an ice cube
*/
avatar_decoration: string | null
/**
* undocumented field; corresponds to the premium feature where you can
* select a custom banner color
*/
banner_color: string | null
/** undocumented field; the CDN URL of their profile picture */
image_url: string
}
export default function Discord<P extends DiscordProfile>(

View File

@@ -65,12 +65,16 @@ export type Provider<P extends Profile = Profile> = (
| EmailConfig
| CredentialsConfig
) & {
/**
* Used to deep merge user-provided config with the default config
* @internal
*/
options: Record<string, unknown>
}
export type BuiltInProviders = Record<
OAuthProviderType,
(options: Partial<OAuthConfig<any>>) => OAuthConfig<any>
(config: Partial<OAuthConfig<any>>) => OAuthConfig<any>
> &
Record<CredentialsProviderType, typeof CredentialsProvider> &
Record<EmailProviderType, typeof EmailProvider>

View File

@@ -1,69 +0,0 @@
// THIS FILE IS AUTOGENERATED. DO NOT EDIT.
export type OAuthProviderType =
| "42-school"
| "apple"
| "atlassian"
| "auth0"
| "authentik"
| "azure-ad-b2c"
| "azure-ad"
| "battlenet"
| "box"
| "boxyhq-saml"
| "bungie"
| "cognito"
| "coinbase"
| "credentials"
| "discord"
| "dropbox"
| "duende-identity-server6"
| "email"
| "eveonline"
| "facebook"
| "faceit"
| "foursquare"
| "freshbooks"
| "fusionauth"
| "github"
| "gitlab"
| "google"
| "hubspot"
| "identity-server4"
| "index"
| "instagram"
| "kakao"
| "keycloak"
| "line"
| "linkedin"
| "mailchimp"
| "mailru"
| "medium"
| "naver"
| "netlify"
| "oauth-types.js"
| "oauth"
| "okta"
| "onelogin"
| "osso"
| "osu"
| "patreon"
| "pinterest"
| "pipedrive"
| "reddit"
| "salesforce"
| "slack"
| "spotify"
| "strava"
| "todoist"
| "trakt"
| "twitch"
| "twitter"
| "united-effects"
| "vk"
| "wikimedia"
| "wordpress"
| "workos"
| "yandex"
| "zitadel"
| "zoho"
| "zoom"

View File

@@ -42,6 +42,8 @@ interface AdvancedEndpointHandler<P extends UrlParams, C, R> {
* You should **try to avoid using advanced options** unless you are very comfortable using them.
*/
request?: EndpointRequest<C, R, P>
/** @internal */
conform?: (response: Response) => Awaitable<Response | undefined>
}
/** Either an URL (containing all the parameters) or an object with more granular control. */
@@ -64,7 +66,7 @@ export type TokenEndpointHandler = EndpointHandler<
params: CallbackParamsType
/**
* When using this custom flow, make sure to do all the necessary security checks.
* Thist object contains parameters you have to match against the request to make sure it is valid.
* This object contains parameters you have to match against the request to make sure it is valid.
*/
checks: OAuthChecks
},
@@ -184,7 +186,11 @@ export type OAuthConfigInternal<Profile> = Omit<
OAuthEndpointType
> & {
authorization?: { url: URL }
token?: { url: URL; request?: TokenEndpointHandler["request"] }
token?: {
url: URL
request?: TokenEndpointHandler["request"]
conform?: TokenEndpointHandler["conform"]
}
userinfo?: { url: URL; request?: UserinfoEndpointHandler["request"] }
} & Pick<Required<OAuthConfig<Profile>>, "clientId" | "checks" | "profile">

View File

@@ -1,4 +1,4 @@
import type { OAuthConfig, OAuthUserConfig } from "./index.js"
import type { OIDCConfig, OIDCUserConfig } from "./index.js"
export interface TwitchProfile extends Record<string, any> {
sub: string
@@ -7,26 +7,52 @@ export interface TwitchProfile extends Record<string, any> {
picture: string
}
export default function Twitch<P extends TwitchProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
export default function Twitch(
config: OIDCUserConfig<TwitchProfile>
): OIDCConfig<TwitchProfile> {
return {
issuer: "https://id.twitch.tv/oauth2",
id: "twitch",
name: "Twitch",
type: "oidc",
client: { token_endpoint_auth_method: "client_secret_post" },
authorization: {
params: {
scope: "openid user:read:email",
claims: {
id_token: {
email: null,
picture: null,
preferred_username: null,
},
id_token: { email: null, picture: null, preferred_username: null },
},
},
},
token: {
async conform(response) {
const body = await response.json()
if (response.ok) {
if (typeof body.scope === "string") {
console.warn(
"'scope' is a string. Redundant workaround, please open an issue."
)
} else if (Array.isArray(body.scope)) {
body.scope = body.scope.join(" ")
return new Response(JSON.stringify(body), response)
} else if ("scope" in body) {
delete body.scope
return new Response(JSON.stringify(body), response)
}
} else {
const { message: error_description, error } = body
if (typeof error !== "string") {
return new Response(
JSON.stringify({ error: "invalid_request", error_description }),
response
)
}
console.warn(
"Response has 'error'. Redundant workaround, please open an issue."
)
}
},
},
style: {
logo: "/twitch.svg",
logoDark: "/twitch-dark.svg",
@@ -35,6 +61,6 @@ export default function Twitch<P extends TwitchProfile>(
bgDark: "#65459B",
textDark: "#fff",
},
options,
options: config,
}
}

View File

@@ -203,7 +203,7 @@ export interface CallbacksOptions<P = Profile, A = Account> {
* Its content is forwarded to the `session` callback,
* where you can control what should be returned to the client.
* Anything else will be kept inaccessible from the client.
*
*
* Returning `null` will invalidate the JWT session by clearing
* the user's cookies. You'll still have to monitor and invalidate
* unexpired tokens from future requests yourself to prevent
@@ -220,7 +220,7 @@ export interface CallbacksOptions<P = Profile, A = Account> {
account?: A | null
profile?: P
isNewUser?: boolean
}) => Awaitable<JWT|null>
}) => Awaitable<JWT | null>
}
/** [Documentation](https://authjs.dev/reference/configuration/auth-config#cookies) */
@@ -452,7 +452,7 @@ export interface ResponseInternal<
status?: number
headers?: Headers | HeadersInit
body?: Body
redirect?: URL | string
redirect?: string
cookies?: Cookie[]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/sveltekit",
"version": "0.1.12",
"version": "0.2.0",
"description": "Authentication for SvelteKit.",
"keywords": [
"authentication",
@@ -32,7 +32,7 @@
"test:unit": "vitest"
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@playwright/test": "1.29.2",
"@sveltejs/adapter-auto": "^1.0.0",
"@sveltejs/kit": "^1.0.0",
"@sveltejs/package": "^1.0.0",

View File

@@ -1,8 +1,16 @@
// After build, copy the files in ./package to the root directory, excluding the package.json file.
import fs from "fs/promises"
import path from "path"
const __dirname = path.dirname(new URL(import.meta.url).pathname)
let __dirname = path.dirname(new URL(import.meta.url).pathname)
// The above hack to polyfill "__dirname" for ESM does not work on Windows computers,
// so we might have to manually perform more steps.
__dirname = __dirname.split(path.sep).join(path.posix.sep)
if (__dirname.match(/^\/\w:\//)) {
__dirname = __dirname.slice(3) // Remove the drive prefix.
}
const root = path.join(__dirname, "..")
const pkgDir = path.join(root, "package")

323
pnpm-lock.yaml generated
View File

@@ -68,11 +68,13 @@ importers:
'@next-auth/prisma-adapter': workspace:*
'@next-auth/supabase-adapter': workspace:*
'@next-auth/typeorm-legacy-adapter': workspace:*
'@playwright/test': 1.29.2
'@prisma/client': ^3
'@supabase/supabase-js': ^2.0.5
'@types/jsonwebtoken': ^8.5.5
'@types/react': ^18.0.15
'@types/react-dom': ^18.0.6
dotenv: ^16.0.3
fake-smtp-server: ^0.8.0
faunadb: ^4
next: 13.1.1
@@ -99,9 +101,11 @@ importers:
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
devDependencies:
'@playwright/test': 1.29.2
'@types/jsonwebtoken': 8.5.8
'@types/react': 18.0.26
'@types/react-dom': 18.0.6
dotenv: 16.0.3
fake-smtp-server: 0.8.0
pg: 8.7.3
prisma: 3.15.2
@@ -110,8 +114,8 @@ importers:
apps/dev/sveltekit:
specifiers:
'@auth/core': 0.2.5
'@auth/sveltekit': 0.1.12
'@auth/core': workspace:*
'@auth/sveltekit': workspace:*
'@sveltejs/adapter-auto': next
'@sveltejs/kit': next
svelte: 3.55.0
@@ -139,7 +143,7 @@ importers:
vercel: ^23.1.2
dependencies:
dotenv: 16.0.3
gatsby: 5.5.0-next.0_biqbaboplfbrettd7655fr4n2y
gatsby: 5.6.0-next.2_biqbaboplfbrettd7655fr4n2y
next-auth: link:../../../packages/next-auth
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
@@ -562,7 +566,7 @@ importers:
packages/frameworks-sveltekit:
specifiers:
'@auth/core': workspace:*
'@playwright/test': ^1.28.1
'@playwright/test': 1.29.2
'@sveltejs/adapter-auto': ^1.0.0
'@sveltejs/kit': ^1.0.0
'@sveltejs/package': ^1.0.0
@@ -576,7 +580,7 @@ importers:
dependencies:
'@auth/core': link:../core
devDependencies:
'@playwright/test': 1.28.1
'@playwright/test': 1.29.2
'@sveltejs/adapter-auto': 1.0.0_@sveltejs+kit@1.0.1
'@sveltejs/kit': 1.0.1_svelte@3.54.0+vite@4.0.1
'@sveltejs/package': 1.0.1_gf4dcx76vtk2o62ixxeqx7chra
@@ -2873,7 +2877,7 @@ packages:
'@babel/core': 7.20.12
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.20.0
'@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.20.12
'@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.12
/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.9_@babel+core@7.20.2:
resolution: {integrity: sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==}
@@ -2884,7 +2888,7 @@ packages:
'@babel/core': 7.20.2
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.20.0
'@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.20.2
'@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.2
dev: true
/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.18.9_@babel+core@7.20.5:
@@ -2896,7 +2900,7 @@ packages:
'@babel/core': 7.20.5
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.20.0
'@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.20.5
'@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.5
dev: true
/@babel/plugin-proposal-async-generator-functions/7.17.12:
@@ -3619,7 +3623,30 @@ packages:
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.20.0
'@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.12
dev: false
/@babel/plugin-proposal-optional-chaining/7.20.7_@babel+core@7.20.2:
resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.20.2
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.20.0
'@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.2
dev: true
/@babel/plugin-proposal-optional-chaining/7.20.7_@babel+core@7.20.5:
resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
'@babel/core': 7.20.5
'@babel/helper-plugin-utils': 7.20.2
'@babel/helper-skip-transparent-expression-wrappers': 7.20.0
'@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.5
dev: true
/@babel/plugin-proposal-private-methods/7.17.12:
resolution: {integrity: sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==}
@@ -7488,8 +7515,8 @@ packages:
resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==}
dev: false
/@builder.io/partytown/0.5.4:
resolution: {integrity: sha512-qnikpQgi30AS01aFlNQV6l8/qdZIcP76mp90ti+u4rucXHsn4afSKivQXApqxvrQG9+Ibv45STyvHizvxef/7A==}
/@builder.io/partytown/0.7.5:
resolution: {integrity: sha512-Zbr2Eo0AQ4yzmQr/36/h+6LKjmdVBB3Q5cGzO6rtlIKB/IOpbQVUZW+XAnhpJmJr9sIF97OZjgbhG9k7Sjn4yw==}
hasBin: true
dev: false
@@ -9076,14 +9103,14 @@ packages:
dev: true
optional: true
/@gatsbyjs/parcel-namer-relative-to-cwd/2.5.0-next.0_@parcel+core@2.8.2:
resolution: {integrity: sha512-1BEOXlcXkIrOPgRdNXf404CfmqmtpcKcAgmde+gpGUS040fT5BRTT+9kxnJQaj7ZbLMhRxI83zjZ6Nkwi/8g8w==}
/@gatsbyjs/parcel-namer-relative-to-cwd/2.6.0-next.0_@parcel+core@2.8.2:
resolution: {integrity: sha512-YAEMsZhRG59bs9Ps3TKh/jxQ2ysnHU7+eVEEq/gssh2o+og//WTiR7KldR6xFEWQWMmmYQThAGyuJe/sN9hVNA==}
engines: {node: '>=18.0.0', parcel: 2.x}
dependencies:
'@babel/runtime': 7.20.7
'@parcel/namer-default': 2.8.2_@parcel+core@2.8.2
'@parcel/plugin': 2.8.2_@parcel+core@2.8.2
gatsby-core-utils: 4.5.0-next.0
gatsby-core-utils: 4.6.0-next.0
transitivePeerDependencies:
- '@parcel/core'
dev: false
@@ -11346,13 +11373,13 @@ packages:
nullthrows: 1.1.1
dev: false
/@playwright/test/1.28.1:
resolution: {integrity: sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==}
/@playwright/test/1.29.2:
resolution: {integrity: sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@types/node': 18.11.10
playwright-core: 1.28.1
playwright-core: 1.29.2
dev: true
/@pmmmwh/react-refresh-webpack-plugin/0.5.10_xsftmjzvfioxqs4ify53ibh7ay:
@@ -14040,8 +14067,10 @@ packages:
indent-string: 4.0.0
dev: true
/ajv-formats/2.1.1:
/ajv-formats/2.1.1_ajv@8.11.0:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
@@ -14545,26 +14574,17 @@ packages:
engines: {node: '>=4'}
dev: false
/axios/0.21.4_debug@3.2.7:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
follow-redirects: 1.15.1_debug@3.2.7
transitivePeerDependencies:
- debug
dev: false
/axios/0.21.4_debug@4.3.4:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
follow-redirects: 1.15.1_debug@4.3.4
transitivePeerDependencies:
- debug
dev: true
/axios/0.25.0:
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
dependencies:
follow-redirects: 1.15.1
follow-redirects: 1.15.1_debug@4.3.4
transitivePeerDependencies:
- debug
dev: true
@@ -14951,8 +14971,8 @@ packages:
- supports-color
dev: true
/babel-plugin-remove-graphql-queries/5.5.0-next.0_jqsf6f3mvfsscspe5xblmymkvm:
resolution: {integrity: sha512-y7xJGPPHPljVR3nhZNllraBJ42F1j1iJsrUlUWQW5nLDNM/iPyWdpKmvKaChvU9gez5cs8pQexVcFh6NbUzcMg==}
/babel-plugin-remove-graphql-queries/5.6.0-next.0_x3fnz3zp5bgltr62wwyqvgm7l4:
resolution: {integrity: sha512-hrxx7U73x6TUL+x/h2/OrQT4hdeGkYjK39oiYtt6erSVz8q1iOYl4tfCYN0wuHyrvpRdXJ7UBrAhLgBFIPn8Gw==}
engines: {node: '>=18.0.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -14961,8 +14981,8 @@ packages:
'@babel/core': 7.20.12
'@babel/runtime': 7.20.7
'@babel/types': 7.20.7
gatsby: 5.5.0-next.0_biqbaboplfbrettd7655fr4n2y
gatsby-core-utils: 4.5.0-next.0
gatsby: 5.6.0-next.2_biqbaboplfbrettd7655fr4n2y
gatsby-core-utils: 4.6.0-next.0
dev: false
/babel-plugin-styled-components/2.0.7_styled-components@5.3.6:
@@ -15046,8 +15066,8 @@ packages:
- supports-color
dev: false
/babel-preset-gatsby/3.5.0-next.0_pp2vm42zn6vfmnpuhar3irht7i:
resolution: {integrity: sha512-ljJsjFAfZjfKoCwO0bw3vxC3PmbE4XTZ91kIlKv8p+au5bOtDu2cFxltLte8khR6aAg5vEwUwvr4D4PUP4Wgsg==}
/babel-preset-gatsby/3.6.0-next.0_pp2vm42zn6vfmnpuhar3irht7i:
resolution: {integrity: sha512-Yg3s+TqJcaiolWzERdtCc/nQMnQDGqv6VvB4CEsRFAzZy9YoDLN6BauTpJ/FWeJc595TdszCYLqYLoa0cXcfVw==}
engines: {node: '>=18.0.0'}
peerDependencies:
'@babel/core': ^7.11.6
@@ -15068,8 +15088,8 @@ packages:
babel-plugin-macros: 3.1.0
babel-plugin-transform-react-remove-prop-types: 0.4.24
core-js: 3.26.0
gatsby-core-utils: 4.5.0-next.0
gatsby-legacy-polyfills: 3.5.0-next.0
gatsby-core-utils: 4.6.0-next.0
gatsby-legacy-polyfills: 3.6.0-next.0
transitivePeerDependencies:
- supports-color
dev: false
@@ -15304,6 +15324,7 @@ packages:
term-size: 2.2.1
type-fest: 0.8.1
widest-line: 3.1.0
dev: true
/boxen/5.1.2:
resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
@@ -15733,6 +15754,7 @@ packages:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: true
/chalk/4.1.1:
resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==}
@@ -16500,8 +16522,8 @@ packages:
readable-stream: 3.6.0
dev: true
/create-gatsby/3.5.0-next.0:
resolution: {integrity: sha512-KmDOCTD0AO/kY2ctCP5j+5Cxc8TGuMs32vvyaKEbLAhkctuvQoRKUQ9NR4+uWSJoWuO5zEm008e3HKI+TpM60A==}
/create-gatsby/3.6.0-next.1:
resolution: {integrity: sha512-7PSTSNLa1w0LZ81WXZ09IBVcE+SCuOk8QOPJC3tK2D50iTeKo1rBp2mZEX0ao5sC5q/N869HuLBPkzdvP5Lvig==}
hasBin: true
dependencies:
'@babel/runtime': 7.20.7
@@ -19713,7 +19735,7 @@ packages:
dependencies:
'@apidevtools/json-schema-ref-parser': 9.0.9
ajv: 8.11.0
ajv-formats: 2.1.1
ajv-formats: 2.1.1_ajv@8.11.0
body-parser: 1.20.0
content-type: 1.0.4
deep-freeze: 0.0.1
@@ -20363,28 +20385,6 @@ packages:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
dev: true
/follow-redirects/1.15.1:
resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: true
/follow-redirects/1.15.1_debug@3.2.7:
resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dependencies:
debug: 3.2.7
dev: false
/follow-redirects/1.15.1_debug@4.3.4:
resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
engines: {node: '>=4.0'}
@@ -20395,7 +20395,6 @@ packages:
optional: true
dependencies:
debug: 4.3.4
dev: true
/for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -20544,6 +20543,16 @@ packages:
graceful-fs: 4.2.10
jsonfile: 6.1.0
universalify: 2.0.0
dev: true
/fs-extra/11.1.0:
resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.10
jsonfile: 6.1.0
universalify: 2.0.0
dev: false
/fs-extra/7.0.1:
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
@@ -20642,8 +20651,8 @@ packages:
/functions-have-names/1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
/gatsby-cli/5.5.0-next.0:
resolution: {integrity: sha512-zDxea5Umb9LKU2kXSac84J/5QL4TvvGxyBkqUtvRq9rZRGYV+MTXgQTIzhlJQens+5kBepahERMLcifB9pJAwg==}
/gatsby-cli/5.6.0-next.1:
resolution: {integrity: sha512-tFrTZlEezIxFKMkCXXKu3tKBrjqpZt+PykNABkSgOdMGO1pT/xysSClCbKOMockWosrpgaNup+1rP8sjVRdIsw==}
engines: {node: '>=18.0.0'}
hasBin: true
requiresBuild: true
@@ -20664,13 +20673,13 @@ packages:
clipboardy: 2.3.0
common-tags: 1.8.2
convert-hrtime: 3.0.0
create-gatsby: 3.5.0-next.0
create-gatsby: 3.6.0-next.1
envinfo: 7.8.1
execa: 5.1.1
fs-exists-cached: 1.0.0
fs-extra: 10.1.0
gatsby-core-utils: 4.5.0-next.0
gatsby-telemetry: 4.5.0-next.0
fs-extra: 11.1.0
gatsby-core-utils: 4.6.0-next.0
gatsby-telemetry: 4.6.0-next.0
hosted-git-info: 3.0.8
is-valid-path: 0.1.1
joi: 17.7.0
@@ -20694,8 +20703,8 @@ packages:
- supports-color
dev: false
/gatsby-core-utils/4.5.0-next.0:
resolution: {integrity: sha512-5rnnAppOADM+aRkglo0ImObnOsENc80NOR0RT1v5Qr/BFhs1KFIDQc4lSW0dvDDCeBDi8Ngj52t1GMvCTuUaTg==}
/gatsby-core-utils/4.6.0-next.0:
resolution: {integrity: sha512-4cc4d7WfJmYWprWBIxQBRiwyT2uECR9/40rqPCe62yeshTHIZUVCxeqzEeU6R0qkDpLjHkP0F/itv2oNa0AspQ==}
engines: {node: '>=18.0.0'}
dependencies:
'@babel/runtime': 7.20.7
@@ -20703,8 +20712,9 @@ packages:
configstore: 5.0.1
fastq: 1.15.0
file-type: 16.5.4
fs-extra: 10.1.0
fs-extra: 11.1.0
got: 11.8.6
hash-wasm: 4.9.0
import-from: 4.0.0
lmdb: 2.5.3
lock: 1.1.0
@@ -20715,20 +20725,20 @@ packages:
xdg-basedir: 4.0.0
dev: false
/gatsby-graphiql-explorer/3.5.0-next.0:
resolution: {integrity: sha512-CY0paEPQwJWJyXi9U8I79ubL3FKWc7Z8QeR6B6QMacodmWdn8KHl7j+qMYG+ErHmM3ruAhxMI5HJyCDOZaHFwA==}
/gatsby-graphiql-explorer/3.6.0-next.0:
resolution: {integrity: sha512-pXW8ufxLdklCjVtKyUZ6Nr6ROjH/5dUqbnp6hMdUGLWFjvpj3AWc/DH3wsDOLw//bBEZqHAcEyj2DwNcFv8mHw==}
engines: {node: '>=18.0.0'}
dev: false
/gatsby-legacy-polyfills/3.5.0-next.0:
resolution: {integrity: sha512-UW+/a4IGFcv7fCEK5BF4K7cwIBE3FEtbuVDSJ8a89zWSHWFLs82cSfc3+bDXPqv3m5/E2rWs0KLCWwEerzHNhQ==}
/gatsby-legacy-polyfills/3.6.0-next.0:
resolution: {integrity: sha512-XwGPOPDF/Xo/wZWW52YmFlZ/89Qb/jZdxBaPHfs2zRKvTAFmFkHBl+KAy5MBISY4IFWTfOyBN2UjnklmxRn93w==}
dependencies:
'@babel/runtime': 7.20.7
core-js-compat: 3.9.0
dev: false
/gatsby-link/5.5.0-next.0_y2kppt6lrltqk6wasg3eswwzsa:
resolution: {integrity: sha512-+TydIhkhkIs/8DnVK7Zv6gai6VSmeloGOPlme2aN/Y1a0DaYLlfH9pkF0to7DVYCC6Fn99n3VlHmUrWQxnA1bw==}
/gatsby-link/5.6.0-next.0_y2kppt6lrltqk6wasg3eswwzsa:
resolution: {integrity: sha512-tKTT58UQI3PQdWTmALZ9UYZ7hN4lgxrkLNhHRlfhhjsZXAwqqVXfR51gfV8cvEltEOdqdfrUSMZ9167KxEnp5A==}
engines: {node: '>=18.0.0'}
peerDependencies:
'@gatsbyjs/reach-router': ^2.0.0
@@ -20737,33 +20747,33 @@ packages:
dependencies:
'@gatsbyjs/reach-router': 2.0.0_biqbaboplfbrettd7655fr4n2y
'@types/reach__router': 1.3.11
gatsby-page-utils: 3.5.0-next.0
gatsby-page-utils: 3.6.0-next.0
prop-types: 15.8.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/gatsby-page-utils/3.5.0-next.0:
resolution: {integrity: sha512-MHD6azIuWYxwSxQ7pgow5K53OdIVbsKUKRKP8/0WUIMG8xZkOBZtRCgGL0QrPB7ze73hujxhMDGd8firz8bNYw==}
/gatsby-page-utils/3.6.0-next.0:
resolution: {integrity: sha512-3jul3U94W/33H8DMoaEfo6z26Tlp62Lf9FtS828s1A076ao9CFBpT2xrCCAn+rF8uXKAY1oB3b337Oaxdyx70g==}
engines: {node: '>=18.0.0'}
dependencies:
'@babel/runtime': 7.20.7
bluebird: 3.7.2
chokidar: 3.5.3
fs-exists-cached: 1.0.0
gatsby-core-utils: 4.5.0-next.0
gatsby-core-utils: 4.6.0-next.0
glob: 7.2.3
lodash: 4.17.21
micromatch: 4.0.5
dev: false
/gatsby-parcel-config/1.5.0-next.0_@parcel+core@2.8.2:
resolution: {integrity: sha512-k+MqR2JQX1/siMttbyIX1zOfv3ZI390H2Pk2SjdUFJxV4dYZzcLermRkAOSNHUQIrrQ7TaLDMtq9AOcIjMmNmA==}
/gatsby-parcel-config/1.6.0-next.0_@parcel+core@2.8.2:
resolution: {integrity: sha512-zqNIyHiaegO0hX8LJ6qmPAsZC0G3QmjjDvcmKVp4xvU8c21L7LUDJP++9Ek1iKm45gfuCweXeeKX01W5EWzDFA==}
engines: {parcel: 2.x}
peerDependencies:
'@parcel/core': ^2.0.0
dependencies:
'@gatsbyjs/parcel-namer-relative-to-cwd': 2.5.0-next.0_@parcel+core@2.8.2
'@gatsbyjs/parcel-namer-relative-to-cwd': 2.6.0-next.0_@parcel+core@2.8.2
'@parcel/bundler-default': 2.8.2_@parcel+core@2.8.2
'@parcel/compressor-raw': 2.8.2_@parcel+core@2.8.2
'@parcel/core': 2.8.2
@@ -20778,8 +20788,8 @@ packages:
'@parcel/transformer-json': 2.8.2_@parcel+core@2.8.2
dev: false
/gatsby-plugin-page-creator/5.5.0-next.0_tiqeetwuawru2xunllrkp6n53m:
resolution: {integrity: sha512-OY0BOHl93aKqHCVwCFrABlGJFCW5T7tp5XqY5MPBmXRgCqWKVuZuZqgpHXoDTwjJSXMcn3sVzC5RhH7OgKIAiQ==}
/gatsby-plugin-page-creator/5.6.0-next.1_wxxlwg4kb5uwyfjvsdo3jviy2e:
resolution: {integrity: sha512-9yE2C4Xbc+llNRzzC9MbpgSM1GQCi4lUqlNy8Hq/C25S0wTTRW+FQZxuWTiR+AUTUL8MK9wJsNF5f0bZc8hfJw==}
engines: {node: '>=18.0.0'}
peerDependencies:
gatsby: ^5.0.0-next
@@ -20789,12 +20799,12 @@ packages:
'@sindresorhus/slugify': 1.1.2
chokidar: 3.5.3
fs-exists-cached: 1.0.0
fs-extra: 10.1.0
gatsby: 5.5.0-next.0_biqbaboplfbrettd7655fr4n2y
gatsby-core-utils: 4.5.0-next.0
gatsby-page-utils: 3.5.0-next.0
gatsby-plugin-utils: 4.5.0-next.0_tiqeetwuawru2xunllrkp6n53m
gatsby-telemetry: 4.5.0-next.0
fs-extra: 11.1.0
gatsby: 5.6.0-next.2_biqbaboplfbrettd7655fr4n2y
gatsby-core-utils: 4.6.0-next.0
gatsby-page-utils: 3.6.0-next.0
gatsby-plugin-utils: 4.6.0-next.1_wxxlwg4kb5uwyfjvsdo3jviy2e
gatsby-telemetry: 4.6.0-next.0
globby: 11.1.0
lodash: 4.17.21
transitivePeerDependencies:
@@ -20803,8 +20813,8 @@ packages:
- supports-color
dev: false
/gatsby-plugin-typescript/5.5.0-next.0_gatsby@5.5.0-next.0:
resolution: {integrity: sha512-Snj6bZC1hPf+tmhbSJ2UBUO6f2vd6AGvEF06M3Tk22jSpZZ3ia0wPCQzAWal03hV0QO2CTRaY/1ZVns7WSlDzg==}
/gatsby-plugin-typescript/5.6.0-next.0_gatsby@5.6.0-next.2:
resolution: {integrity: sha512-CUguJx8GjTQHymcfBOcwUXZtetfhLYtgUehkM8ovvNlscgavGxmbGD90gXMmo5JgrLPhdx8krtMsdIeCOOvo4w==}
engines: {node: '>=18.0.0'}
peerDependencies:
gatsby: ^5.0.0-next
@@ -20815,14 +20825,14 @@ packages:
'@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.12
'@babel/preset-typescript': 7.18.6_@babel+core@7.20.12
'@babel/runtime': 7.20.7
babel-plugin-remove-graphql-queries: 5.5.0-next.0_jqsf6f3mvfsscspe5xblmymkvm
gatsby: 5.5.0-next.0_biqbaboplfbrettd7655fr4n2y
babel-plugin-remove-graphql-queries: 5.6.0-next.0_x3fnz3zp5bgltr62wwyqvgm7l4
gatsby: 5.6.0-next.2_biqbaboplfbrettd7655fr4n2y
transitivePeerDependencies:
- supports-color
dev: false
/gatsby-plugin-utils/4.5.0-next.0_tiqeetwuawru2xunllrkp6n53m:
resolution: {integrity: sha512-PomEO3MOAEGAxhpiAVqvCd6FH4VNk5ce3J/2YDAY3mZcNl7443TzyBoLyebki27BY+cAhddYXx5dxsa23UplKw==}
/gatsby-plugin-utils/4.6.0-next.1_wxxlwg4kb5uwyfjvsdo3jviy2e:
resolution: {integrity: sha512-RhM2cQ6RYmdGQkXKs4d+KfG5ovGjvbF4fps/H4Sk81nJ1AnlPGbUlMaCdgFhhTJL1ID8l4rYdpQKXM87THJosQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
gatsby: ^5.0.0-next
@@ -20830,10 +20840,10 @@ packages:
dependencies:
'@babel/runtime': 7.20.7
fastq: 1.15.0
fs-extra: 10.1.0
gatsby: 5.5.0-next.0_biqbaboplfbrettd7655fr4n2y
gatsby-core-utils: 4.5.0-next.0
gatsby-sharp: 1.5.0-next.0
fs-extra: 11.1.0
gatsby: 5.6.0-next.2_biqbaboplfbrettd7655fr4n2y
gatsby-core-utils: 4.6.0-next.0
gatsby-sharp: 1.6.0-next.0
graphql: 16.6.0
graphql-compose: 9.0.10_graphql@16.6.0
import-from: 4.0.0
@@ -20841,8 +20851,8 @@ packages:
mime: 3.0.0
dev: false
/gatsby-react-router-scroll/6.5.0-next.0_y2kppt6lrltqk6wasg3eswwzsa:
resolution: {integrity: sha512-x8prKBH32JDvDVjo04j0XHZ1kXKcshqkTCUDQu/5yQ/l3dy485Ut/kXlTylbp591fw8uuEqjgvF5ElsBrmMlWg==}
/gatsby-react-router-scroll/6.6.0-next.0_y2kppt6lrltqk6wasg3eswwzsa:
resolution: {integrity: sha512-zSVEibH40eR64HROVbQ2OTV4sSDVXCgAEIG9yykhGJv5oz2yEFwWcjmPYWEtaiu2qcjPnLSkjsPOw5tVqrFr7A==}
engines: {node: '>=18.0.0'}
peerDependencies:
'@gatsbyjs/reach-router': ^2.0.0
@@ -20856,8 +20866,8 @@ packages:
react-dom: 18.2.0_react@18.2.0
dev: false
/gatsby-script/2.5.0-next.0_y2kppt6lrltqk6wasg3eswwzsa:
resolution: {integrity: sha512-eYFPQQ6QwjZxw8H4ojuJIFM62cFDos/CZFHxKAxyEvt9Qv1m2bzWCIK4PxRswESa0jAQYpbx+i/nLFA3P3zYsQ==}
/gatsby-script/2.6.0-next.0_y2kppt6lrltqk6wasg3eswwzsa:
resolution: {integrity: sha512-kJQ+NqtS4ue3I7t0Asgy+bAdnwQ0Wzu6BK3z/jnj57lU6mBthU5XWj9RiCu3W4GjsIW/qqjN1e/rqvukN+sWMw==}
engines: {node: '>=18.0.0'}
peerDependencies:
'@gatsbyjs/reach-router': ^2.0.0
@@ -20869,16 +20879,16 @@ packages:
react-dom: 18.2.0_react@18.2.0
dev: false
/gatsby-sharp/1.5.0-next.0:
resolution: {integrity: sha512-6ETKQa9CgNqpuxfrAovKxJsC8kwqbw3mhMTDBgD6ceeQr6GgKx9xG6jmNBbA6IxEiFvKpwz5cAIuJr8P5ylznw==}
/gatsby-sharp/1.6.0-next.0:
resolution: {integrity: sha512-QaGkqU55JaZAbjgU1QyrX0rbyRUEFluMT7LykLlfGMCdox4DzE5ERtP5BVIHCzRXc0JAZtmFCDcFmpPSKxB28w==}
engines: {node: '>=18.0.0'}
dependencies:
'@types/sharp': 0.31.1
sharp: 0.31.3
dev: false
/gatsby-telemetry/4.5.0-next.0:
resolution: {integrity: sha512-RcD7x7hnc9wOe9K0xvufQHLBjNW8xcQcf4e6DY7ffftBVIlYAudIlBe1n9BRtTUXOHPumHIGDhp97CyDv+m4HQ==}
/gatsby-telemetry/4.6.0-next.0:
resolution: {integrity: sha512-LQcqw21mO0s5sKPoi5MWYqcK7iVp5qej5fTpOYgDenabGDSIxYAoRa5CGQ6tRwGUjMu3NYykmCpPdsY0DHNHyw==}
engines: {node: '>=18.0.0'}
requiresBuild: true
dependencies:
@@ -20886,10 +20896,10 @@ packages:
'@babel/runtime': 7.20.7
'@turist/fetch': 7.2.0_node-fetch@2.6.7
'@turist/time': 0.0.2
boxen: 4.2.0
boxen: 5.1.2
configstore: 5.0.1
fs-extra: 10.1.0
gatsby-core-utils: 4.5.0-next.0
fs-extra: 11.1.0
gatsby-core-utils: 4.6.0-next.0
git-up: 7.0.0
is-docker: 2.2.1
lodash: 4.17.21
@@ -20898,20 +20908,20 @@ packages:
- encoding
dev: false
/gatsby-worker/2.5.0-next.0:
resolution: {integrity: sha512-u/IdMyXh1vxWNG19oq4KCD9p9tcYzQ3tFcG98Jai6phyzjIOvl4RIhV8jByTJ5YvupIWlOtfsrd4ra+cuGLAoA==}
/gatsby-worker/2.6.0-next.0:
resolution: {integrity: sha512-C2bBppw4MqtEo1pEUma2UE06txv22q+ix04G3idMxjqFLtWq+H3+fQmuDZB4laI601ClzyAHK99dSNKQrfGqeA==}
engines: {node: '>=18.0.0'}
dependencies:
'@babel/core': 7.20.12
'@babel/runtime': 7.20.7
fs-extra: 10.1.0
fs-extra: 11.1.0
signal-exit: 3.0.7
transitivePeerDependencies:
- supports-color
dev: false
/gatsby/5.5.0-next.0_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-5wR5rmmQ96PbMF3J4vNCwN7eIHcbrp3ReNVPzL6Pt673Pzlt3ga/Jaf6qUmeUORXaHpp5faDfWvcrfF9a92zQQ==}
/gatsby/5.6.0-next.2_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-cXyyDjlA2V3NhnfJtLhcrShrYU6k/FB67el5MZIqzrHjb7sg77lJztMpoy4PvXs4MlNvhRupSclIO+o9+G0pyg==}
engines: {node: '>=18.0.0'}
hasBin: true
requiresBuild: true
@@ -20927,7 +20937,7 @@ packages:
'@babel/runtime': 7.20.7
'@babel/traverse': 7.20.12
'@babel/types': 7.20.7
'@builder.io/partytown': 0.5.4
'@builder.io/partytown': 0.7.5
'@gatsbyjs/reach-router': 2.0.0_biqbaboplfbrettd7655fr4n2y
'@gatsbyjs/webpack-hot-middleware': 2.25.3
'@graphql-codegen/add': 3.2.3_graphql@16.6.0
@@ -20951,14 +20961,14 @@ packages:
address: 1.2.2
anser: 2.1.1
autoprefixer: 10.4.13_postcss@8.4.20
axios: 0.21.4_debug@3.2.7
axios: 0.21.4_debug@4.3.4
babel-jsx-utils: 1.1.0
babel-loader: 8.3.0_la66t7xldg4uecmyawueag5wkm
babel-plugin-add-module-exports: 1.0.4
babel-plugin-dynamic-import-node: 2.3.3
babel-plugin-lodash: 3.3.4
babel-plugin-remove-graphql-queries: 5.5.0-next.0_jqsf6f3mvfsscspe5xblmymkvm
babel-preset-gatsby: 3.5.0-next.0_pp2vm42zn6vfmnpuhar3irht7i
babel-plugin-remove-graphql-queries: 5.6.0-next.0_x3fnz3zp5bgltr62wwyqvgm7l4
babel-preset-gatsby: 3.6.0-next.0_pp2vm42zn6vfmnpuhar3irht7i
better-opn: 2.1.1
bluebird: 3.7.2
browserslist: 4.21.4
@@ -20967,14 +20977,14 @@ packages:
chokidar: 3.5.3
common-tags: 1.8.2
compression: 1.7.4
cookie: 0.4.2
cookie: 0.5.0
core-js: 3.26.0
cors: 2.8.5
css-loader: 5.2.7_webpack@5.75.0
css-minimizer-webpack-plugin: 2.0.0_webpack@5.75.0
css.escape: 1.5.1
date-fns: 2.29.3
debug: 3.2.7
debug: 4.3.4
deepmerge: 4.2.2
detect-port: 1.5.1
devcert: 1.2.2
@@ -20998,21 +21008,21 @@ packages:
file-loader: 6.2.0_webpack@5.75.0
find-cache-dir: 3.3.2
fs-exists-cached: 1.0.0
fs-extra: 10.1.0
gatsby-cli: 5.5.0-next.0
gatsby-core-utils: 4.5.0-next.0
gatsby-graphiql-explorer: 3.5.0-next.0
gatsby-legacy-polyfills: 3.5.0-next.0
gatsby-link: 5.5.0-next.0_y2kppt6lrltqk6wasg3eswwzsa
gatsby-page-utils: 3.5.0-next.0
gatsby-parcel-config: 1.5.0-next.0_@parcel+core@2.8.2
gatsby-plugin-page-creator: 5.5.0-next.0_tiqeetwuawru2xunllrkp6n53m
gatsby-plugin-typescript: 5.5.0-next.0_gatsby@5.5.0-next.0
gatsby-plugin-utils: 4.5.0-next.0_tiqeetwuawru2xunllrkp6n53m
gatsby-react-router-scroll: 6.5.0-next.0_y2kppt6lrltqk6wasg3eswwzsa
gatsby-script: 2.5.0-next.0_y2kppt6lrltqk6wasg3eswwzsa
gatsby-telemetry: 4.5.0-next.0
gatsby-worker: 2.5.0-next.0
fs-extra: 11.1.0
gatsby-cli: 5.6.0-next.1
gatsby-core-utils: 4.6.0-next.0
gatsby-graphiql-explorer: 3.6.0-next.0
gatsby-legacy-polyfills: 3.6.0-next.0
gatsby-link: 5.6.0-next.0_y2kppt6lrltqk6wasg3eswwzsa
gatsby-page-utils: 3.6.0-next.0
gatsby-parcel-config: 1.6.0-next.0_@parcel+core@2.8.2
gatsby-plugin-page-creator: 5.6.0-next.1_wxxlwg4kb5uwyfjvsdo3jviy2e
gatsby-plugin-typescript: 5.6.0-next.0_gatsby@5.6.0-next.2
gatsby-plugin-utils: 4.6.0-next.1_wxxlwg4kb5uwyfjvsdo3jviy2e
gatsby-react-router-scroll: 6.6.0-next.0_y2kppt6lrltqk6wasg3eswwzsa
gatsby-script: 2.6.0-next.0_y2kppt6lrltqk6wasg3eswwzsa
gatsby-telemetry: 4.6.0-next.0
gatsby-worker: 2.6.0-next.0
glob: 7.2.3
globby: 11.1.0
got: 11.8.6
@@ -21029,11 +21039,10 @@ packages:
latest-version: 7.0.0
lmdb: 2.5.3
lodash: 4.17.21
md5-file: 5.0.0
meant: 1.0.3
memoizee: 0.4.15
micromatch: 4.0.5
mime: 2.6.0
mime: 3.0.0
mini-css-extract-plugin: 1.6.2_webpack@5.75.0
mitt: 1.2.0
moment: 2.29.4
@@ -21083,11 +21092,11 @@ packages:
webpack-dev-middleware: 4.3.0_webpack@5.75.0
webpack-merge: 5.8.0
webpack-stats-plugin: 1.1.1
webpack-virtual-modules: 0.3.2
webpack-virtual-modules: 0.5.0
xstate: 4.35.2
yaml-loader: 0.8.0
optionalDependencies:
gatsby-sharp: 1.5.0-next.0
gatsby-sharp: 1.6.0-next.0
transitivePeerDependencies:
- '@swc/core'
- '@types/webpack'
@@ -21854,6 +21863,10 @@ packages:
resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==}
dev: true
/hash-wasm/4.9.0:
resolution: {integrity: sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==}
dev: false
/hasha/5.2.2:
resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==}
engines: {node: '>=8'}
@@ -22180,7 +22193,7 @@ packages:
engines: {node: '>=8.0.0'}
dependencies:
eventemitter3: 4.0.7
follow-redirects: 1.15.1
follow-redirects: 1.15.1_debug@4.3.4
requires-port: 1.0.0
transitivePeerDependencies:
- debug
@@ -25827,12 +25840,6 @@ packages:
hasBin: true
dev: true
/md5-file/5.0.0:
resolution: {integrity: sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: false
/mdast-squeeze-paragraphs/4.0.0:
resolution: {integrity: sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==}
dependencies:
@@ -27799,8 +27806,8 @@ packages:
resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
dev: false
/playwright-core/1.28.1:
resolution: {integrity: sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==}
/playwright-core/1.29.2:
resolution: {integrity: sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==}
engines: {node: '>=14'}
hasBin: true
dev: true
@@ -30471,7 +30478,7 @@ packages:
dependencies:
'@types/json-schema': 7.0.11
ajv: 8.11.0
ajv-formats: 2.1.1
ajv-formats: 2.1.1_ajv@8.11.0
ajv-keywords: 5.1.0_ajv@8.11.0
dev: true
@@ -32139,6 +32146,7 @@ packages:
/term-size/2.2.1:
resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==}
engines: {node: '>=8'}
dev: true
/terminal-link/2.1.1:
resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==}
@@ -34252,17 +34260,8 @@ packages:
resolution: {integrity: sha512-aWwE/YuO2W7VCOyWwyDJ7BRSYRYjeXat+X31YiasMM3FS6/4X9W4Mb9Q0g+jIdVgArr1Mb08sHBJKMT5M9+gVA==}
dev: false
/webpack-virtual-modules/0.3.2:
resolution: {integrity: sha512-RXQXioY6MhzM4CNQwmBwKXYgBs6ulaiQ8bkNQEl2J6Z+V+s7lgl/wGvaI/I0dLnYKB8cKsxQc17QOAVIphPLDw==}
dependencies:
debug: 3.2.7
transitivePeerDependencies:
- supports-color
dev: false
/webpack-virtual-modules/0.5.0:
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
dev: true
/webpack/5.73.0:
resolution: {integrity: sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==}

View File

@@ -25,6 +25,10 @@
"dependsOn": ["^build"],
"outputs": ["lib/**", "providers/**", "*.js", "*.d.ts", "*.d.ts.map"]
},
"@auth/sveltekit#build": {
"dependsOn": ["^build"],
"outputs": ["client.*", "index.*"]
},
"clean": {
"cache": false
},
@@ -34,6 +38,9 @@
"test": {
"outputs": []
},
"e2e": {
"outputs": ["playwright-report/**"]
},
"@next-auth/upstash-redis-adapter#test": {
"env": ["UPSTASH_REDIS_KEY", "UPSTASH_REDIS_URL"]
}