diff --git a/package.json b/package.json index 72f5b396d..ffadd92c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.4.0", + "version": "0.4.1", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/next-auth/.eslintrc.json b/packages/next-auth/.eslintrc.json new file mode 100644 index 000000000..0a913e874 --- /dev/null +++ b/packages/next-auth/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] +} diff --git a/packages/next-auth/README.md b/packages/next-auth/README.md new file mode 100644 index 000000000..b9ae1045b --- /dev/null +++ b/packages/next-auth/README.md @@ -0,0 +1,5 @@ +# ZenStack Runtime Library + +This package is for integrating [next-auth](https://next-auth.js.org/) with ZenStack. + +Visit [Homepage](https://zenstack.dev) for more details. diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json new file mode 100644 index 000000000..f2985eb60 --- /dev/null +++ b/packages/next-auth/package.json @@ -0,0 +1,46 @@ +{ + "name": "@zenstackhq/next-auth", + "displayName": "ZenStack next-auth integration library", + "version": "0.4.1", + "description": "ZenStack adapter for integrating with next-auth", + "repository": { + "type": "git", + "url": "https://github.com/zenstackhq/zenstack" + }, + "main": "index.js", + "scripts": { + "clean": "rimraf dist", + "build": "pnpm lint && pnpm clean && tsc && cp ./package.json ./README.md dist/", + "watch": "tsc --watch", + "lint": "eslint src --ext ts", + "prepublishOnly": "pnpm build", + "publish-dev": "pnpm publish --tag dev" + }, + "publishConfig": { + "directory": "dist", + "linkDirectory": true + }, + "author": { + "name": "ZenStack Team" + }, + "homepage": "https://zenstack.dev", + "license": "MIT", + "keywords": [ + "zenstack", + "next-auth" + ], + "dependencies": { + "@next-auth/prisma-adapter": "^1.0.5", + "@zenstackhq/runtime": "workspace:*", + "bcryptjs": "^2.4.3" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "next-auth": "^4.0.0", + "rimraf": "^3.0.2", + "typescript": "^4.9.3" + }, + "peerDependencies": { + "next-auth": "^4.0.0" + } +} diff --git a/packages/next-auth/src/adapter.ts b/packages/next-auth/src/adapter.ts new file mode 100644 index 000000000..9b347eeaa --- /dev/null +++ b/packages/next-auth/src/adapter.ts @@ -0,0 +1,11 @@ +import { Adapter } from 'next-auth/adapters'; +import type { Service } from '@zenstackhq/runtime/server'; +import { PrismaAdapter } from '@next-auth/prisma-adapter'; + +/** + * Next-auth adapter for reading and persisting auth entities + * @param service ZenStack service + */ +export function Adapter(service: Service): Adapter { + return PrismaAdapter(service.db); +} diff --git a/packages/next-auth/src/authorize.ts b/packages/next-auth/src/authorize.ts new file mode 100644 index 000000000..0f8e4ddc2 --- /dev/null +++ b/packages/next-auth/src/authorize.ts @@ -0,0 +1,59 @@ +import { Service } from '@zenstackhq/runtime/server'; +import { compare } from 'bcryptjs'; + +async function verifyPassword(password: string, hashedPassword: string) { + return compare(password, hashedPassword); +} + +export class AuthorizeError extends Error { + constructor(message: string) { + super(message); + } +} + +export function authorize(service: Service) { + return async ( + credentials: Record<'email' | 'password', string> | undefined + ) => { + if (!credentials) { + throw new AuthorizeError('Missing credentials'); + } + + if (!credentials.email) { + throw new AuthorizeError('"email" is required in credentials'); + } + + if (!credentials.password) { + throw new AuthorizeError('"password" is required in credentials'); + } + + const maybeUser = await service.db.user.findFirst({ + where: { + email: credentials.email, + }, + select: { + id: true, + email: true, + password: true, + }, + }); + + if (!maybeUser || !maybeUser.password) { + return null; + } + + const isValid = await verifyPassword( + credentials.password, + maybeUser.password + ); + + if (!isValid) { + return null; + } + + return { + id: maybeUser.id, + email: maybeUser.email, + }; + }; +} diff --git a/packages/next-auth/src/index.ts b/packages/next-auth/src/index.ts new file mode 100644 index 000000000..802ff07f9 --- /dev/null +++ b/packages/next-auth/src/index.ts @@ -0,0 +1,2 @@ +export * from './adapter'; +export * from './authorize'; diff --git a/packages/next-auth/tsconfig.json b/packages/next-auth/tsconfig.json new file mode 100644 index 000000000..36d8bdce1 --- /dev/null +++ b/packages/next-auth/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "lib": ["ESNext"], + "sourceMap": true, + "outDir": "dist", + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "resolveJsonModule": true, + "strictPropertyInitialization": false, + "paths": {} + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/runtime/README.md b/packages/runtime/README.md index 453a79769..a5cd746fa 100644 --- a/packages/runtime/README.md +++ b/packages/runtime/README.md @@ -2,4 +2,4 @@ This package is the runtime library supporting web apps built using ZenStack. -Visit [Homepage](https://github.com/zenstackhq/zenstack#readme) for more details. +Visit [Homepage](https://zenstack.dev) for more details. diff --git a/packages/runtime/package.json b/packages/runtime/package.json index e754cda8a..e049f633f 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "0.4.0", + "version": "0.4.1", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", @@ -9,7 +9,7 @@ }, "scripts": { "clean": "rimraf dist", - "build": "npm run clean && tsc && cp -r pre/* dist/ && cp ./package.json dist/", + "build": "pnpm lint && pnpm clean && tsc && cp -r pre/* dist/ && cp ./package.json ./README.md dist/", "watch": "tsc --watch", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build", @@ -39,6 +39,7 @@ "author": { "name": "ZenStack Team" }, + "homepage": "https://zenstack.dev", "license": "MIT", "devDependencies": { "@types/bcryptjs": "^2.4.2", diff --git a/packages/runtime/pre/server/index.d.ts b/packages/runtime/pre/server/index.d.ts index 13bc9471d..76946d7b4 100644 --- a/packages/runtime/pre/server/index.d.ts +++ b/packages/runtime/pre/server/index.d.ts @@ -6,6 +6,8 @@ export type { PolicyOperationKind, RuntimeAttribute, QueryContext, + Service, + DbClientContract, } from '../lib/types'; export { diff --git a/packages/schema/package.json b/packages/schema/package.json index a7418f642..f8fb3d6e1 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -2,11 +2,12 @@ "name": "zenstack", "publisher": "zenstack", "displayName": "ZenStack Language Tools", - "description": "A toolkit for modeling data and access policies in full-stack development with Next.js and Typescript", - "version": "0.4.0", + "description": "A toolkit for building secure CRUD apps with Next.js + Typescript", + "version": "0.4.1", "author": { "name": "ZenStack Team" }, + "homepage": "https://zenstack.dev", "license": "MIT", "keywords": [ "fullstack", diff --git a/packages/schema/src/generator/index.ts b/packages/schema/src/generator/index.ts index 23265586f..96df4b145 100644 --- a/packages/schema/src/generator/index.ts +++ b/packages/schema/src/generator/index.ts @@ -5,7 +5,6 @@ import colors from 'colors'; import PrismaGenerator from './prisma'; import ServiceGenerator from './service'; import ReactHooksGenerator from './react-hooks'; -import NextAuthGenerator from './next-auth'; import { TypescriptCompilation } from './tsc'; import FieldConstraintGenerator from './field-constraint'; import telemetry from '../telemetry'; @@ -46,7 +45,6 @@ export class ZenStackGenerator { new PrismaGenerator(), new ServiceGenerator(), new ReactHooksGenerator(), - new NextAuthGenerator(), new FieldConstraintGenerator(), new TypescriptCompilation(), ]; diff --git a/packages/schema/src/generator/next-auth/index.ts b/packages/schema/src/generator/next-auth/index.ts deleted file mode 100644 index fb81983a2..000000000 --- a/packages/schema/src/generator/next-auth/index.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { Context, Generator } from '../types'; -import { Project } from 'ts-morph'; -import * as path from 'path'; -import colors from 'colors'; -import { DataModel, isDataModel, Model } from '@lang/generated/ast'; -import { execSync } from 'child_process'; - -/** - * Generates NextAuth adaptor code - */ -export default class NextAuthGenerator implements Generator { - get name() { - return 'next-auth'; - } - - private findModel(schema: Model, name: string) { - return schema.declarations.find( - (d) => isDataModel(d) && d.name === name - ) as DataModel; - } - - private modelHasField(model: DataModel, name: string) { - return !!model.fields.find((f) => f.name === name); - } - - async generate(context: Context): Promise { - try { - execSync('npm ls next-auth'); - } catch (err) { - return; - } - - if (!this.findModel(context.schema, 'User')) { - console.warn( - colors.yellow( - 'Skipping generating next-auth adapter: "User" model not found.' - ) - ); - return; - } - - const userModel = this.findModel(context.schema, 'User'); - if ( - !this.modelHasField(userModel, 'email') || - !this.modelHasField(userModel, 'emailVerified') - ) { - console.warn( - colors.yellow( - `Skipping generating next-auth adapter because "User" model doesn't meet requirements: "email" and "emailVerified" fields are required.` - ) - ); - return; - } - - const project = new Project(); - - this.generateIndex(project, context); - this.generateAdapter(project, context); - this.generateAuthorize(project, context); - - await project.save(); - - console.log(colors.blue(` ✔️ Next-auth adapter generated`)); - } - - private generateIndex(project: Project, context: Context) { - const sf = project.createSourceFile( - path.join(context.generatedCodeDir, 'src/auth/index.ts'), - undefined, - { overwrite: true } - ); - - sf.addStatements([ - `export * from './next-auth-adapter';`, - `export * from './authorize';`, - ]); - - sf.formatText(); - } - - private generateAdapter(project: Project, context: Context) { - const sf = project.createSourceFile( - path.join( - context.generatedCodeDir, - 'src/auth/next-auth-adapter.ts' - ), - undefined, - { overwrite: true } - ); - - sf.addImportDeclarations([ - { - namedImports: [ - { - name: 'ZenStackService', - }, - ], - moduleSpecifier: '..', - }, - { - namedImports: [ - { - name: 'Adapter', - }, - ], - moduleSpecifier: 'next-auth/adapters', - }, - { - namedImports: [ - { - name: 'Prisma', - }, - ], - moduleSpecifier: '../../.prisma', - isTypeOnly: true, - }, - ]); - - const adapter = sf.addFunction({ - name: 'NextAuthAdapter', - isExported: true, - returnType: 'Adapter', - }); - - adapter.addParameter({ - name: 'service', - type: 'ZenStackService', - }); - - const userModel = this.findModel(context.schema, 'User'); - - adapter.setBodyText((writer) => { - writer.writeLine('const db = service.db;'); - writer.write('return '); - writer.block(() => { - writer.writeLine( - ` - createUser: (data) => db.user.create({ data: data as Prisma.UserCreateInput }), - getUser: (id) => db.user.findUnique({ where: { id } }), - updateUser: (data) => db.user.update({ where: { id: data.id }, data: data as Prisma.UserUpdateInput }), - deleteUser: (id) => db.user.delete({ where: { id } }), - ` - ); - - if (this.modelHasField(userModel, 'email')) { - writer.writeLine( - 'getUserByEmail: (id) => db.user.findUnique({ where: { id } }),' - ); - } else { - writer.writeLine( - `getUserByEmail: (id) => { throw new Error('"User" model has no "email" field'); },` - ); - } - - if (this.findModel(context.schema, 'Account')) { - writer.writeLine( - ` - async getUserByAccount(provider_providerAccountId) { - const account = await db.account.findUnique({ - where: { provider_providerAccountId }, - select: { user: true }, - }); - return account?.user ?? null; - }, - linkAccount: (data) => db.account.create({ data }) as any, - unlinkAccount: (provider_providerAccountId) => - db.account.delete({ where: { provider_providerAccountId } }) as any, - ` - ); - } else { - writer.writeLine( - ` - async getUserByAccount(provider_providerAccountId) { throw new Error('Schema has no "Account" model declared'); }, - linkAccount: (data) => { throw new Error('Schema has no "Account" model declared'); }, - unlinkAccount: (provider_providerAccountId) => { throw new Error('Schema has no "Account" model declared'); }, - ` - ); - } - - if (this.findModel(context.schema, 'Session')) { - writer.writeLine( - ` - async getSessionAndUser(sessionToken) { - const userAndSession = await db.session.findUnique({ - where: { sessionToken }, - include: { user: true }, - }); - if (!userAndSession) return null; - const { user, ...session } = userAndSession; - return { user, session }; - }, - createSession: (data) => db.session.create({ data }), - updateSession: (data) => - db.session.update({ - data, - where: { sessionToken: data.sessionToken }, - }), - deleteSession: (sessionToken) => - db.session.delete({ where: { sessionToken } }), - ` - ); - } else { - writer.writeLine( - ` - async getSessionAndUser(sessionToken) { throw new Error('Schema has no "Session" model declared'); }, - createSession: (data) => { throw new Error('Schema has no "Session" model declared'); }, - updateSession: (data) => { throw new Error('Schema has no "Session" model declared'); }, - deleteSession: (sessionToken) => { throw new Error('Schema has no "Session" model declared'); }, ` - ); - } - - if (this.findModel(context.schema, 'VerificationToken')) { - writer.writeLine( - ` - createVerificationToken: (data) => db.verificationToken.create({ data }), - async useVerificationToken(identifier_token) { - try { - return await db.verificationToken.delete({ - where: { identifier_token }, - }); - } catch (error) { - // If token already used/deleted, just return null - // https://www.prisma.io/docs/reference/api-reference/error-reference#p2025 - if ( - (error as Prisma.PrismaClientKnownRequestError).code === - 'P2025' - ) - return null; - throw error; - } - }, - ` - ); - } else { - writer.writeLine( - ` - createVerificationToken: (data) => { throw new Error('Schema has no "VerificationToken" model declared'); }, - async useVerificationToken(identifier_token) { throw new Error('Schema has no "VerificationToken" model declared'); }, ` - ); - } - }); - }); - - sf.formatText(); - } - - private generateAuthorize(project: Project, context: Context) { - const userModel = this.findModel(context.schema, 'User'); - const hasEmail = userModel && this.modelHasField(userModel, 'email'); - const hasPassword = - userModel && this.modelHasField(userModel, 'password'); - - let content = ''; - if (!hasEmail || !hasPassword) { - content = ` - import { ZenStackService } from '..'; - - export function authorize(service: ZenStackService, implicitSignup = false) { - throw new Error('"User" model must have "email" and "password" field'); - } - `; - } else { - content = ` - import { ZenStackService } from '..'; - import { hash, compare } from 'bcryptjs'; - - async function hashPassword(password: string) { - const hashedPassword = await hash(password, 12); - return hashedPassword; - } - - async function verifyPassword(password: string, hashedPassword: string) { - const isValid = await compare(password, hashedPassword); - return isValid; - } - - export function authorize(service: ZenStackService, implicitSignup = false) { - return async ( - credentials: Record<'email' | 'password', string> | undefined - ) => { - if (!credentials) { - throw new Error('Missing credentials'); - } - - try { - let maybeUser = await service.db.user.findFirst({ - where: { - email: credentials!.email, - }, - select: { - id: true, - email: true, - password: true, - name: true, - }, - }); - - if (!maybeUser) { - if (!implicitSignup || !credentials.password || !credentials.email) { - return null; - } - - maybeUser = await service.db.user.create({ - data: { - email: credentials.email, - password: await hashPassword(credentials.password), - }, - select: { - id: true, - email: true, - password: true, - name: true, - }, - }); - } else { - if (!maybeUser.password) { - throw new Error('Invalid User Record'); - } - - const isValid = await verifyPassword( - credentials.password, - maybeUser.password - ); - - if (!isValid) { - return null; - } - } - - return { - id: maybeUser.id, - email: maybeUser.email, - name: maybeUser.name, - }; - } catch (error) { - console.log('Error occurred during authorization:', error); - throw error; - } - }; - } - `; - } - - const sf = project.createSourceFile( - path.join(context.generatedCodeDir, 'src/auth/authorize.ts'), - content, - { overwrite: true } - ); - - sf.formatText(); - } -} diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index 9b436a05c..4a8078cb4 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -54,7 +54,7 @@ enum AttributeTargetField { function env(name: String): String {} /* - * Gets thec current login user. + * Gets the current login user. */ function auth(): Any {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75499068e..49c2b3b0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,26 @@ importers: devDependencies: '@changesets/cli': 2.25.2 + packages/next-auth: + specifiers: + '@next-auth/prisma-adapter': ^1.0.5 + '@types/bcryptjs': ^2.4.2 + '@zenstackhq/runtime': workspace:* + bcryptjs: ^2.4.3 + next-auth: ^4.0.0 + rimraf: ^3.0.2 + typescript: ^4.9.3 + dependencies: + '@next-auth/prisma-adapter': 1.0.5_w3qkztkwfy6eylhe7n5itugygy + '@zenstackhq/runtime': link:../runtime/dist + bcryptjs: 2.4.3 + devDependencies: + '@types/bcryptjs': 2.4.2 + next-auth: 4.17.0_azq6kxkn3od7qdylwkyksrwopy + rimraf: 3.0.2 + typescript: 4.9.3 + publishDirectory: dist + packages/runtime: specifiers: '@types/bcryptjs': ^2.4.2 @@ -689,7 +709,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 - dev: true /@babel/template/7.18.10: resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} @@ -1508,6 +1527,16 @@ packages: read-yaml-file: 1.1.0 dev: true + /@next-auth/prisma-adapter/1.0.5_w3qkztkwfy6eylhe7n5itugygy: + resolution: {integrity: sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==} + peerDependencies: + '@prisma/client': '>=2.26.0 || >=3' + next-auth: ^4 + dependencies: + '@prisma/client': 4.7.1 + next-auth: 4.17.0_azq6kxkn3od7qdylwkyksrwopy + dev: false + /@next/env/12.3.1: resolution: {integrity: sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==} @@ -1676,6 +1705,22 @@ packages: engines: {node: '>=14'} dev: true + /@panva/hkdf/1.0.2: + resolution: {integrity: sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==} + + /@prisma/client/4.7.1: + resolution: {integrity: sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==} + engines: {node: '>=14.17'} + requiresBuild: true + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + '@prisma/engines-version': 4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c + dev: false + /@prisma/debug/4.7.0: resolution: {integrity: sha512-KdfL70X2OgYMrweEiXa9pZReAPbsYbCbFAyT8Ud/8+w+zI1xOWpFZ4watK95ambK0f6/2p1kP6WGHGKOor1imA==} dependencies: @@ -1707,6 +1752,10 @@ packages: - supports-color dev: true + /@prisma/engines-version/4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c: + resolution: {integrity: sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==} + dev: false + /@prisma/engines/4.7.0: resolution: {integrity: sha512-afKrVFktaZ1pOK12/uFl2hRsBWIJZuC5FdDtacuKk5x/mR+rC5AbA+PlN3ZCZbmYTaeiBMHjcU5wbT5z2N3nSQ==} requiresBuild: true @@ -2957,6 +3006,10 @@ packages: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: true + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + /cookiejar/2.1.3: resolution: {integrity: sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==} dev: true @@ -5410,6 +5463,9 @@ packages: - ts-node dev: true + /jose/4.11.1: + resolution: {integrity: sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==} + /js-sdsl/4.1.5: resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==} dev: true @@ -5846,6 +5902,31 @@ packages: engines: {node: '>=10'} dev: true + /next-auth/4.17.0_azq6kxkn3od7qdylwkyksrwopy: + resolution: {integrity: sha512-aN2tdnjS0MDeUpB2tBDOaWnegkgeMWrsccujbXRGMJ607b+EwRcy63MFGSr0OAboDJEe0902piXQkt94GqF8Qw==} + 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.1 + '@panva/hkdf': 1.0.2 + cookie: 0.5.0 + jose: 4.11.1 + next: 12.3.1_biqbaboplfbrettd7655fr4n2y + oauth: 0.9.15 + openid-client: 5.3.1 + preact: 10.11.3 + preact-render-to-string: 5.2.6_preact@10.11.3 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + uuid: 8.3.2 + /next/12.3.1_6tziyx3dehkoeijunclpkpolha: resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==} engines: {node: '>=12.22.0'} @@ -5934,7 +6015,6 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - dev: false /no-case/3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -6005,6 +6085,13 @@ packages: boolbase: 1.0.0 dev: true + /oauth/0.9.15: + resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} + + /object-hash/2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} dev: true @@ -6024,6 +6111,10 @@ packages: object-keys: 1.1.1 dev: true + /oidc-token-hash/5.0.1: + resolution: {integrity: sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==} + engines: {node: ^10.13.0 || >=12.0.0} + /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -6045,6 +6136,14 @@ packages: is-wsl: 2.2.0 dev: true + /openid-client/5.3.1: + resolution: {integrity: sha512-RLfehQiHch9N6tRWNx68cicf3b1WR0x74bJWHRc25uYIbSRwjxYcTFaRnzbbpls5jroLAaB/bFIodTgA5LJMvw==} + dependencies: + jose: 4.11.1 + lru-cache: 6.0.0 + object-hash: 2.2.0 + oidc-token-hash: 5.0.1 + /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -6273,6 +6372,17 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /preact-render-to-string/5.2.6_preact@10.11.3: + resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} + peerDependencies: + preact: '>=10' + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + + /preact/10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + /prebuild-install/7.1.1: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} engines: {node: '>=10'} @@ -6331,6 +6441,9 @@ packages: react-is: 18.2.0 dev: true + /pretty-format/3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + /prisma/4.7.0: resolution: {integrity: sha512-VsecNo0Ca3+bDTzSpJqIpdupKVhhQ8aOYeWc09JlUM89knqvhSrlMrg0U8BiOD4tFrY1OPaCcraK8leDBxKMBg==} engines: {node: '>=14.17'} @@ -6516,7 +6629,6 @@ packages: /regenerator-runtime/0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - dev: true /regexp-to-ast/0.5.0: resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} @@ -6961,7 +7073,6 @@ packages: optional: true dependencies: react: 18.2.0 - dev: false /superagent/8.0.2: resolution: {integrity: sha512-QtYZ9uaNAMexI7XWl2vAXAh0j4q9H7T0WVEI/y5qaUB3QLwxo+voUgCQ217AokJzUTIVOp0RTo7fhZrwhD7A2Q==} @@ -7519,7 +7630,6 @@ packages: /uuid/8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - dev: true /uuid/9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} diff --git a/samples/todo/package-lock.json b/samples/todo/package-lock.json index cb2563a39..9667e6717 100644 --- a/samples/todo/package-lock.json +++ b/samples/todo/package-lock.json @@ -1,16 +1,17 @@ { "name": "todo", - "version": "0.3.23", + "version": "0.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "todo", - "version": "0.3.23", + "version": "0.4.1", "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.7.0", - "@zenstackhq/runtime": "^0.3.23", + "@zenstackhq/next-auth": "^0.4.1", + "@zenstackhq/runtime": "^0.4.1", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -33,7 +34,7 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.3.23" + "zenstack": "^0.4.1" } }, "../../packages/runtime": { @@ -253,6 +254,15 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@next-auth/prisma-adapter": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@next-auth/prisma-adapter/-/prisma-adapter-1.0.5.tgz", + "integrity": "sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==", + "peerDependencies": { + "@prisma/client": ">=2.26.0 || >=3", + "next-auth": "^4" + } + }, "node_modules/@next/env": { "version": "12.3.1", "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", @@ -749,10 +759,23 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@zenstackhq/next-auth": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@zenstackhq/next-auth/-/next-auth-0.4.1.tgz", + "integrity": "sha512-BbBydqrwgMfVnAaK16i6S6sxi5ovu2l+2JJvG8gCeap02+yQdKE7+iUvYvDRZCsU3dpILyPaBW7j1FuKTnPzdA==", + "dependencies": { + "@next-auth/prisma-adapter": "^1.0.5", + "@zenstackhq/runtime": "0.4.1", + "bcryptjs": "^2.4.3" + }, + "peerDependencies": { + "next-auth": "^4.0.0" + } + }, "node_modules/@zenstackhq/runtime": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.3.23.tgz", - "integrity": "sha512-p6QDSjn2I5hDLhz0qK2//9XTno2YpP2C9x8ak9GzoV4XOJcUua7bNvSpRjJ25n15VYzTnelXndsruZjDTeoCaw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.4.1.tgz", + "integrity": "sha512-jOOlHin15vet3cP2lEedRsnelXdcAhZ/WmfuwT1UpgPisHzkVZMJTPmRacxWaL5uMWhJZ0cDUVaKcEQpFwGLdA==", "dependencies": { "@types/bcryptjs": "^2.4.2", "bcryptjs": "^2.4.3", @@ -4603,13 +4626,13 @@ } }, "node_modules/zenstack": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.3.23.tgz", - "integrity": "sha512-sJMIVxNpxWGozFbs2kndyLM9NPLucdHvjYASfGRi8tp9FrRg2d4Ji2qDC2W5C7sqIvWrUltxpuoynbdjNljhHA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.4.1.tgz", + "integrity": "sha512-Z55ViVb8JWCpo0fVEN/PnEuTFX/mgnw6WMPq/ddWU30lMfcnZhS37msTC/ZzQxLssKvwB2cMA86cmbK8to+JrA==", "dev": true, "hasInstallScript": true, "dependencies": { - "@zenstackhq/runtime": "0.3.23", + "@zenstackhq/runtime": "0.4.1", "async-exit-hook": "^2.0.1", "change-case": "^4.1.2", "chevrotain": "^9.1.0", @@ -4825,6 +4848,12 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@next-auth/prisma-adapter": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@next-auth/prisma-adapter/-/prisma-adapter-1.0.5.tgz", + "integrity": "sha512-VqMS11IxPXrPGXw6Oul6jcyS/n8GLOWzRMrPr3EMdtD6eOalM6zz05j08PcNiis8QzkfuYnCv49OvufTuaEwYQ==", + "requires": {} + }, "@next/env": { "version": "12.3.1", "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", @@ -5127,10 +5156,20 @@ } } }, + "@zenstackhq/next-auth": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@zenstackhq/next-auth/-/next-auth-0.4.1.tgz", + "integrity": "sha512-BbBydqrwgMfVnAaK16i6S6sxi5ovu2l+2JJvG8gCeap02+yQdKE7+iUvYvDRZCsU3dpILyPaBW7j1FuKTnPzdA==", + "requires": { + "@next-auth/prisma-adapter": "^1.0.5", + "@zenstackhq/runtime": "0.4.1", + "bcryptjs": "^2.4.3" + } + }, "@zenstackhq/runtime": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.3.23.tgz", - "integrity": "sha512-p6QDSjn2I5hDLhz0qK2//9XTno2YpP2C9x8ak9GzoV4XOJcUua7bNvSpRjJ25n15VYzTnelXndsruZjDTeoCaw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@zenstackhq/runtime/-/runtime-0.4.1.tgz", + "integrity": "sha512-jOOlHin15vet3cP2lEedRsnelXdcAhZ/WmfuwT1UpgPisHzkVZMJTPmRacxWaL5uMWhJZ0cDUVaKcEQpFwGLdA==", "requires": { "@types/bcryptjs": "^2.4.2", "bcryptjs": "^2.4.3", @@ -7925,12 +7964,12 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "zenstack": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.3.23.tgz", - "integrity": "sha512-sJMIVxNpxWGozFbs2kndyLM9NPLucdHvjYASfGRi8tp9FrRg2d4Ji2qDC2W5C7sqIvWrUltxpuoynbdjNljhHA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/zenstack/-/zenstack-0.4.1.tgz", + "integrity": "sha512-Z55ViVb8JWCpo0fVEN/PnEuTFX/mgnw6WMPq/ddWU30lMfcnZhS37msTC/ZzQxLssKvwB2cMA86cmbK8to+JrA==", "dev": true, "requires": { - "@zenstackhq/runtime": "0.3.23", + "@zenstackhq/runtime": "0.4.1", "async-exit-hook": "^2.0.1", "change-case": "^4.1.2", "chevrotain": "^9.1.0", diff --git a/samples/todo/package.json b/samples/todo/package.json index 6b66745fd..0f84cd9ae 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.4.0", + "version": "0.4.1", "private": true, "scripts": { "dev": "next dev", @@ -14,14 +14,15 @@ "db:browse": "zenstack studio", "generate": "zenstack generate", "vercel-build": "npm run build && npm run db:deploy", - "deps-local": "npm i -D ../../packages/schema && npm i ../../packages/runtime/dist", - "deps-latest": "npm i -D zenstack@latest && npm i @zenstackhq/runtime@latest", - "deps-dev": "npm i -D zenstack@dev && npm i @zenstackhq/runtime@dev" + "deps-local": "npm i -D ../../packages/schema && npm i ../../packages/runtime/dist ../../packages/next-auth/dist zod zod-validation-error swr", + "deps-latest": "npm rm zod zod-validation-error swr && npm i -D zenstack@latest && npm i @zenstackhq/runtime@latest @zenstackhq/next-auth@latest", + "deps-dev": "npm rm zod zod-validation-error swr && npm i -D zenstack@dev && npm i @zenstackhq/runtime@dev @zenstackhq/next-auth@dev" }, "dependencies": { "@heroicons/react": "^2.0.12", "@prisma/client": "^4.7.0", - "@zenstackhq/runtime": "^0.3.23", + "@zenstackhq/next-auth": "^0.4.1", + "@zenstackhq/runtime": "^0.4.1", "bcryptjs": "^2.4.3", "daisyui": "^2.31.0", "moment": "^2.29.4", @@ -44,6 +45,6 @@ "postcss": "^8.4.16", "tailwindcss": "^3.1.8", "typescript": "^4.6.2", - "zenstack": "^0.3.23" + "zenstack": "^0.4.1" } } diff --git a/samples/todo/pages/api/auth/[...nextauth].ts b/samples/todo/pages/api/auth/[...nextauth].ts index 987f5d53b..f1a2dccac 100644 --- a/samples/todo/pages/api/auth/[...nextauth].ts +++ b/samples/todo/pages/api/auth/[...nextauth].ts @@ -1,10 +1,7 @@ import NextAuth, { NextAuthOptions, User } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; import GitHubProvider from 'next-auth/providers/github'; -import { - authorize, - NextAuthAdapter as Adapter, -} from '@zenstackhq/runtime/server/auth'; +import { authorize, Adapter } from '@zenstackhq/next-auth'; import service from '@zenstackhq/runtime/server'; import { nanoid } from 'nanoid'; import { SpaceUserRole } from '@zenstackhq/runtime/types';