mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
11 Commits
fix/confor
...
fix/add-no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dd29bcc17 | ||
|
|
44c66b7406 | ||
|
|
294039a497 | ||
|
|
b2450ef625 | ||
|
|
a81bb3e51e | ||
|
|
bb506f7eb9 | ||
|
|
87d9cc4244 | ||
|
|
d99f9b714a | ||
|
|
d2e3b76031 | ||
|
|
c36834b3b0 | ||
|
|
8f7145801a |
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -35,6 +35,21 @@ jobs:
|
||||
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
- name: Run E2E tests
|
||||
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
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/dev/nextjs/playwright-report/
|
||||
retention-days: 30
|
||||
# - name: Coverage
|
||||
# uses: codecov/codecov-action@v1
|
||||
# with:
|
||||
|
||||
4
apps/dev/nextjs/.gitignore
vendored
Normal file
4
apps/dev/nextjs/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
@@ -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",
|
||||
|
||||
107
apps/dev/nextjs/playwright.config.ts
Normal file
107
apps/dev/nextjs/playwright.config.ts
Normal 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;
|
||||
39
apps/dev/nextjs/tests/signin.spec.ts
Normal file
39
apps/dev/nextjs/tests/signin.spec.ts
Normal 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()
|
||||
})
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -296,4 +296,16 @@ export interface AuthConfig {
|
||||
cookies?: Partial<CookiesOptions>
|
||||
/** @todo */
|
||||
trustHost?: boolean
|
||||
skipCSRFCheck?: typeof skipCSRFCheck
|
||||
}
|
||||
|
||||
/**
|
||||
* :::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")
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { SessionStore } from "./cookie.js"
|
||||
import { UnknownAction } from "../errors.js"
|
||||
import { skipCSRFCheck } from "../index.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 +20,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 +31,7 @@ export async function AuthInternal<
|
||||
csrfToken: request.body?.csrfToken,
|
||||
cookies: request.cookies,
|
||||
isPost: method === "POST",
|
||||
csrfDisabled,
|
||||
})
|
||||
|
||||
const sessionStore = new SessionStore(
|
||||
@@ -48,12 +52,22 @@ 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}${
|
||||
@@ -125,8 +139,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 +151,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 +162,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 }
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -104,7 +104,7 @@ export async function handleOAuth(
|
||||
resCookies.push(nonce.cookie)
|
||||
}
|
||||
|
||||
const codeGrantResponse = await o.authorizationCodeGrantRequest(
|
||||
let codeGrantResponse = await o.authorizationCodeGrantRequest(
|
||||
as,
|
||||
client,
|
||||
parameters,
|
||||
@@ -112,6 +112,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) {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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. */
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -69,4 +69,4 @@
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +126,5 @@
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0"
|
||||
}
|
||||
}
|
||||
|
||||
231
pnpm-lock.yaml
generated
231
pnpm-lock.yaml
generated
@@ -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
|
||||
@@ -129,6 +133,27 @@ importers:
|
||||
typescript: 4.9.4
|
||||
vite: 4.0.1
|
||||
|
||||
apps/examples/nextjs:
|
||||
specifiers:
|
||||
'@types/node': ^17
|
||||
'@types/react': ^18.0.15
|
||||
next: latest
|
||||
next-auth: latest
|
||||
nodemailer: ^6
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
typescript: ^4
|
||||
dependencies:
|
||||
next: 13.1.2_biqbaboplfbrettd7655fr4n2y
|
||||
next-auth: 4.18.8_xpfrgizk2uibqsegg5y3s2zbwy
|
||||
nodemailer: 6.8.0
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
devDependencies:
|
||||
'@types/node': 17.0.45
|
||||
'@types/react': 18.0.26
|
||||
typescript: 4.9.4
|
||||
|
||||
apps/playgrounds/gatsby:
|
||||
specifiers:
|
||||
dotenv: ^16.0.0
|
||||
@@ -562,7 +587,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 +601,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
|
||||
@@ -10610,6 +10635,10 @@ packages:
|
||||
/@next/env/13.1.1:
|
||||
resolution: {integrity: sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw==}
|
||||
|
||||
/@next/env/13.1.2:
|
||||
resolution: {integrity: sha512-PpT4UZIX66VMTqXt4HKEJ+/PwbS+tWmmhZlazaws1a+dbUA5pPdjntQ46Jvj616i3ZKN9doS9LHx3y50RLjAWg==}
|
||||
dev: false
|
||||
|
||||
/@next/swc-android-arm-eabi/13.1.1:
|
||||
resolution: {integrity: sha512-qnFCx1kT3JTWhWve4VkeWuZiyjG0b5T6J2iWuin74lORCupdrNukxkq9Pm+Z7PsatxuwVJMhjUoYz7H4cWzx2A==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10618,6 +10647,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm-eabi/13.1.2:
|
||||
resolution: {integrity: sha512-7mRz1owoGsbfIcdOJA3kk7KEwPZ+OvVT1z9DkR/yru4QdVLF69h/1SHy0vlUNQMxDRllabhxCfkoZCB34GOGAg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64/13.1.1:
|
||||
resolution: {integrity: sha512-eCiZhTzjySubNqUnNkQCjU3Fh+ep3C6b5DCM5FKzsTH/3Gr/4Y7EiaPZKILbvnXmhWtKPIdcY6Zjx51t4VeTfA==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10626,6 +10664,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-android-arm64/13.1.2:
|
||||
resolution: {integrity: sha512-mgjZ2eJSayovQm1LcE54BLSI4jjnnnLtq5GY5g+DdPuUiCT644gKtjZ/w2BQvuIecCqqBO+Ph9yzo/wUTq7NLg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/13.1.1:
|
||||
resolution: {integrity: sha512-9zRJSSIwER5tu9ADDkPw5rIZ+Np44HTXpYMr0rkM656IvssowPxmhK0rTreC1gpUCYwFsRbxarUJnJsTWiutPg==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10634,6 +10681,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-arm64/13.1.2:
|
||||
resolution: {integrity: sha512-RikoQqy109r2222UJlyGs4dZw2BibkfPqpeFdW5JEGv+L2PStlHID8DwyVYbmHfQ0VIBGvbf/NAUtFakAWlhwg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/13.1.1:
|
||||
resolution: {integrity: sha512-qWr9qEn5nrnlhB0rtjSdR00RRZEtxg4EGvicIipqZWEyayPxhUu6NwKiG8wZiYZCLfJ5KWr66PGSNeDMGlNaiA==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10642,6 +10698,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-darwin-x64/13.1.2:
|
||||
resolution: {integrity: sha512-JbDZjaTvL8gyPC5TAH6OnD4jmXPkyUxRYPvu08ZmhT/XAFBb/Cso0BdXyDax/BPCG70mimP9d3hXNKNq+A0VtQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-freebsd-x64/13.1.1:
|
||||
resolution: {integrity: sha512-UwP4w/NcQ7V/VJEj3tGVszgb4pyUCt3lzJfUhjDMUmQbzG9LDvgiZgAGMYH6L21MoyAATJQPDGiAMWAPKsmumA==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10650,6 +10715,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-freebsd-x64/13.1.2:
|
||||
resolution: {integrity: sha512-ax4j8VrdFQ/xc3W7Om0u1vnDxVApQHKsChBbAMynCrnycZmpbqK4MZu4ZkycT+mx2eccCiqZROpbzDbEdPosEw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/13.1.1:
|
||||
resolution: {integrity: sha512-CnsxmKHco9sosBs1XcvCXP845Db+Wx1G0qouV5+Gr+HT/ZlDYEWKoHVDgnJXLVEQzq4FmHddBNGbXvgqM1Gfkg==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10658,6 +10732,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm-gnueabihf/13.1.2:
|
||||
resolution: {integrity: sha512-NcRHTesnCxnUvSJa637PQJffBBkmqi5XS/xVWGY7dI6nyJ+pC96Oj7kd+mcjnFUQI5lHKbg39qBWKtOzbezc4w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/13.1.1:
|
||||
resolution: {integrity: sha512-JfDq1eri5Dif+VDpTkONRd083780nsMCOKoFG87wA0sa4xL8LGcXIBAkUGIC1uVy9SMsr2scA9CySLD/i+Oqiw==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10666,6 +10749,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-gnu/13.1.2:
|
||||
resolution: {integrity: sha512-AxJdjocLtPrsBY4P2COSBIc3crT5bpjgGenNuINoensOlXhBkYM0aRDYZdydwXOhG+kN2ngUvfgitop9pa204w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/13.1.1:
|
||||
resolution: {integrity: sha512-GA67ZbDq2AW0CY07zzGt07M5b5Yaq5qUpFIoW3UFfjOPgb0Sqf3DAW7GtFMK1sF4ROHsRDMGQ9rnT0VM2dVfKA==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10674,6 +10766,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-arm64-musl/13.1.2:
|
||||
resolution: {integrity: sha512-JmNimDkcCRq7P5zpkdqeaSZ69qKDntEPtyIaMNWqy5M0WUJxGim0Fs6Qzxayiyvuuh9Guxks4woQ/j/ZvX/c8Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/13.1.1:
|
||||
resolution: {integrity: sha512-nnjuBrbzvqaOJaV+XgT8/+lmXrSCOt1YYZn/irbDb2fR2QprL6Q7WJNgwsZNxiLSfLdv+2RJGGegBx9sLBEzGA==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10682,6 +10783,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-gnu/13.1.2:
|
||||
resolution: {integrity: sha512-TsLsjZwUlgmvI42neTuIoD6K9RlXCUzqPtvIClgXxVO0um0DiZwK+M+0zX/uVXhMVphfPY2c5YeR1zFSIONY4A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/13.1.1:
|
||||
resolution: {integrity: sha512-CM9xnAQNIZ8zf/igbIT/i3xWbQZYaF397H+JroF5VMOCUleElaMdQLL5riJml8wUfPoN3dtfn2s4peSr3azz/g==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10690,6 +10800,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-linux-x64-musl/13.1.2:
|
||||
resolution: {integrity: sha512-eSkyXgCXydEFPTkcncQOGepafedPte6JT/OofB9uvruucrrMVBagCASOuPxodWEMrlfEKSXVnExMKIlfmQMD7A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/13.1.1:
|
||||
resolution: {integrity: sha512-pzUHOGrbgfGgPlOMx9xk3QdPJoRPU+om84hqVoe6u+E0RdwOG0Ho/2UxCgDqmvpUrMab1Deltlt6RqcXFpnigQ==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10698,6 +10817,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-arm64-msvc/13.1.2:
|
||||
resolution: {integrity: sha512-DmXFaRTgt2KrV9dmRLifDJE+cYiutHVFIw5/C9BtnwXH39uf3YbPxeD98vNrtqqqZVVLXY/1ySaSIwzYnqeY9g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/13.1.1:
|
||||
resolution: {integrity: sha512-WeX8kVS46aobM9a7Xr/kEPcrTyiwJqQv/tbw6nhJ4fH9xNZ+cEcyPoQkwPo570dCOLz3Zo9S2q0E6lJ/EAUOBg==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10706,6 +10834,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-ia32-msvc/13.1.2:
|
||||
resolution: {integrity: sha512-3+nBkuFs/wT+lmRVQNH5SyDT7I4vUlNPntosEaEP63FuYQdPLaxz0GvcR66MdFSFh2fsvazpe4wciOwVS4FItQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/13.1.1:
|
||||
resolution: {integrity: sha512-mVF0/3/5QAc5EGVnb8ll31nNvf3BWpPY4pBb84tk+BfQglWLqc5AC9q1Ht/YMWiEgs8ALNKEQ3GQnbY0bJF2Gg==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -10714,6 +10851,15 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@next/swc-win32-x64-msvc/13.1.2:
|
||||
resolution: {integrity: sha512-avsyveEvcvH42PvKjR4Pb8JlLttuGURr2H3ZhS2b85pHOiZ7yjH3rMUoGnNzuLMApyxYaCvd4MedPrLhnNhkog==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@nicolo-ribaudo/chokidar-2/2.1.8-no-fsevents.3:
|
||||
resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==}
|
||||
requiresBuild: true
|
||||
@@ -11346,13 +11492,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:
|
||||
@@ -26547,6 +26693,33 @@ packages:
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dev: true
|
||||
|
||||
/next-auth/4.18.8_xpfrgizk2uibqsegg5y3s2zbwy:
|
||||
resolution: {integrity: sha512-USP8ihmvB7iCGtkS0+toe2QPrzdbZfkydQZX56JOI9Ft5n/BardOXh3D4wQ2An+vpq/jDKojGlgfv21wVElW7A==}
|
||||
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0}
|
||||
peerDependencies:
|
||||
next: ^12.2.5 || ^13
|
||||
nodemailer: ^6.6.5
|
||||
react: ^17.0.2 || ^18
|
||||
react-dom: ^17.0.2 || ^18
|
||||
peerDependenciesMeta:
|
||||
nodemailer:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.7
|
||||
'@panva/hkdf': 1.0.2
|
||||
cookie: 0.5.0
|
||||
jose: 4.11.1
|
||||
next: 13.1.2_biqbaboplfbrettd7655fr4n2y
|
||||
nodemailer: 6.8.0
|
||||
oauth: 0.9.15
|
||||
openid-client: 5.1.6
|
||||
preact: 10.11.3
|
||||
preact-render-to-string: 5.2.3_preact@10.11.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
uuid: 8.3.2
|
||||
dev: false
|
||||
|
||||
/next-tick/1.1.0:
|
||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||
|
||||
@@ -26638,6 +26811,50 @@ packages:
|
||||
- babel-plugin-macros
|
||||
dev: false
|
||||
|
||||
/next/13.1.2_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-Rdnnb2YH///w78FEOR/IQ6TXga+qpth4OqFSem48ng1PYYKr6XBsIk1XVaRcIGM3o6iiHnun0nJvkJHDf+ICyQ==}
|
||||
engines: {node: '>=14.6.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
fibers: '>= 3.1.0'
|
||||
node-sass: ^6.0.0 || ^7.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
sass: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
fibers:
|
||||
optional: true
|
||||
node-sass:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@next/env': 13.1.2
|
||||
'@swc/helpers': 0.4.14
|
||||
caniuse-lite: 1.0.30001431
|
||||
postcss: 8.4.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
styled-jsx: 5.1.1_react@18.2.0
|
||||
optionalDependencies:
|
||||
'@next/swc-android-arm-eabi': 13.1.2
|
||||
'@next/swc-android-arm64': 13.1.2
|
||||
'@next/swc-darwin-arm64': 13.1.2
|
||||
'@next/swc-darwin-x64': 13.1.2
|
||||
'@next/swc-freebsd-x64': 13.1.2
|
||||
'@next/swc-linux-arm-gnueabihf': 13.1.2
|
||||
'@next/swc-linux-arm64-gnu': 13.1.2
|
||||
'@next/swc-linux-arm64-musl': 13.1.2
|
||||
'@next/swc-linux-x64-gnu': 13.1.2
|
||||
'@next/swc-linux-x64-musl': 13.1.2
|
||||
'@next/swc-win32-arm64-msvc': 13.1.2
|
||||
'@next/swc-win32-ia32-msvc': 13.1.2
|
||||
'@next/swc-win32-x64-msvc': 13.1.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
dev: false
|
||||
|
||||
/nice-try/1.0.5:
|
||||
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
||||
|
||||
@@ -27799,8 +28016,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
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
"test": {
|
||||
"outputs": []
|
||||
},
|
||||
"e2e": {
|
||||
"outputs": ["playwright-report/**"]
|
||||
},
|
||||
"@next-auth/upstash-redis-adapter#test": {
|
||||
"env": ["UPSTASH_REDIS_KEY", "UPSTASH_REDIS_URL"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user