Compare commits

..

2 Commits

Author SHA1 Message Date
Balázs Orbán
be6a85d7b5 import Account as type 2022-09-14 14:15:48 +02:00
Balázs Orbán
946717523b feat(adapters): add Hasura Adapter 2022-09-14 14:05:26 +02:00
27 changed files with 619 additions and 1892 deletions

View File

@@ -1161,9 +1161,9 @@ isexe@^2.0.0:
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
jose@^4.1.4, jose@^4.3.7:
version "4.9.3"
resolved "https://registry.yarnpkg.com/jose/-/jose-4.9.3.tgz#890abd3f26725fe0f2aa720bc2f7835702b624db"
integrity sha512-f8E/z+T3Q0kA9txzH2DKvH/ds2uggcw0m3vVPSB9HrSkrQ7mojjifvS7aR8cw+lQl2Fcmx9npwaHpM/M3GD8UQ==
version "4.5.0"
resolved "https://registry.yarnpkg.com/jose/-/jose-4.5.0.tgz#92829d8cf846351eb55aaaf94f252fb1d191f2d5"
integrity sha512-GFcVFQwYQKbQTUOo2JlpFGXTkgBw26uzDsRMD2q1WgSKNSnpKS9Ug7bdQ8dS+p4sZHNH6iRPu6WK2jLIjspaMA==
js-yaml@^4.1.0:
version "4.1.0"

View File

@@ -67,7 +67,7 @@ export default function Component() {
Due to the way how Next.js handles `getServerSideProps` and `getInitialProps`, every protected page load has to make a server-side request to check if the session is valid and then generate the requested page (SSR). This increases server load, and if you are good with making the requests from the client, there is an alternative. You can use `useSession` in a way that makes sure you always have a valid session. If after the initial loading state there was no session found, you can define the appropriate action to respond.
The default behavior is to redirect the user to the sign-in page, from where - after a successful login - they will be sent back to the page they started on. You can also define an `onUnauthenticated()` callback, if you would like to do something else:
The default behavior is to redirect the user to the sign-in page, from where - after a successful login - they will be sent back to the page they started on. You can also define an `onFail()` callback, if you would like to do something else:
#### Example

View File

@@ -24,11 +24,7 @@ providers: [
AtlassianProvider({
clientId: process.env.ATLASSIAN_CLIENT_ID,
clientSecret: process.env.ATLASSIAN_CLIENT_SECRET,
authorization: {
params: {
scope: "write:jira-work read:jira-work read:jira-user offline_access read:me"
}
}
scope: "write:jira-work read:jira-work read:jira-user offline_access read:me"
})
]
...

View File

@@ -42,19 +42,13 @@ export default function Page() {
### Next.js (Middleware)
With NextAuth.js 4.2.0 and Next.js 12, you can now protect your pages via the middleware pattern more easily. If you would like to protect all pages, you can create a `_middleware.js` file in your root `pages` directory which looks like this:
With NextAuth.js 4.2.0 and Next.js 12, you can now protect your pages via the middleware pattern more easily. If you would like to protect all pages, you can create a `_middleware.js` file in your root `pages` directory which looks like this.
```js title="/middleware.js"
export { default } from "next-auth/middleware"
```
If you only want to secure certain pages, export a `config` object with a `matcher`:
```js
export { default } from "next-auth/middleware"
export const config = { matcher: ["/dashboard"] }
```
Otherwise, if you only want to protect a subset of pages, you could put it in a subdirectory as well, for example in `/pages/admin/_middleware.js` would protect all pages under `/admin`.
For the time being, the `withAuth` middleware only supports `"jwt"` as [session strategy](https://next-auth.js.org/configuration/options#session).

View File

@@ -7,7 +7,7 @@
"build:app": "turbo run build --filter=next-auth-app --include-dependencies",
"build": "turbo run build --filter=next-auth --filter=@next-auth/* --no-deps",
"lint": "turbo run lint --filter=!next-auth-docs --parallel",
"test": "turbo run test --concurrency=1 --filter=!@next-auth/pouchdb-adapter --filter=!@next-auth/upstash-redis-adapter --filter=!next-auth-* --filter=[HEAD^1]",
"test": "turbo run test --concurrency=1 --filter=!@next-auth/pouchdb-adapter --filter=!@next-auth/mikro-orm-adapter --filter=!@next-auth/upstash-redis-adapter --filter=!next-auth-* --filter=[HEAD^1]",
"clean": "turbo run clean --no-cache",
"dev:app": "turbo run dev --parallel --continue --filter=next-auth-app...",
"dev:docs": "turbo run dev --filter=next-auth-docs",

View File

@@ -0,0 +1,10 @@
<p align="center">
<br/>
<a href="https://next-auth.js.org" target="_blank"><img height="64px" src="https://next-auth.js.org/img/logo/logo-sm.png" /></a>&nbsp;&nbsp;&nbsp;&nbsp;<img height="64px" src="https://hasura.io/brand-assets/hasura-logo-primary-dark.svg" />
<h3 align="center"><b>Hasura Adapter</b> - NextAuth.js</h3>
<p align="center">
Open Source. Full Stack. Own Your Data.
</p>
</p>
TODO

View File

@@ -0,0 +1,44 @@
{
"name": "@next-auth/hasura-adapter",
"version": "1.0.0",
"description": "Hasura adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "Balázs Orbán <info@balazsorban.com>",
"contributors": [],
"main": "dist/index.js",
"files": [
"dist",
"index.d.ts"
],
"license": "ISC",
"keywords": [
"next-auth",
"next.js",
"hasura",
"graphql"
],
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "tsc --watch",
"build": "tsc",
"test": "./tests/test.sh"
},
"peerDependencies": {
"next-auth": "^4"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"next-auth": "workspace:*"
},
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}

View File

@@ -0,0 +1,49 @@
import type { Account } from "next-auth"
import type {
AdapterSession,
AdapterUser,
VerificationToken,
} from "next-auth/adapters"
export interface Model<T> {
name: string
fields: Array<keyof T>
}
export interface Models {
User: Model<AdapterUser>
Account: Model<Account>
Session: Model<AdapterSession>
VerificationToken: Model<VerificationToken>
}
export const models: Models = {
User: {
name: "User",
fields: ["email", "id", "image", "name", "emailVerified"],
},
Account: {
name: "Account",
fields: [
"id",
"type",
"provider",
"providerAccountId",
"expires_at",
"token_type",
"scope",
"access_token",
"refresh_token",
"id_token",
"session_state",
],
},
Session: {
name: "Session",
fields: ["expires", "id", "sessionToken"],
},
VerificationToken: {
name: "VerificationToken",
fields: ["identifier", "token", "expires"],
},
}

View File

@@ -0,0 +1 @@
# TODO

View File

@@ -0,0 +1,209 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-base-to-string */
import { format } from "./utils"
import { Models, Model, models } from "./graphql/models"
import type { Adapter, AdapterUser } from "next-auth/adapters"
import type { Account } from "next-auth"
export type { Models, Model }
export interface HasuraClientOptions {
/** GraphQL endpoint */
url: string
/**
* `X-Hasura-Admin-Secret` header value
*
* [Securing the GraphQL endpoint](https://hasura.io/docs/latest/deployment/securing-graphql-endpoint/)
*/
adminSecret: string
}
export interface HasuraAdapterOptions {
client: HasuraClientOptions
models?: Partial<Models>
}
export function HasuraAdapter(options: HasuraAdapterOptions): Adapter {
const c = client(options.client)
const m: Models = {
User: { ...models.User, ...options.models?.User },
Account: { ...models.Account, ...options.models?.Account },
Session: { ...models.Session, ...options.models?.Session },
VerificationToken: {
...models.VerificationToken,
...options.models?.VerificationToken,
},
}
return {
async createUser(data) {
const result = await c.run<AdapterUser[]>(
/* GraphQL */ `
mutation ($data: [${m.User.name}_insert_input!]!) {
insert_${m.User.name}(objects: $data) {
returning {
${m.User.fields.join(" ")}
}
}
}
`,
{ data }
)
return format.from(result?.[0])
},
async getUser(id) {
const result = await c.run<AdapterUser>(
/* GraphQL */ `
query ($id: String!) {
${m.User.name}(where: { id: { _eq: $id } }) {
${m.User.fields.join(" ")}
}
}
`,
{ id }
)
return format.from(result)
},
async getUserByEmail(email) {
const result = await c.run<AdapterUser[]>(
/* GraphQL */ `
query ($email: String!) {
${m.User.name}(where: { email: { _eq: $email } }) {
${m.User.fields.join(" ")}
}
}
`,
{ email }
)
return result?.[0] ?? null
},
async getUserByAccount(provider_providerAccountId) {
const result = await c.run<Array<AdapterUser & { Accounts: Account }>>(
/* GraphQL */ `
query getUserByAccount($providerAccountId: String!, $provider: String!) {
${m.User.name}(
where: {Accounts: {providerAccountId: {_eq: $providerAccountId}, provider: {_eq: $provider}}}
) {
${m.User.fields.join(" ")}
}
}
`,
provider_providerAccountId
)
if (!result?.length) return null
const { Accounts: _, ...user } = result[0]
return user
},
async updateUser({ id, ...input }) {
throw new HasuraAdapterError("`updateUser` not implemented")
},
async deleteUser(id) {
throw new HasuraAdapterError("`deleteUser` not implemented")
},
async linkAccount(data) {
throw new HasuraAdapterError("`linkAccount` not implemented")
},
async unlinkAccount(provider_providerAccountId) {
throw new HasuraAdapterError("`unlinkAccount` not implemented")
},
async getSessionAndUser(sessionToken) {
throw new HasuraAdapterError("`getSessionAndUser` not implemented")
},
async createSession(data) {
throw new HasuraAdapterError("`createSession` not implemented")
},
async updateSession({ sessionToken, ...input }) {
throw new HasuraAdapterError("`updateSession` not implemented")
},
async deleteSession(sessionToken) {
throw new HasuraAdapterError("`deleteSession` not implemented")
},
async createVerificationToken(input) {
throw new HasuraAdapterError("`createVerificationToken` not implemented")
},
async useVerificationToken(params) {
throw new HasuraAdapterError("`useVerificationToken` not implemented")
},
}
}
export class HasuraAdapterError extends Error {
name = "HasuraAdapterError"
query?: string
variables?: any
constructor(
e: Error | string | Array<Error | string>,
query?: string,
variables?: any
) {
// @ts-expect-error
super(Array.isArray(e) ? e.map((e) => e.message ?? e).join("\n") : e.m ?? e)
this.query = query
this.variables = variables
}
toJSON() {
return {
name: this.name,
message: this.message,
query: this.query,
variables: this.variables,
}
}
toString() {
const items = [
this.message,
this.query,
JSON.stringify(this.variables, null, 2),
].filter(Boolean)
return `${this.name}: ${items.join("\n")}`
}
}
export function client(options: HasuraClientOptions) {
if (!globalThis.fetch) {
throw new HasuraAdapterError("Please provide a `fetch` implementation")
}
const { url, adminSecret } = options
if (!adminSecret) {
throw new HasuraAdapterError("Please provide an API key")
}
if (!url) {
throw new HasuraAdapterError("Please provide a GraphQL endpoint")
}
return {
async run<T>(
query: string,
variables?: Record<string, any>
): Promise<T | null> {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Hasura-Admin-Secret": adminSecret,
},
body: JSON.stringify({ query, variables }),
})
const { data = {}, errors } = await response.json()
if (errors?.length) {
throw new HasuraAdapterError(errors, query, variables)
}
return Object.values(data)[0] as any
} catch (error) {
if (error instanceof HasuraAdapterError) throw error
throw new HasuraAdapterError(error as Error, query, variables)
}
},
}
}
export { format, models }

View File

@@ -0,0 +1,26 @@
// https://github.com/honeinc/is-iso-date/blob/master/index.js
const isoDateRE =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
function isDate(value: any) {
return value && isoDateRE.test(value) && !isNaN(Date.parse(value))
}
export const format = {
from<T extends Record<string, any> | null>(
data?: T
): T extends null ? null : T {
if (!data) return null as any
const newObject: Record<string, unknown> = {}
for (const key in data) {
const value = data[key]
if (isDate(value)) {
newObject[key] = new Date(value)
} else {
newObject[key] = value
}
}
return newObject as any
},
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": ["tests", "src"],
"exclude": [
"./*.js",
"./*.d.ts",
]
}

View File

@@ -0,0 +1,15 @@
{
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": [
"."
],
"exclude": [
"tests",
"dist",
"jest.config.js"
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/mikro-orm-adapter",
"version": "3.0.0",
"version": "2.0.1",
"description": "MikroORM adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -32,22 +32,22 @@
"dist"
],
"peerDependencies": {
"@mikro-orm/core": "^5",
"@mikro-orm/core": "^5.0.2",
"next-auth": "^4"
},
"devDependencies": {
"@mikro-orm/core": "^5",
"@mikro-orm/sqlite": "^5",
"@mikro-orm/core": "^5.0.2",
"@mikro-orm/sqlite": "^5.0.2",
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"@types/uuid": ">=8",
"jest": "^29",
"@types/uuid": "^8.3.3",
"jest": "^27.4.3",
"next-auth": "workspace:*"
},
"dependencies": {
"uuid": "^9"
},
"jest": {
"preset": "@next-auth/adapter-test/jest"
},
"dependencies": {
"uuid": "^8.3.2"
}
}
}

View File

@@ -9,7 +9,6 @@ import {
OneToMany,
Collection,
ManyToOne,
types,
} from "@mikro-orm/core"
import type { DefaultAccount } from "next-auth"
@@ -30,56 +29,55 @@ export class User implements RemoveIndex<AdapterUser> {
@PrimaryKey()
id: string = randomUUID()
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
name?: string
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
@Unique()
email?: string
@Property({ type: types.datetime, nullable: true })
@Property({ type: "Date", nullable: true })
emailVerified: Date | null = null
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
image?: string
@OneToMany({
entity: 'Session',
mappedBy: (session: Session) => session.user,
entity: () => Session,
mappedBy: (session) => session.user,
hidden: true,
orphanRemoval: true,
})
sessions = new Collection<Session, object>(this)
sessions = new Collection<Session>(this)
@OneToMany({
entity: 'Account',
mappedBy: (account: Account) => account.user,
entity: () => Account,
mappedBy: (account) => account.user,
hidden: true,
orphanRemoval: true,
})
accounts = new Collection<Account, object>(this)
accounts = new Collection<Account>(this)
}
@Entity()
export class Session implements AdapterSession {
@PrimaryKey()
@Property({ type: types.string })
id: string = randomUUID()
@ManyToOne({
entity: 'User',
entity: () => User,
hidden: true,
onDelete: "cascade",
})
user!: User
@Property({ type: types.string, persist: false })
@Property({ persist: false })
userId!: string
@Property({ type: 'Date' })
@Property()
expires!: Date
@Property({ type: types.string })
@Property()
@Unique()
sessionToken!: string
}
@@ -88,47 +86,46 @@ export class Session implements AdapterSession {
@Unique({ properties: ["provider", "providerAccountId"] })
export class Account implements RemoveIndex<DefaultAccount> {
@PrimaryKey()
@Property({ type: types.string })
id: string = randomUUID()
@ManyToOne({
entity: 'User',
entity: () => User,
hidden: true,
onDelete: "cascade",
})
user!: User
@Property({ type: types.string, persist: false })
@Property({ persist: false })
userId!: string
@Property({ type: types.string })
@Enum()
type!: ProviderType
@Property({ type: types.string })
@Property()
provider!: string
@Property({ type: types.string })
@Property()
providerAccountId!: string
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
refresh_token?: string
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
access_token?: string
@Property({ type: types.integer, nullable: true })
@Property({ nullable: true })
expires_at?: number
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
token_type?: string
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
scope?: string
@Property({ type: types.text, nullable: true })
@Property({ nullable: true })
id_token?: string
@Property({ type: types.string, nullable: true })
@Property({ nullable: true })
session_state?: string
}
@@ -136,12 +133,12 @@ export class Account implements RemoveIndex<DefaultAccount> {
@Unique({ properties: ["token", "identifier"] })
export class VerificationToken implements AdapterVerificationToken {
@PrimaryKey()
@Property({ type: types.string })
@Property()
token!: string
@Property({ type: 'Date' })
@Property()
expires!: Date
@Property({ type: types.string })
@Property()
identifier!: string
}

View File

@@ -10,7 +10,7 @@ import { MikroORM, wrap } from "@mikro-orm/core"
import * as defaultEntities from "./entities"
export { defaultEntities }
export * as defaultEntities from "./entities"
/**
* The MikroORM adapter accepts a MikroORM configuration and returns a NextAuth adapter.

View File

@@ -1,591 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`run migrations: createSchemaSQL 1`] = `
"pragma foreign_keys = off;
create table \`user\` (\`id\` text not null, \`name\` text null, \`email\` text null, \`email_verified\` datetime null, \`image\` text null, primary key (\`id\`));
create unique index \`user_email_unique\` on \`user\` (\`email\`);
create table \`session\` (\`id\` text not null, \`user_id\` text not null, \`expires\` datetime not null, \`session_token\` text not null, constraint \`session_user_id_foreign\` foreign key(\`user_id\`) references \`user\`(\`id\`) on delete cascade on update cascade, primary key (\`id\`));
create index \`session_user_id_index\` on \`session\` (\`user_id\`);
create unique index \`session_session_token_unique\` on \`session\` (\`session_token\`);
create table \`account\` (\`id\` text not null, \`user_id\` text not null, \`type\` text not null, \`provider\` text not null, \`provider_account_id\` text not null, \`refresh_token\` text null, \`access_token\` text null, \`expires_at\` integer null, \`token_type\` text null, \`scope\` text null, \`id_token\` text null, \`session_state\` text null, constraint \`account_user_id_foreign\` foreign key(\`user_id\`) references \`user\`(\`id\`) on delete cascade on update cascade, primary key (\`id\`));
create index \`account_user_id_index\` on \`account\` (\`user_id\`);
create unique index \`account_provider_provider_account_id_unique\` on \`account\` (\`provider\`, \`provider_account_id\`);
create table \`verification_token\` (\`token\` text not null, \`expires\` datetime not null, \`identifier\` text not null, primary key (\`token\`));
create unique index \`verification_token_token_identifier_unique\` on \`verification_token\` (\`token\`, \`identifier\`);
pragma foreign_keys = on;
"
`;
exports[`run migrations: targetSchema 1`] = `
{
"name": undefined,
"namespaces": [],
"tables": [
{
"checks": [],
"columns": {
"email": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "email",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"email_verified": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": 0,
"mappedType": "datetime",
"name": "email_verified",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "datetime",
"unsigned": false,
},
"id": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "id",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"image": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "image",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"name": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "name",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
},
"comment": undefined,
"foreignKeys": {},
"indexes": [
{
"columnNames": [
"email",
],
"composite": false,
"keyName": "user_email_unique",
"primary": false,
"unique": true,
},
{
"columnNames": [
"id",
],
"composite": false,
"expression": undefined,
"keyName": "primary",
"primary": true,
"type": undefined,
"unique": true,
},
],
"name": "user",
"schema": undefined,
},
{
"checks": [],
"columns": {
"expires": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": 0,
"mappedType": "datetime",
"name": "expires",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "datetime",
"unsigned": false,
},
"id": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "id",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"session_token": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "session_token",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"user_id": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "user_id",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
},
"comment": undefined,
"foreignKeys": {
"session_user_id_foreign": {
"columnNames": [
"user_id",
],
"constraintName": "session_user_id_foreign",
"deleteRule": "cascade",
"localTableName": "session",
"referencedColumnNames": [
"id",
],
"referencedTableName": "user",
"updateRule": "cascade",
},
},
"indexes": [
{
"columnNames": [
"user_id",
],
"composite": false,
"keyName": "session_user_id_index",
"primary": false,
"unique": false,
},
{
"columnNames": [
"session_token",
],
"composite": false,
"keyName": "session_session_token_unique",
"primary": false,
"unique": true,
},
{
"columnNames": [
"id",
],
"composite": false,
"expression": undefined,
"keyName": "primary",
"primary": true,
"type": undefined,
"unique": true,
},
],
"name": "session",
"schema": undefined,
},
{
"checks": [],
"columns": {
"access_token": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "access_token",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"expires_at": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "integer",
"name": "expires_at",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "integer",
"unsigned": false,
},
"id": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "id",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"id_token": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "id_token",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"provider": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "provider",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"provider_account_id": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "provider_account_id",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"refresh_token": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "refresh_token",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"scope": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "scope",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"session_state": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "session_state",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"token_type": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "token_type",
"nullable": true,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"type": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "type",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"user_id": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "user_id",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
},
"comment": undefined,
"foreignKeys": {
"account_user_id_foreign": {
"columnNames": [
"user_id",
],
"constraintName": "account_user_id_foreign",
"deleteRule": "cascade",
"localTableName": "account",
"referencedColumnNames": [
"id",
],
"referencedTableName": "user",
"updateRule": "cascade",
},
},
"indexes": [
{
"columnNames": [
"user_id",
],
"composite": false,
"keyName": "account_user_id_index",
"primary": false,
"unique": false,
},
{
"columnNames": [
"provider",
"provider_account_id",
],
"composite": true,
"expression": undefined,
"keyName": "account_provider_provider_account_id_unique",
"primary": false,
"type": undefined,
"unique": true,
},
{
"columnNames": [
"id",
],
"composite": false,
"expression": undefined,
"keyName": "primary",
"primary": true,
"type": undefined,
"unique": true,
},
],
"name": "account",
"schema": undefined,
},
{
"checks": [],
"columns": {
"expires": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": 0,
"mappedType": "datetime",
"name": "expires",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "datetime",
"unsigned": false,
},
"identifier": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "identifier",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
"token": {
"autoincrement": false,
"comment": undefined,
"default": undefined,
"enumItems": undefined,
"extra": undefined,
"length": undefined,
"mappedType": "text",
"name": "token",
"nullable": false,
"precision": undefined,
"primary": false,
"scale": undefined,
"type": "text",
"unsigned": false,
},
},
"comment": undefined,
"foreignKeys": {},
"indexes": [
{
"columnNames": [
"token",
"identifier",
],
"composite": true,
"expression": undefined,
"keyName": "verification_token_token_identifier_unique",
"primary": false,
"type": undefined,
"unique": true,
},
{
"columnNames": [
"token",
],
"composite": false,
"expression": undefined,
"keyName": "primary",
"primary": true,
"type": undefined,
"unique": true,
},
],
"name": "verification_token",
"schema": undefined,
},
],
}
`;

View File

@@ -1,69 +1,10 @@
import { Options, types } from "@mikro-orm/core"
import type { Options } from "@mikro-orm/core"
import type { SqliteDriver } from "@mikro-orm/sqlite"
import { MikroORM, wrap } from "@mikro-orm/core"
import { runBasicTests } from "@next-auth/adapter-test"
import { MikroOrmAdapter, defaultEntities } from "../src"
import {
Cascade,
Collection,
Entity,
OneToMany,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { randomUUID } from "@next-auth/adapter-test"
@Entity()
export class User implements defaultEntities.User {
@PrimaryKey()
@Property({ type: types.string })
id: string = randomUUID()
@Property({ type: types.string, nullable: true })
name?: string
@Property({ type: types.string, nullable: true })
@Unique()
email?: string
@Property({ type: 'Date', nullable: true })
emailVerified: Date | null = null
@Property({ type: types.string, nullable: true })
image?: string
@OneToMany({
entity: 'Session',
mappedBy: (session: defaultEntities.Session) => session.user,
hidden: true,
orphanRemoval: true,
cascade: [Cascade.ALL],
})
sessions = new Collection<defaultEntities.Session>(this)
@OneToMany({
entity: 'Account',
mappedBy: (account: defaultEntities.Account) => account.user,
hidden: true,
orphanRemoval: true,
cascade: [Cascade.ALL],
})
accounts = new Collection<defaultEntities.Account>(this)
@Property({ type: types.string, hidden: true })
role = "ADMIN"
}
@Entity()
export class VeryImportantEntity {
@PrimaryKey()
@Property({ type: types.string })
id: string = randomUUID()
@Property({ type: types.boolean })
important = true
}
import { User, VeryImportantEntity } from "./testEntities"
let _init: MikroORM

View File

@@ -1,28 +0,0 @@
import { MikroORM, Options } from "@mikro-orm/core";
import { SqliteDriver } from "@mikro-orm/sqlite";
import { defaultEntities } from "../src";
const config: Options<SqliteDriver> = {
dbName: "./db.sqlite",
type: "sqlite",
entities: [
defaultEntities.User,
defaultEntities.Account,
defaultEntities.Session,
defaultEntities.VerificationToken,
],
}
it("run migrations", async () => {
const orm = await MikroORM.init(config)
await orm.getSchemaGenerator().dropSchema()
const createSchemaSQL = await orm.getSchemaGenerator().getCreateSchemaSQL()
expect(createSchemaSQL).toMatchSnapshot('createSchemaSQL')
const targetSchema = await orm.getSchemaGenerator().getTargetSchema()
expect(targetSchema).toMatchSnapshot('targetSchema')
await orm.getSchemaGenerator().dropSchema()
await orm.close().catch(() => null)
})

View File

@@ -0,0 +1,61 @@
import {
Cascade,
Collection,
Entity,
OneToMany,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { randomUUID } from "@next-auth/adapter-test"
import type { defaultEntities } from "../src"
import { Account, Session } from "../src/entities"
@Entity()
export class User implements defaultEntities.User {
@PrimaryKey()
id: string = randomUUID()
@Property({ nullable: true })
name?: string
@Property({ nullable: true })
@Unique()
email?: string
@Property({ type: "Date", nullable: true })
emailVerified: Date | null = null
@Property({ nullable: true })
image?: string
@OneToMany({
entity: () => Session,
mappedBy: (session) => session.user,
hidden: true,
orphanRemoval: true,
cascade: [Cascade.ALL],
})
sessions = new Collection<Session>(this)
@OneToMany({
entity: () => Account,
mappedBy: (account) => account.user,
hidden: true,
orphanRemoval: true,
cascade: [Cascade.ALL],
})
accounts = new Collection<Account>(this)
@Property({ hidden: true })
role = "ADMIN"
}
@Entity()
export class VeryImportantEntity {
@PrimaryKey()
id: string = randomUUID()
@Property()
important = true
}

View File

@@ -5,4 +5,4 @@
"./*.js",
"./*.d.ts",
]
}
}

View File

@@ -8,6 +8,5 @@
"outDir": "dist",
"stripInternal": true
},
"include": ["src"],
"exclude": ["dist", "test", "node_modules"]
"exclude": ["tests", "dist", "jest.config.js"]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/mongodb-adapter",
"version": "1.1.0",
"version": "1.0.4",
"description": "mongoDB adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",

View File

@@ -1,6 +1,6 @@
{
"name": "next-auth",
"version": "4.11.0",
"version": "4.10.3",
"description": "Authentication for Next.js",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth.git",
@@ -69,7 +69,7 @@
"dependencies": {
"@babel/runtime": "^7.16.3",
"@panva/hkdf": "^1.0.1",
"cookie": "^0.5.0",
"cookie": "^0.4.1",
"jose": "^4.3.7",
"oauth": "^0.9.15",
"openid-client": "^5.1.0",
@@ -78,7 +78,6 @@
"uuid": "^8.3.2"
},
"peerDependencies": {
"next": "12.2.5",
"nodemailer": "^6.6.5",
"react": "^17.0.2 || ^18",
"react-dom": "^17.0.2 || ^18"
@@ -119,7 +118,7 @@
"jest-environment-jsdom": "^28.1.1",
"jest-watch-typeahead": "^1.1.0",
"msw": "^0.42.3",
"next": "12.2.5",
"next": "12.2.0",
"postcss": "^8.4.14",
"postcss-cli": "^9.1.0",
"postcss-nested": "^5.0.6",
@@ -130,4 +129,4 @@
"engines": {
"node": "^12.19.0 || ^14.15.0 || ^16.13.0"
}
}
}

View File

@@ -106,13 +106,12 @@ async function handleMiddleware(
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
const errorPage = options?.pages?.error ?? "/api/auth/error"
const basePath = parseUrl(process.env.NEXTAUTH_URL).path
const publicPaths = ["/_next", "/favicon.ico"]
const publicPaths = [signInPage, errorPage, "/_next", "/favicon.ico"]
// Avoid infinite redirects/invalid response
// on paths that never require authentication
if (
pathname.startsWith(basePath) ||
[signInPage, errorPage].includes(pathname) ||
publicPaths.some((p) => pathname.startsWith(p))
) {
return

View File

@@ -1,40 +0,0 @@
import { NextMiddleware } from "next/server"
import { NextAuthMiddlewareOptions, withAuth } from "../next/middleware"
it("should not match pages as public paths", async () => {
const options: NextAuthMiddlewareOptions = {
pages: {
signIn: "/",
error: "/"
},
secret: "secret"
}
const nextUrl: any = {
pathname: "/protected/pathA",
search: "",
origin: "http://127.0.0.1"
}
const req: any = { nextUrl, headers: { authorization: "" } }
const handleMiddleware = withAuth(options) as NextMiddleware
const res = await handleMiddleware(req, null)
expect(res).toBeDefined()
expect(res.status).toBe(307)
})
it("should not redirect on public paths", async () => {
const options: NextAuthMiddlewareOptions = {
secret: "secret"
}
const nextUrl: any = {
pathname: "/_next/foo",
search: "",
origin: "http://127.0.0.1"
}
const req: any = { nextUrl, headers: { authorization: "" } }
const handleMiddleware = withAuth(options) as NextMiddleware
const res = await handleMiddleware(req, null)
expect(res).toBeUndefined()
})

1236
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff