1
0
mirror of synced 2026-02-04 12:08:33 -05:00

Compare commits

...

9 Commits

Author SHA1 Message Date
github-actions[bot]
13c2642bdb Version Packages (beta) (#3992)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-12-16 14:09:41 -05:00
Brandon Bayer
03bad3175d fix Cannot read properties of null (reading 'isReady') for pnpm/yarn v3 (#4008) 2022-12-16 14:02:44 -05:00
Tom MacWright
74a14b7040 fix: Reject db.() with an error object (#4006) 2022-12-15 09:58:27 -05:00
Siddharth Suresh
6ece096134 Decouple Blitz RPC and Blitz Auth (#3943)
* inital unwrapping of blitz rpc from blitz auth

* fix linr

* Revert "fix linr"

This reverts commit 000e2c7259.

* remove duplication of code and dynamically import blitz auth if plugin is used

* return types to blitz-auth and import in rpc as types

* remove excess files from git diff

* remove todo ts-ignore

* add changeset

* better error messages

* Update odd-cars-fry.md

* switch from blitz log to console - due to import error.

* Revert "Merge branch 'rpc-without-auth' of https://github.com/blitz-js/blitz into rpc-without-auth"

This reverts commit 5b45d65b4d, reversing
changes made to b15dfa6dec.

* Revert "switch from blitz log to console - due to import error."

This reverts commit b15dfa6dec.

* Revert "better error messages"

This reverts commit 75922cb063.

* fix location of seting global variable

* better error message due to dynamic import

* allow setting csrf token in blitz rpc

* cleanup

* fix

* pnpm lock fix and update csrf api

* fix global.ts type definition

* remove change to merge

* fix pnpm-lock

* update integration-tests to work without blitz-auth

* initial working commit after switch to plugin system

* fix pnpm-lock

* readd the changeset

* update hook names

* Revert "readd the changeset"

This reverts commit 796f3f518e.

* Revert "update hook names"

This reverts commit fb127ed84e.

* Revert "fix pnpm-lock"

This reverts commit d7447b5966.

* Revert "Revert "fix pnpm-lock""

This reverts commit c2f21aa0e5.

* Revert "Revert "update hook names""

This reverts commit 4b66846b20.

* Revert "Revert "readd the changeset""

This reverts commit c95d150e64.

* add header to rpc plugin

* pnpm lock fix

* cleanup - change global hook names to prefix with __BLITZ

* initial commit suggestion - TODO Fix types

* fix most type assertions

* fix error without blitz auth

* add typea to events and middleware reducers

* implement suggestion

* Apply suggestions from code review

Co-authored-by: Brandon Bayer <b@bayer.ws>

* move onSessionCreated event from blitz-auth to blitz-rpc

* move globals to blitz core, move event listener to blitz-next

* remove middlewareCtx to Ctx

* fix imports

* improve type definition of hook types

* format

* Revert "remove middlewareCtx to Ctx"

This reverts commit 4259b4dbed.

* Revert "fix imports"

This reverts commit 7422bfaee3.

* revert changes from MiddlewareCtx to Ctx

* pnpm lock and other fixes

* remove type assertion

* merge to one `Array.reduce`

* Apply suggestions from code review

Co-authored-by: Brandon Bayer <b@bayer.ws>

* implement review suggestions

* Update packages/blitz/src/types.ts

* add unit tests

* cleanup

* Update packages/blitz/tests/plugin.test.ts

* add providers to plugin reduce

* add initial integration test for full blitz rpc+auth and custom client plugins

* test commenting out playwright install

* fixes

* remove changes related to console.log checking

* test

* try with different command

* comment

* another try

* try adding global install

* change console.log to console.info for better identification

* fix db

* lowdb import fix

* convert from lowdb to prisma

* fix blitz build error

* add custom plugin events to integration-tests

* manipulate the timing of event firing

* fix

* check

* add middleware tests

* fix

* fix commented test and cleanup

* add the migration file

Co-authored-by: Brandon Bayer <b@bayer.ws>
2022-12-13 18:44:39 -05:00
Dillon Raphael
a0596279bd Fix blitz install for recipes that use the path helper (#3995) 2022-12-09 08:32:44 -08:00
Dillon Raphael
fe2e4eb1e9 Update readme 2022-12-02 08:51:26 -05:00
Aleksandra
8c247e26e7 Switch from jest to vitest in new app templates (#3985)
* Switch from jest to vitest in new app templates

* Finish vitest setup

* Handle vitest.config.js vs vitest.config.ts

* Add proper vitest config to js templates

* Add changeset

* Update READMEs in new app templates

* Fix tests after vitest upgrade

* Update spyOn references in tests
2022-11-28 14:24:08 -05:00
Siddharth Suresh
650a157e1d fix #3989 (#3990) 2022-11-25 18:21:41 -05:00
Blitz.js Bot
9dc81fe958 (meta) added @usamaster as contributor 2022-11-24 11:22:59 -05:00
164 changed files with 3865 additions and 957 deletions

View File

@@ -3766,6 +3766,15 @@
"doc",
"code"
]
},
{
"login": "usamaster",
"name": "usamaster",
"avatar_url": "https://avatars.githubusercontent.com/u/5255330?v=4",
"profile": "https://github.com/usamaster",
"contributions": [
"doc"
]
}
],
"contributorsPerLine": 7,

View File

@@ -0,0 +1,5 @@
---
"blitz": minor
---
When db.\$reset() rejects, reject with an Error object

View File

@@ -0,0 +1,6 @@
---
"@blitzjs/rpc": patch
"@blitzjs/generator": patch
---
Switch from jest to vitest in new app templates

View File

@@ -0,0 +1,8 @@
---
"blitz": minor
"@blitzjs/auth": minor
"@blitzjs/next": minor
"@blitzjs/rpc": minor
---
Decoupled Blitz RPC from Blitz Auth to allow independent use.

View File

@@ -0,0 +1,7 @@
---
"@blitzjs/auth": minor
"@blitzjs/next": minor
"@blitzjs/rpc": minor
---
fix Cannot read properties of null (reading 'isReady') for pnpm/yarn v3

View File

@@ -45,7 +45,8 @@
"@blitzjs/recipe-tailwind": "0.34.0-canary.0",
"@blitzjs/recipe-theme-ui": "0.34.0-canary.0",
"@blitzjs/recipe-vanilla-extract": "0.34.0-canary.0",
"test-rpc-path-root": "0.0.0"
"test-rpc-path-root": "0.0.0",
"test-full-auth-with-rpc": "0.0.0"
},
"changesets": [
"afraid-dancers-juggle",
@@ -63,6 +64,7 @@
"calm-carpets-deny",
"calm-horses-tie",
"calm-nails-wait",
"calm-papayas-protect",
"calm-tomatoes-drive",
"chilled-carrots-own",
"chilly-nails-nail",
@@ -74,11 +76,13 @@
"cuddly-pugs-crash",
"curly-rules-speak",
"curly-seas-serve",
"curvy-days-attend",
"cyan-bulldogs-heal",
"cyan-cars-greet",
"dirty-monkeys-greet",
"dirty-planets-chew",
"early-lamps-itch",
"eighty-apes-sleep",
"eleven-humans-sort",
"eleven-lobsters-drop",
"empty-berries-rule",
@@ -139,6 +143,7 @@
"itchy-houses-marry",
"itchy-spoons-tan",
"khaki-ducks-cheer",
"khaki-pens-rest",
"kind-walls-suffer",
"late-steaks-give",
"lazy-maps-sort",
@@ -205,8 +210,10 @@
"red-badgers-retire",
"red-gorillas-marry",
"rich-chairs-invent",
"rich-gorillas-develop",
"rich-queens-travel",
"rotten-rocks-remember",
"rude-trainers-visit",
"serious-mugs-leave",
"shaggy-carpets-brake",
"sharp-falcons-begin",

View File

@@ -0,0 +1,5 @@
---
"@blitzjs/rpc": patch
---
fix: allow `GET` requests without `params` and `meta` keys

View File

@@ -0,0 +1,5 @@
---
"blitz": patch
---
Fix blitz install for recipes that use the path helper to check if ./src/pages directory is available, otherwise use ./pages

2
.github/CODEOWNERS vendored
View File

@@ -1,5 +1,5 @@
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
* @beerose @dillonraphael
* @dillonraphael
packages/generator/templates**/* @flybayer

View File

@@ -8,10 +8,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
TURBO_TOKEN: 05de0230f01174d1f8cb4845a01dc6c895ce28f04ebef2318ab11615791b871c35eabbf8
TURBO_TEAM: foo
jobs:
lint:
name: Lint
@@ -29,11 +25,6 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- run: pnpm manypkg check
- name: Turborepo local server
uses: felixmosh/turborepo-gh-artifacts@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
server-token: ${{ env.TURBO_TOKEN }}
- name: Build
run: pnpm build
- name: Lint
@@ -52,11 +43,6 @@ jobs:
with:
node-version: 16
cache: "pnpm"
- name: Turborepo local server
uses: felixmosh/turborepo-gh-artifacts@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
server-token: ${{ env.TURBO_TOKEN }}
- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
@@ -89,12 +75,6 @@ jobs:
node-version: 16
cache: "pnpm"
- name: Turborepo local server
uses: felixmosh/turborepo-gh-artifacts@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
server-token: ${{ env.TURBO_TOKEN }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
shell: bash
@@ -154,18 +134,14 @@ jobs:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"
- name: Turborepo local server
uses: felixmosh/turborepo-gh-artifacts@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
server-token: ${{ env.TURBO_TOKEN }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
shell: bash
- name: Install playwright
run: npx playwright install --with-deps
run: |
npm i -g playwright
PLAYWRIGHT_BROWSERS_PATH=$HOME/pw-browsers npx playwright install
shell: bash
- name: Build

View File

@@ -6,7 +6,7 @@
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
</a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-398-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-399-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE">
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
@@ -136,8 +136,6 @@ Your financial contributions help ensure Blitz continues to be developed and mai
<tr>
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br />Creator</td>
<td align="center"><a href="http://twitter.com/dillonraphael"><img src="https://avatars.githubusercontent.com/u/3496193?v=4" width="100px;" alt=""/><br /><sub><b>Dillon Raphael</b></sub></a><br />Lead Maintainer</td>
<td align="center"><a href="http://aleksandra.codes"><img src="https://avatars.githubusercontent.com/u/9019397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aleksandra Sikora</b></sub></a><br />Core Maintainer</td>
</tr>
</table>
@@ -734,6 +732,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
<td align="center"><a href="https://kevinjones.engineer"><img src="https://avatars.githubusercontent.com/u/20748598?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Jones</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=joneskj55" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/paulm17"><img src="https://avatars.githubusercontent.com/u/387463?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Paul</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=paulm17" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=paulm17" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=paulm17" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/selcukfatihsevinc"><img src="https://avatars.githubusercontent.com/u/384836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Selçuk Fatih Sevinç</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=selcukfatihsevinc" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=selcukfatihsevinc" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/usamaster"><img src="https://avatars.githubusercontent.com/u/5255330?v=4?s=100" width="100px;" alt=""/><br /><sub><b>usamaster</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=usamaster" title="Documentation">📖</a></td>
</tr>
</table>

View File

@@ -30,7 +30,7 @@
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "4.6.0",
"blitz": "workspace:2.0.0-beta.19",
"blitz": "workspace:2.0.0-beta.20",
"next": "12.2.5",
"openid-client": "5.2.1",
"prisma": "4.6.0",

View File

@@ -4,7 +4,7 @@ TODO
This is a [Blitz.js](https://github.com/blitz-js/blitz) app.
# ****name****
# \***\*name\*\***
## Getting Started
@@ -38,7 +38,7 @@ Runs your tests using Jest.
yarn test
```
Blitz comes with a test setup using [Jest](https://jestjs.io/) and [react-testing-library](https://testing-library.com/).
Blitz comes with a test setup using [Vitest](https://vitest.dev/) and [react-testing-library](https://testing-library.com/).
## Commands

View File

@@ -1,7 +0,0 @@
const nextJest = require("@blitzjs/next/jest")
const createJestConfig = nextJest({
dir: "./",
})
module.exports = createJestConfig(customJestConfig)

View File

@@ -8,7 +8,8 @@
"lint": "next lint",
"prisma:start": "prisma generate && prisma migrate deploy",
"prisma:studio": "prisma studio",
"test:local": "jest"
"test:local": "prisma generate && vitest run --passWithNoTests",
"test:watch": "vitest"
},
"prisma": {
"schema": "db/schema.prisma"
@@ -30,7 +31,7 @@
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "4.6.0",
"blitz": "workspace:2.0.0-beta.19",
"blitz": "workspace:2.0.0-beta.20",
"next": "12.2.5",
"prisma": "4.6.0",
"react": "18.2.0",
@@ -41,25 +42,27 @@
},
"devDependencies": {
"@next/bundle-analyzer": "12.0.8",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.2.2",
"@types/node": "18.11.9",
"@types/preview-email": "2.0.1",
"@types/react": "18.0.25",
"@typescript-eslint/eslint-plugin": "5.42.1",
"@vitejs/plugin-react": "2.2.0",
"eslint": "8.27.0",
"eslint-config-next": "12.3.1",
"eslint-config-prettier": "8.5.0",
"husky": "8.0.2",
"jest": "29.3.0",
"jest-environment-jsdom": "29.3.0",
"jsdom": "20.0.3",
"lint-staged": "13.0.3",
"prettier": "^2.7.1",
"prettier-plugin-prisma": "4.4.0",
"pretty-quick": "3.1.3",
"preview-email": "3.0.7",
"typescript": "^4.8.4"
"typescript": "^4.8.4",
"vite-tsconfig-paths": "3.6.0",
"vitest": "0.25.3"
},
"private": true
}

View File

@@ -0,0 +1,64 @@
import { vi, describe, it, beforeEach } from "vitest"
import db from "db"
import { hash256 } from "@blitzjs/auth"
import forgotPassword from "./forgotPassword"
import previewEmail from "preview-email"
import { Ctx } from "@blitzjs/next"
beforeEach(async () => {
await db.$reset()
})
const generatedToken = "plain-token"
vi.mock("@blitzjs/auth", async () => {
const auth = await vi.importActual<Record<string, unknown>>("@blitzjs/auth")!
return {
...auth,
generateToken: () => generatedToken,
}
})
vi.mock("preview-email", () => ({ default: vi.fn() }))
describe("forgotPassword mutation", () => {
it("does not throw error if user doesn't exist", async () => {
await expect(forgotPassword({ email: "no-user@email.com" }, {} as Ctx)).resolves.not.toThrow()
})
it("works correctly", async () => {
// Create test user
const user = await db.user.create({
data: {
email: "user@example.com",
tokens: {
// Create old token to ensure it's deleted
create: {
type: "RESET_PASSWORD",
hashedToken: "token",
expiresAt: new Date(),
sentTo: "user@example.com",
},
},
},
include: { tokens: true },
})
// Invoke the mutation
await forgotPassword({ email: user.email }, {} as Ctx)
const tokens = await db.token.findMany({ where: { userId: user.id } })
const token = tokens[0]
if (!user.tokens[0]) throw new Error("Missing user token")
if (!token) throw new Error("Missing token")
// delete's existing tokens
expect(tokens.length).toBe(1)
expect(token.id).not.toBe(user.tokens[0].id)
expect(token.type).toBe("RESET_PASSWORD")
expect(token.sentTo).toBe(user.email)
expect(token.hashedToken).toBe(hash256(generatedToken))
expect(token.expiresAt > new Date()).toBe(true)
expect(previewEmail).toBeCalled()
})
})

View File

@@ -0,0 +1,83 @@
import { vi, describe, it, beforeEach, expect } from "vitest"
import resetPassword from "./resetPassword"
import db from "db"
import { SecurePassword, hash256 } from "@blitzjs/auth"
beforeEach(async () => {
await db.$reset()
})
const mockCtx: any = {
session: {
$create: vi.fn(),
},
}
describe("resetPassword mutation", () => {
it("works correctly", async () => {
expect(true).toBe(true)
// Create test user
const goodToken = "randomPasswordResetToken"
const expiredToken = "expiredRandomPasswordResetToken"
const future = new Date()
future.setHours(future.getHours() + 4)
const past = new Date()
past.setHours(past.getHours() - 4)
const user = await db.user.create({
data: {
email: "user@example.com",
tokens: {
// Create old token to ensure it's deleted
create: [
{
type: "RESET_PASSWORD",
hashedToken: hash256(expiredToken),
expiresAt: past,
sentTo: "user@example.com",
},
{
type: "RESET_PASSWORD",
hashedToken: hash256(goodToken),
expiresAt: future,
sentTo: "user@example.com",
},
],
},
},
include: { tokens: true },
})
const newPassword = "newPassword"
// Non-existent token
await expect(
resetPassword({ token: "no-token", password: "", passwordConfirmation: "" }, mockCtx)
).rejects.toThrowError()
// Expired token
await expect(
resetPassword(
{ token: expiredToken, password: newPassword, passwordConfirmation: newPassword },
mockCtx
)
).rejects.toThrowError()
// Good token
await resetPassword(
{ token: goodToken, password: newPassword, passwordConfirmation: newPassword },
mockCtx
)
// Delete's the token
const numberOfTokens = await db.token.count({ where: { userId: user.id } })
expect(numberOfTokens).toBe(0)
// Updates user's password
const updatedUser = await db.user.findFirst({ where: { id: user.id } })
expect(await SecurePassword.verify(updatedUser!.hashedPassword, newPassword)).toBe(
SecurePassword.VALID
)
})
})

View File

@@ -1,6 +1,7 @@
import { AuthClientPlugin } from "@blitzjs/auth"
import { setupBlitzClient } from "@blitzjs/next"
import { BlitzRpcPlugin } from "@blitzjs/rpc"
import { BlitzCustomPlugin } from "./custom-plugin/plugin"
export const { withBlitz } = setupBlitzClient({
plugins: [
@@ -8,5 +9,6 @@ export const { withBlitz } = setupBlitzClient({
cookiePrefix: "web-cookie-prefix",
}),
BlitzRpcPlugin({}),
BlitzCustomPlugin({}),
],
})

View File

@@ -0,0 +1,38 @@
import { createClientPlugin } from "blitz"
type CustomPluginOptions = {
// ... your options
}
export const BlitzCustomPlugin = createClientPlugin<CustomPluginOptions, {}>(
(options?: CustomPluginOptions) => {
// ... your plugin code
console.log("Custom plugin loaded")
return {
events: {
onSessionCreated: async () => {
// Called when a new session is created - Usually when the user logs in or logs out
console.log("onSessionCreated in custom plugin")
},
onRpcError: async () => {
// Called when an RPC call fails
console.log("onRpcError in custom plugin")
},
},
middleware: {
beforeHttpRequest: (req) => {
//make changes to the request options before RPC call
req.headers = { ...req.headers, ...{ customHeader: "customHeaderValue" } }
return req
},
beforeHttpResponse: (res) => {
//make changes to the response before returning to the caller
return res
},
},
exports: () => ({
// ... your exports
}),
}
}
)

View File

@@ -7,7 +7,6 @@ import logout from "src/auth/mutations/logout"
import logo from "public/logo.png"
import { useMutation } from "@blitzjs/rpc"
import { Routes, BlitzPage } from "@blitzjs/next"
import { getSession, useSession } from "@blitzjs/auth"
/*
* This file is just for a pleasant getting started page for your new app.

View File

@@ -1,27 +1,32 @@
import { useCurrentUser } from "src/users/hooks/useCurrentUser"
/**
* @vitest-environment jsdom
*/
import { expect, vi, test } from "vitest"
import { render } from "test/utils"
import Home from "../src/pages/index"
jest.mock("src/users/hooks/useCurrentUser")
const mockUseCurrentUser = useCurrentUser as jest.MockedFunction<typeof useCurrentUser>
vi.mock("public/logo.png", () => ({
default: { src: "/logo.png" },
}))
describe("renders blitz documentation link", () => {
it("test", () => {
// This is an example of how to ensure a specific item is in the document
// But it's disabled by default (by test.skip) so the test doesn't fail
// when you remove the the default content from the page
test.skip("renders blitz documentation link", () => {
// This is an example of how to ensure a specific item is in the document
// But it's disabled by default (by test.skip) so the test doesn't fail
// when you remove the the default content from the page
// This is an example on how to mock api hooks when testing
mockUseCurrentUser.mockReturnValue({
// This is an example on how to mock api hooks when testing
vi.mock("src/users/hooks/useCurrentUser", () => ({
useCurrentUser: () => ({
id: 1,
name: "User",
email: "user@email.com",
role: "user",
})
}),
}))
const { getByText } = render(<Home />)
const linkElement = getByText(/Documentation/i)
expect(linkElement).toBeInTheDocument()
})
const { getByText } = render(<Home />)
const linkElement = getByText(/Documentation/i)
expect(linkElement).toBeInTheDocument()
})

View File

@@ -1,4 +1,3 @@
// This is the jest 'setupFilesAfterEnv' setup file
// It's a good place to set globals, add global before/after hooks, etc
import "@testing-library/jest-dom"
export {} // so TS doesn't complain
export {}

View File

@@ -1,3 +1,4 @@
import { vi } from "vitest"
import { render as defaultRender } from "@testing-library/react"
import { renderHook as defaultRenderHook } from "@testing-library/react-hooks"
import { NextRouter } from "next/router"
@@ -82,16 +83,16 @@ export const mockRouter: NextRouter = {
isReady: true,
isLocaleDomain: false,
isPreview: false,
push: jest.fn(),
replace: jest.fn(),
reload: jest.fn(),
back: jest.fn(),
prefetch: jest.fn(),
beforePopState: jest.fn(),
push: vi.fn(),
replace: vi.fn(),
reload: vi.fn(),
back: vi.fn(),
prefetch: vi.fn(),
beforePopState: vi.fn(),
events: {
on: jest.fn(),
off: jest.fn(),
emit: jest.fn(),
on: vi.fn(),
off: vi.fn(),
emit: vi.fn(),
},
isFallback: false,
}

View File

@@ -0,0 +1,17 @@
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import tsconfigPaths from "vite-tsconfig-paths"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
dir: "./",
globals: true,
setupFiles: "./test/setup.ts",
coverage: {
reporter: ["text", "json", "html"],
},
},
})

View File

@@ -0,0 +1,2 @@
SESSION_SECRET_KEY=hsdenhJfpLHrGjgdgg3jdF8g2bYD2PaQ
HEADLESS=true

View File

@@ -0,0 +1 @@
module.exports = require("@blitzjs/next/eslint")

View File

@@ -0,0 +1,3 @@
node_modules
# Keep environment variables out of version control
*.sqlite

View File

@@ -0,0 +1,7 @@
import {enhancePrisma} from "blitz"
import {PrismaClient} from "@prisma/client"
const EnhancedPrisma = enhancePrisma(PrismaClient)
export * from "@prisma/client"
const prisma = new EnhancedPrisma()
export default prisma

View File

@@ -0,0 +1,47 @@
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"name" TEXT,
"email" TEXT NOT NULL,
"hashedPassword" TEXT,
"role" TEXT NOT NULL DEFAULT 'user'
);
-- CreateTable
CREATE TABLE "Session" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"expiresAt" DATETIME,
"handle" TEXT NOT NULL,
"userId" INTEGER,
"hashedSessionToken" TEXT,
"antiCSRFToken" TEXT,
"publicData" TEXT,
"privateData" TEXT,
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Token" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"hashedToken" TEXT NOT NULL,
"type" TEXT NOT NULL,
"expiresAt" DATETIME NOT NULL,
"sentTo" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
CONSTRAINT "Token_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Session_handle_key" ON "Session"("handle");
-- CreateIndex
CREATE UNIQUE INDEX "Token_hashedToken_type_key" ON "Token"("hashedToken", "type");

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"

View File

@@ -0,0 +1,50 @@
datasource sqlite {
provider = "sqlite"
url = "file:./db.sqlite"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String?
email String @unique
hashedPassword String?
role String @default("user")
sessions Session[]
tokens Token[]
}
model Session {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
expiresAt DateTime?
handle String @unique
user User? @relation(fields: [userId], references: [id])
userId Int?
hashedSessionToken String?
antiCSRFToken String?
publicData String?
privateData String?
}
model Token {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
hashedToken String
type String
expiresAt DateTime
sentTo String
user User @relation(fields: [userId], references: [id])
userId Int
@@unique([hashedToken, type])
}

View File

@@ -0,0 +1,18 @@
import prisma from "./index"
import {SecurePassword} from "@blitzjs/auth"
const seed = async () => {
const hashedPassword = await SecurePassword.hash("abcd1234")
await prisma.user.create({
data: {
email: "test@test.com",
hashedPassword,
role: "user",
},
})
process.exit(0)
}
seed()
export default seed

View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -0,0 +1,2 @@
const {withBlitz} = require("@blitzjs/next")
module.exports = withBlitz({})

View File

@@ -0,0 +1,62 @@
{
"name": "test-full-auth-with-rpc",
"version": "0.0.0",
"private": true,
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} db/seed.ts",
"schema": "db/schema.prisma"
},
"scripts": {
"start:dev": "pnpm run prisma:start && blitz dev",
"test": "vitest run",
"test-watch": "vitest",
"start": "blitz start",
"lint": "next lint",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
"prisma:start": "blitz prisma migrate deploy",
"prisma:studio": "prisma studio"
},
"dependencies": {
"@blitzjs/auth": "workspace:2.0.0-beta.20",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10",
"@prisma/client": "4.6.0",
"blitz": "workspace:2.0.0-beta.20",
"delay": "5.0.0",
"next": "12.2.5",
"prisma": "4.6.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "7.39.1",
"ts-node": "10.9.1",
"zod": "3.19.1"
},
"devDependencies": {
"@next/bundle-analyzer": "12.0.8",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
"@testing-library/react-hooks": "8.0.1",
"@types/node": "18.11.9",
"@types/preview-email": "2.0.1",
"@types/react": "18.0.25",
"@typescript-eslint/eslint-plugin": "5.42.1",
"@vitejs/plugin-react": "2.2.0",
"eslint": "8.27.0",
"eslint-config-next": "12.3.1",
"eslint-config-prettier": "8.5.0",
"husky": "8.0.2",
"jsdom": "20.0.3",
"lint-staged": "13.0.3",
"playwright": "1.28.0",
"prettier": "^2.7.1",
"prettier-plugin-prisma": "4.4.0",
"pretty-quick": "3.1.3",
"preview-email": "3.0.7",
"typescript": "^4.8.4",
"vite-tsconfig-paths": "3.6.0",
"vitest": "0.25.3"
}
}

View File

@@ -0,0 +1,14 @@
import {AuthClientPlugin} from "@blitzjs/auth"
import {setupBlitzClient} from "@blitzjs/next"
import {BlitzRpcPlugin} from "@blitzjs/rpc"
import {BlitzCustomPlugin} from "./custom-plugin/plugin"
export const {withBlitz} = setupBlitzClient({
plugins: [
AuthClientPlugin({
cookiePrefix: "web-cookie-prefix",
}),
BlitzRpcPlugin({}),
BlitzCustomPlugin({}),
],
})

View File

@@ -0,0 +1,23 @@
import type {BlitzCliConfig} from "blitz"
import {setupBlitzServer} from "@blitzjs/next"
import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
import db from "../db"
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
import {BlitzLogger} from "blitz"
const {gSSP, gSP, api} = setupBlitzServer({
plugins: [
AuthServerPlugin({
cookiePrefix: "web-cookie-prefix",
storage: PrismaStorage(db),
isAuthorized: simpleRolesIsAuthorized,
}),
],
logger: BlitzLogger({}),
})
export {gSSP, gSP, api}
export const cliConfig: BlitzCliConfig = {
customTemplates: "app/templates",
}

View File

@@ -0,0 +1,50 @@
import {createClientPlugin} from "blitz"
type CustomPluginOptions = {
// ... your options
}
export const BlitzCustomPlugin = createClientPlugin<CustomPluginOptions, {}>(
(options?: CustomPluginOptions) => {
// ... your plugin code
console.info("Custom plugin loaded")
return {
events: {
onSessionCreated: async () => {
// ... Called when a new session is created - Usually when the user logs in or logs out
// if the document url is /custom-plugin then write message to the document
if (document.location.pathname === "/custom-plugin") {
//find the content in div id page and write message
document.getElementById("page")!.innerText = "Custom plugin Session Created"
}
},
onRpcError: async () => {
// ... Called when an RPC call fails
if (document.location.pathname === "/custom-plugin") {
document.getElementById("page")!.innerText = "Custom plugin RPC Error"
}
},
},
middleware: {
beforeHttpRequest: (req) => {
// ... make changes to the request options before RPC call
if (document.location.pathname === "/custom-plugin") {
req.headers = {...req.headers, ...{customHeader: "customHeaderValue"}}
document.getElementById("before-req")!.innerText = req.headers["customHeader"]
}
return req
},
beforeHttpResponse: (res) => {
// ... make changes to the response before returning to the caller
if (document.location.pathname === "/custom-plugin") {
document.getElementById("before-res")!.innerText = res.headers.get("content-length")!
}
return res
},
},
exports: () => ({
// ... your exports
}),
}
},
)

View File

@@ -0,0 +1,9 @@
import {BlitzLayout} from "@blitzjs/next"
const AuthenticateLayout: BlitzLayout = ({children}) => {
return <div id="layout">{children}</div>
}
AuthenticateLayout.authenticate = true
export default AuthenticateLayout

View File

@@ -0,0 +1,9 @@
import {BlitzLayout} from "@blitzjs/next"
const AuthenticateRedirectLayout: BlitzLayout = ({children}) => {
return <div id="layout">{children}</div>
}
AuthenticateRedirectLayout.authenticate = {redirectTo: "/login"}
export default AuthenticateRedirectLayout

View File

@@ -0,0 +1,9 @@
import {BlitzLayout} from "@blitzjs/next"
const RedirectAuthenticatedLayout: BlitzLayout = ({children}) => {
return <div id="layout">{children}</div>
}
RedirectAuthenticatedLayout.redirectAuthenticatedTo = "/authenticated-query"
export default RedirectAuthenticatedLayout

View File

@@ -0,0 +1,10 @@
import {setPublicDataForUser} from "@blitzjs/auth"
import {Ctx} from "blitz"
export default async function changeRole({userId, role}: any, ctx: Ctx) {
// create two sessions to be changed
await ctx.session.$create({userId, role: "USER"})
await ctx.session.$create({userId, role: "USER"})
await setPublicDataForUser(userId, {role})
}

View File

@@ -0,0 +1,6 @@
import {Ctx} from "blitz"
export default async function login(_: any, ctx: Ctx) {
await ctx.session.$create({userId: 1, role: "USER"})
return true
}

View File

@@ -0,0 +1,6 @@
import {Ctx} from "blitz"
export default async function logout(_: any, ctx: Ctx) {
await ctx.session.$revoke()
return true
}

View File

@@ -0,0 +1,29 @@
import {withBlitz} from "../blitz-client"
import {useQueryErrorResetBoundary} from "@blitzjs/rpc"
import {AppProps, ErrorBoundary, ErrorFallbackProps} from "@blitzjs/next"
if (typeof window !== "undefined") {
;(window as any).DEBUG_BLITZ = 1
}
export default withBlitz(function App({Component, pageProps}: AppProps) {
const getLayout = Component.getLayout || ((page) => page)
return (
<ErrorBoundary
FallbackComponent={RootErrorFallback}
onReset={useQueryErrorResetBoundary().reset}
>
{getLayout(<Component {...pageProps} />)}
</ErrorBoundary>
)
})
function RootErrorFallback({error}: ErrorFallbackProps) {
return (
<div>
<div id="error">{error.name}</div>
{error.statusCode} {error.message}
</div>
)
}

View File

@@ -0,0 +1,23 @@
import Document, {NextScript, Head, Html, Main} from "next/document"
class MyDocument extends Document {
// Only uncomment if you need to customize this behaviour
// static async getInitialProps(ctx: DocumentContext) {
// const initialProps = await Document.getInitialProps(ctx)
// return {...initialProps}
// }
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

View File

@@ -0,0 +1,4 @@
import {rpcHandler} from "@blitzjs/rpc"
import {api} from "../../../blitz-server"
export default api(rpcHandler({onError: console.log}))

View File

@@ -0,0 +1,34 @@
import {useMutation, useQuery} from "@blitzjs/rpc"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
function AuthenticatedQuery() {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
export default AuthenticatedQuery

View File

@@ -0,0 +1,43 @@
import {BlitzPage} from "@blitzjs/next"
import {Suspense, useEffect} from "react"
import {useQuery} from "@blitzjs/rpc"
import getNoauthBasic from "../queries/getNoauthBasic"
const Content = () => {
const [result] = useQuery(getNoauthBasic, undefined)
return (
<>
<div id="rpc-result">{result}</div>
</>
)
}
const CustomPlugin: BlitzPage = () => {
//send the event to the plugin to create a new session
useEffect(() => {
setTimeout(() => {
const event = new Event("blitz:session-created")
document.dispatchEvent(event)
}, 100)
setTimeout(() => {
const error = new Error("RPC failed")
const rpcEvent = new CustomEvent("blitz:rpc-error", {
detail: error,
})
document.dispatchEvent(rpcEvent)
}, 2000)
})
return (
<div id="root">
<div id="page">This is the custom plugin page</div>
<div id="before-req"> Initial Content </div>
<div id="before-res"> Initial Content </div>
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
export default CustomPlugin

View File

@@ -0,0 +1,33 @@
import {gSSP} from "../blitz-server"
import {getSession} from "@blitzjs/auth"
import {GetServerSideProps} from "next"
import {Suspense} from "react"
export const getServerSideProps: GetServerSideProps = gSSP(async ({req, res}) => {
const session = await getSession(req, res)
await session.$setPublicData({role: "USER"})
return {
props: {},
}
})
function Content() {
return (
<div>
<div id="content">it works</div>
</div>
)
}
function GSSPSetPublicData() {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
export default GSSPSetPublicData

View File

@@ -0,0 +1,40 @@
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import AuthenticateRedirectLayout from "../layouts/AuthenticateRedirectLayout"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
const LayoutAuthenticateLogoutPage: BlitzPage = () => {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
LayoutAuthenticateLogoutPage.getLayout = (page) => (
<AuthenticateRedirectLayout>{page}</AuthenticateRedirectLayout>
)
export default LayoutAuthenticateLogoutPage

View File

@@ -0,0 +1,40 @@
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import AuthenticateRedirectLayout from "../layouts/AuthenticateRedirectLayout"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
const LayoutAuthenticateRedirectPage: BlitzPage = () => {
return (
<div id="page">
<Suspense fallback={"Loading redirect page..."}>
<Content />
</Suspense>
</div>
)
}
LayoutAuthenticateRedirectPage.getLayout = (page) => (
<AuthenticateRedirectLayout>{page}</AuthenticateRedirectLayout>
)
export default LayoutAuthenticateRedirectPage

View File

@@ -0,0 +1,38 @@
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import AuthenticateLayout from "../layouts/AuthenticateLayout"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
const LayoutAuthenticatePage: BlitzPage = () => {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
LayoutAuthenticatePage.getLayout = (page) => <AuthenticateLayout>{page}</AuthenticateLayout>
export default LayoutAuthenticatePage

View File

@@ -0,0 +1,10 @@
import {BlitzPage} from "@blitzjs/next"
import RedirectAuthenticatedLayout from "../layouts/RedirectAuthenticatedLayout"
const LayoutRedirectAuthenticatedPage: BlitzPage = () => <div id="page-container">Hello World</div>
LayoutRedirectAuthenticatedPage.getLayout = (page) => (
<RedirectAuthenticatedLayout>{page}</RedirectAuthenticatedLayout>
)
export default LayoutRedirectAuthenticatedPage

View File

@@ -0,0 +1,15 @@
import {BlitzPage} from "@blitzjs/next"
import AuthenticateLayout from "../layouts/AuthenticateLayout"
const LayoutUnauthenticatePage: BlitzPage = () => {
return (
<div id="page">
<p id="content">this should be rendered</p>
</div>
)
}
LayoutUnauthenticatePage.getLayout = (page) => <AuthenticateLayout>{page}</AuthenticateLayout>
LayoutUnauthenticatePage.authenticate = false
export default LayoutUnauthenticatePage

View File

@@ -0,0 +1,62 @@
import {useRouter} from "next/router"
import {useMutation, useQuery} from "@blitzjs/rpc"
import login from "../mutations/login"
import logout from "../mutations/logout"
import getCurrentUser from "../queries/getCurrentUser"
import {Suspense, useState} from "react"
function Content() {
const router = useRouter()
const [error, setError] = useState(null)
const [userId] = useQuery(getCurrentUser, null)
const [loginMutation] = useMutation(login)
const [logoutMutation] = useMutation(logout)
if (error) return <div id="error">{error}</div>
return (
<div>
<div id="content">{userId ? "logged-in" : "logged-out"}</div>
{userId ? (
<button
id="logout"
onClick={async () => {
try {
await logoutMutation()
} catch (error: any) {
setError(error.toString())
}
}}
>
logout
</button>
) : (
<button
id="login"
onClick={async () => {
await loginMutation()
const next = router.query.next ? decodeURIComponent(router.query.next as string) : null
if (next) {
await router.push(next)
}
}}
>
login
</button>
)}
</div>
)
}
function Login() {
return (
<div id="page">
<Suspense fallback="Loading...">
<Content />
</Suspense>
</div>
)
}
export default Login

View File

@@ -0,0 +1,20 @@
import {useQuery} from "@blitzjs/rpc"
import getNoauthBasic from "../queries/getNoauthBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getNoauthBasic, undefined)
return <div id="content">{result}</div>
}
function NoAuthQuery() {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
export default NoAuthQuery

View File

@@ -0,0 +1,37 @@
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
const AuthLogout: BlitzPage = () => {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
AuthLogout.authenticate = {redirectTo: "/login"}
export default AuthLogout

View File

@@ -0,0 +1,37 @@
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
const AuthRedirect: BlitzPage = () => {
return (
<div id="page">
<Suspense fallback={"Loading redirect page..."}>
<Content />
</Suspense>
</div>
)
}
AuthRedirect.authenticate = {redirectTo: "/login"}
export default AuthRedirect

View File

@@ -0,0 +1,37 @@
import {useMutation, useQuery} from "@blitzjs/rpc"
import {BlitzPage} from "@blitzjs/next"
import logout from "../mutations/logout"
import getAuthenticatedBasic from "../queries/getAuthenticatedBasic"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
const Authenticate: BlitzPage = () => {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
Authenticate.authenticate = true
export default Authenticate

View File

@@ -0,0 +1,43 @@
import {gSSP} from "../blitz-server"
import {GetServerSidePropsContext} from "next"
import {dehydrate, getQueryClient, getQueryKey, QueryClient, useQuery} from "@blitzjs/rpc"
import getNoauthBasic from "../queries/getNoauthBasic"
import {Suspense} from "react"
import {SessionContext} from "@blitzjs/auth"
function Content() {
const [result] = useQuery(getNoauthBasic, null, {
staleTime: 60 * 1000,
})
return <div id="content">{result}</div>
}
function Bomb() {
return <div id="content">somebody set up us the bomb</div>
}
export default function Page() {
return (
<div id="page">
<Suspense fallback={<Bomb />}>
<Content />
</Suspense>
</div>
)
}
type Props = {
dehydratedState: any
}
export const getServerSideProps = gSSP<Props>(async ({ctx}) => {
await getQueryClient().prefetchQuery(getQueryKey(getNoauthBasic, null), () =>
getNoauthBasic(null, ctx),
)
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
})

View File

@@ -0,0 +1,7 @@
function RedirectAuthenticated() {
return <div id="page-container">Hello World</div>
}
RedirectAuthenticated.redirectAuthenticatedTo = "/authenticated-query"
export default RedirectAuthenticated

View File

@@ -0,0 +1,39 @@
import {invalidateQuery, useMutation, useQuery} from "@blitzjs/rpc"
import changeRole from "../mutations/changeRole"
import getPublicDataForUser from "../queries/getPublicDataForUser"
import {Suspense} from "react"
function Content() {
const [publicData] = useQuery(getPublicDataForUser, {userId: 1})
return (
<div id="session">
<>
<div className="userId">userId: {publicData.userId}</div>
<div className="role">role: {publicData.role}</div>
</>
</div>
)
}
function SetPublicData() {
const [changeRoleMutation] = useMutation(changeRole)
return (
<div id="page">
<button
id="change-role"
onClick={async () => {
await changeRoleMutation({userId: 1, role: "new role"})
await invalidateQuery(getPublicDataForUser)
}}
>
Set new role for user
</button>
<Suspense fallback="">
<Content />
</Suspense>
</div>
)
}
export default SetPublicData

View File

@@ -0,0 +1,8 @@
import {Ctx} from "blitz"
import delay from "delay"
export default async function getAuthenticatedBasic(_: any, ctx: Ctx) {
await delay(10)
ctx.session.$authorize()
return "authenticated-basic-result"
}

View File

@@ -0,0 +1,7 @@
import {Ctx} from "blitz"
import delay from "delay"
export default async function getNoauthBasic(_: any, ctx: Ctx) {
await delay(10)
return ctx.session.userId
}

View File

@@ -0,0 +1,7 @@
import {Ctx} from "blitz"
import delay from "delay"
export default async function getNoauthBasic(_: any, ctx: Ctx) {
await delay(10)
return "noauth-basic-result"
}

View File

@@ -0,0 +1,10 @@
import {Ctx} from "blitz"
import db from "../../db"
export default async function getPublicDataForUser({userId}: any, ctx: Ctx) {
const role = ctx.session.role
return {
userId,
role,
}
}

View File

@@ -0,0 +1,291 @@
import {describe, it, expect, beforeAll, afterAll} from "vitest"
import {
killApp,
findPort,
runBlitzCommand,
blitzLaunchApp,
blitzBuild,
blitzStart,
waitFor,
} from "../../utils/next-test-utils"
import webdriver from "../../utils/next-webdriver"
let app: any
let appPort: number
const runTests = () => {
describe("Auth", () => {
describe("custom plugin", () => {
it("custom plugin - events", async () => {
const browser = await webdriver(appPort, "/custom-plugin")
let text = await browser.elementByCss("#page").text()
await waitFor(250)
text = await browser.elementByCss("#page").text()
expect(text).toBe("Custom plugin Session Created")
await waitFor(2000)
text = await browser.elementByCss("#page").text()
expect(text).toBe("Custom plugin RPC Error")
if (browser) {
await browser.close()
}
})
it("custom plugin - middleware", async () => {
const browser = await webdriver(appPort, "/custom-plugin")
await waitFor(100)
let text = await browser.elementByCss("#before-req").text()
expect(text).toBe("customHeaderValue")
await waitFor(1000)
text = await browser.elementByCss("#before-res").text()
expect(text).toBe("55")
if (browser) {
await browser.close()
}
})
})
describe("unauthenticated", () => {
it("should render result for open query", async () => {
const browser = await webdriver(appPort, "/noauth-query")
let text = await browser.elementByCss("#page").text()
await browser.waitForElementByCss("#content")
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/noauth-basic-result/)
if (browser) await browser.close()
})
it("should render error for protected query", async () => {
const browser = await webdriver(appPort, "/authenticated-query")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
it("should render error for protected page", async () => {
const browser = await webdriver(appPort, "/page-dot-authenticate")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
it("should render error for protected layout", async () => {
const browser = await webdriver(appPort, "/layout-authenticate")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
it("should render Page.authenticate = false even when Layout.authenticate = true", async () => {
const browser = await webdriver(appPort, "/layout-unauthenticate")
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/this should be rendered/)
if (browser) await browser.close()
})
})
describe("authenticated", () => {
it("should login and out successfully", async () => {
const browser = await webdriver(appPort, "/login")
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
await browser.elementByCss("#login").click()
await waitFor(200)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-in/)
await browser.elementByCss("#logout").click()
await waitFor(250)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
if (browser) await browser.close()
})
it("should logout without infinite loop #2233", async () => {
// Login
let browser = await webdriver(appPort, "/login")
await waitFor(200)
await browser.elementByCss("#login").click()
await waitFor(200)
await browser.eval(`window.location = "/authenticated-query"`)
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
await browser.elementByCss("#logout").click()
await waitFor(200)
await browser.waitForElementByCss("#error")
text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
it("Page.authenticate = {redirect} should work ", async () => {
// Login
let browser = await webdriver(appPort, "/login")
await waitFor(200)
await browser.elementByCss("#login").click()
await waitFor(200)
await browser.eval(`window.location = "/page-dot-authenticate-redirect"`)
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
await browser.elementByCss("#logout").click()
await waitFor(500)
expect(await browser.url()).toMatch(/\/login/)
if (browser) await browser.close()
})
it("Layout.authenticate = {redirect} should work ", async () => {
// Login
let browser = await webdriver(appPort, "/login")
await waitFor(200)
await browser.elementByCss("#login").click()
await waitFor(200)
await browser.eval(`window.location = "/layout-authenticate-redirect"`)
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
await browser.elementByCss("#logout").click()
await waitFor(500)
expect(await browser.url()).toMatch(/\/login/)
if (browser) await browser.close()
})
})
describe("prefetching", () => {
it("should prefetch from the query cache #2281", async () => {
const browser = await webdriver(appPort, "/prefetching")
await waitFor(100)
await browser.waitForElementByCss("#content")
const text = await browser.elementByCss("#content").text()
expect(text).toMatch(/noauth-basic-result/)
if (browser) await browser.close()
})
})
describe("setting public data for a user", () => {
it("should update all sessions of the user", async () => {
// Ensure logged out
const browser = await webdriver(appPort, "/login")
await waitFor(200)
let text = await browser.elementByCss("#content").text()
if (text.match(/logged-in/)) {
await browser.elementByCss("#logout").click()
await waitFor(200)
}
await browser.eval(`window.location = "/set-public-data"`)
await browser.waitForElementByCss("#change-role")
await browser.elementByCss("#change-role").click()
await waitFor(500)
await browser.waitForElementByCss(".role")
text = await browser.elementByCss(".role").text()
expect(text).toMatch(/role: new role/)
if (browser) await browser.close()
})
})
describe("Page.redirectAuthenticatedTo", () => {
it("should work when redirecting to page with useQuery", async () => {
// https://github.com/blitz-js/legacy-framework/issues/2527
// Ensure logged in
const browser = await webdriver(appPort, "/login")
await waitFor(200)
let text = await browser.elementByCss("#content").text()
if (text.match(/logged-out/)) {
await browser.elementByCss("#login").click()
await waitFor(200)
}
await browser.eval(`window.location = "/redirect-authenticated"`)
await browser.waitForElementByCss("#content")
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
if (browser) await browser.close()
})
})
describe("Layout.redirectAuthenticatedTo", () => {
it("should work when redirecting to page with useQuery", async () => {
// https://github.com/blitz-js/legacy-framework/issues/2527
// Ensure logged in
const browser = await webdriver(appPort, "/login")
await waitFor(200)
let text = await browser.elementByCss("#content").text()
if (text.match(/logged-out/)) {
await browser.elementByCss("#login").click()
await waitFor(200)
}
await browser.eval(`window.location = "/layout-redirect-authenticated"`)
await browser.waitForElementByCss("#content")
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
if (browser) await browser.close()
})
})
describe("setPublicData", () => {
it("it should not throw CSRF error", async () => {
// https://github.com/blitz-js/legacy-framework/issues/2448
// ensure logged out
const browser = await webdriver(appPort, "/login")
await waitFor(200)
let text = await browser.elementByCss("#content").text()
if (text.match(/logged-in/)) {
await browser.elementByCss("#logout").click()
await waitFor(200)
}
await browser.eval(`window.location = "/gssp-setpublicdata"`)
await browser.waitForElementByCss("#content")
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/it works/)
if (browser) await browser.close()
})
})
})
}
describe("Auth Tests", () => {
describe("dev mode", () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
appPort = await findPort()
app = await blitzLaunchApp(appPort, {cwd: process.cwd()})
} catch (error) {
console.log(error)
}
}, 5000 * 60 * 2)
afterAll(async () => await killApp(app))
runTests()
})
describe("server mode", () => {
beforeAll(async () => {
try {
await runBlitzCommand(["prisma", "generate"])
await runBlitzCommand(["prisma", "migrate", "deploy"])
await blitzBuild()
appPort = await findPort()
app = await blitzStart(appPort, {cwd: process.cwd()})
} catch (err) {
console.log(err)
}
}, 5000 * 60 * 2)
afterAll(async () => await killApp(app))
runTests()
})
})

View File

@@ -0,0 +1,11 @@
{
"extends": "@blitzjs/config/tsconfig.nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types"],
"compilerOptions": {
"paths": {
"react": ["./node_modules/@types/react"]
}
},
"exclude": ["node_modules"],
"baseUrl": "."
}

View File

@@ -0,0 +1,9 @@
/// <reference types="vitest" />
import {defineConfig} from "vitest/config"
export default defineConfig({
test: {
testTimeout: 100000,
hookTimeout: 100000,
},
})

View File

@@ -17,11 +17,11 @@
"prisma:studio": "prisma studio"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/auth": "workspace:2.0.0-beta.20",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@prisma/client": "4.6.0",
"blitz": "workspace:2.0.0-beta.19",
"blitz": "workspace:2.0.0-beta.20",
"lowdb": "3.0.0",
"next": "12.2.5",
"prisma": "4.6.0",

View File

@@ -16,11 +16,11 @@
"schema": "db/schema.prisma"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"@blitzjs/auth": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"@prisma/client": "4.6.0",
"blitz": "workspace:*",
"blitz": "workspace:2.0.0-beta.20",
"lowdb": "3.0.0",
"next": "12.2.5",
"prisma": "4.6.0",
@@ -28,7 +28,7 @@
"react-dom": "18.2.0"
},
"devDependencies": {
"@blitzjs/config": "workspace:*",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -11,10 +11,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"dependencies": {
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"blitz": "workspace:*",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"blitz": "workspace:2.0.0-beta.20",
"next": "12.2.5",
"react": "18.2.0",
"react-dom": "18.2.0"

View File

@@ -16,11 +16,11 @@
"prisma:studio": "prisma studio"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"@blitzjs/auth": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"@prisma/client": "4.6.0",
"blitz": "workspace:*",
"blitz": "workspace:2.0.0-beta.20",
"lowdb": "3.0.0",
"next": "12.2.5",
"prisma": "4.6.0",
@@ -28,7 +28,7 @@
"react-dom": "18.2.0"
},
"devDependencies": {
"@blitzjs/config": "workspace:*",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -8,13 +8,13 @@
"clean": "rm -rf .turbo && rm -rf node_modules"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"@blitzjs/auth": "workspace:2.0.0-beta.20",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"@prisma/client": "4.6.0",
"@tanstack/react-query": "4.0.10",
"blitz": "workspace:*",
"blitz": "workspace:2.0.0-beta.20",
"next": "12.2.5",
"prisma": "4.6.0",
"react": "18.2.0",

View File

@@ -1,14 +1,8 @@
import {BlitzRpcPlugin} from "@blitzjs/rpc"
import {setupBlitzClient} from "@blitzjs/next"
import {AuthClientPlugin} from "@blitzjs/auth"
const {withBlitz} = setupBlitzClient({
plugins: [
AuthClientPlugin({
cookiePrefix: "react-query-utils-tests-cookie-prefix",
}),
BlitzRpcPlugin({}),
],
plugins: [BlitzRpcPlugin({})],
})
export {withBlitz}

View File

@@ -1,17 +1,9 @@
import {setupBlitzServer} from "@blitzjs/next"
import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
import db from "../db"
import {BlitzLogger} from "blitz"
const {gSSP, gSP, api} = setupBlitzServer({
plugins: [
AuthServerPlugin({
cookiePrefix: "react-query-utils-tests-cookie-prefix",
storage: PrismaStorage(db),
isAuthorized: simpleRolesIsAuthorized,
}),
],
plugins: [],
logger: BlitzLogger({}),
})

View File

@@ -16,11 +16,10 @@
"schema": "db/schema.prisma"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"@prisma/client": "4.6.0",
"blitz": "workspace:*",
"blitz": "workspace:2.0.0-beta.20",
"lowdb": "3.0.0",
"next": "12.2.5",
"prisma": "4.6.0",
@@ -28,7 +27,7 @@
"react-dom": "18.2.0"
},
"devDependencies": {
"@blitzjs/config": "workspace:*",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -7,11 +7,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"blitz": "workspace:*",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"blitz": "workspace:2.0.0-beta.20",
"next": "12.2.5",
"react": "18.2.0",
"react-dom": "18.2.0"

View File

@@ -7,11 +7,10 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"blitz": "workspace:*",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"blitz": "workspace:2.0.0-beta.20",
"next": "12.2.5",
"react": "18.2.0",
"react-dom": "18.2.0"

View File

@@ -94,21 +94,6 @@ function runTests(dev = false) {
5000 * 60 * 2,
)
it(
"requires params - GET",
async () => {
const res = await fetchViaHTTP(appPort, "/api/rpc/getBasicWithGET", null, {
method: "GET",
})
const json = await res.json()
expect(res.status).toEqual(400)
expect(json.error.message).toBe(
"Request query is missing the required `params` and `meta` keys",
)
},
5000 * 60 * 2,
)
it(
"query works",
async () => {

View File

@@ -16,11 +16,11 @@
"schema": "db/schema.prisma"
},
"dependencies": {
"@blitzjs/auth": "workspace:*",
"@blitzjs/next": "workspace:*",
"@blitzjs/rpc": "workspace:*",
"@blitzjs/auth": "workspace:2.0.0-beta.20",
"@blitzjs/next": "workspace:2.0.0-beta.20",
"@blitzjs/rpc": "workspace:2.0.0-beta.20",
"@prisma/client": "4.6.0",
"blitz": "workspace:*",
"blitz": "workspace:2.0.0-beta.20",
"lowdb": "3.0.0",
"next": "12.2.5",
"prisma": "4.6.0",
@@ -28,7 +28,7 @@
"react-dom": "18.2.0"
},
"devDependencies": {
"@blitzjs/config": "workspace:*",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13",
"@types/fs-extra": "9.0.13",

View File

@@ -35,7 +35,7 @@
"prettier-plugin-prisma": "4.4.0",
"pretty-quick": "3.1.3",
"turbo": "1.4.2",
"vitest": "0.8.2",
"vitest": "0.25.3",
"wait-on": "6.0.1"
},
"npmClient": "pnpm",

View File

@@ -1,5 +1,19 @@
# @blitzjs/auth
## 2.0.0-beta.20
### Minor Changes
- 6ece0961: Decoupled Blitz RPC from Blitz Auth to allow independent use.
- 03bad317: fix Cannot read properties of null (reading 'isReady') for pnpm/yarn v3
### Patch Changes
- Updated dependencies [74a14b70]
- Updated dependencies [6ece0961]
- Updated dependencies [a0596279]
- blitz@2.0.0-beta.20
## 2.0.0-beta.19
### Minor Changes

View File

@@ -2,7 +2,7 @@ import {BuildConfig} from "unbuild"
const config: BuildConfig = {
entries: ["./src/index-browser", "./src/index-server"],
externals: ["index-browser.cjs", "index-browser.mjs", "react", "next"],
externals: ["index-browser.cjs", "index-browser.mjs", "react"],
declaration: true,
rollup: {
emitCJS: true,

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/auth",
"version": "2.0.0-beta.19",
"version": "2.0.0-beta.20",
"scripts": {
"build": "unbuild",
"predev": "wait-on -d 250 ../blitz/dist/index-server.d.ts",
@@ -26,7 +26,6 @@
"@types/secure-password": "3.1.1",
"b64-lite": "1.4.0",
"bad-behavior": "1.0.1",
"blitz": "2.0.0-beta.19",
"cookie": "0.4.1",
"cookie-session": "2.0.0",
"debug": "4.3.3",
@@ -39,8 +38,11 @@
"supports-color": "8.1.1",
"url": "0.11.0"
},
"peerDependencies": {
"blitz": "2.0.0-beta.20"
},
"devDependencies": {
"@blitzjs/config": "workspace:2.0.0-beta.19",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@testing-library/react": "13.4.0",
"@testing-library/react-hooks": "8.0.1",
"@types/cookie": "0.4.1",
@@ -48,6 +50,7 @@
"@types/jsonwebtoken": "8.5.8",
"@types/react": "18.0.25",
"@types/react-dom": "17.0.14",
"blitz": "2.0.0-beta.20",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "^4.8.4",

View File

@@ -2,7 +2,7 @@
* @vitest-environment jsdom
*/
import {vi, expect, describe, it, beforeAll, afterAll, spyOn, SpyInstance} from "vitest"
import {vi, expect, describe, it, beforeAll, afterAll, SpyInstance} from "vitest"
import {parsePublicDataToken, getPublicDataStore, useSession} from "./index"
import {COOKIE_PUBLIC_DATA_TOKEN} from "../shared"
import {toBase64} from "b64-lite"
@@ -54,7 +54,7 @@ describe("parsePublicDataToken", () => {
describe("publicDataStore", () => {
it("calls readCookie token on init", () => {
const spy = spyOn(stdlib, "readCookie")
const spy = vi.spyOn(stdlib, "readCookie")
getPublicDataStore()
expect(spy).toHaveBeenCalledWith(COOKIE_PUBLIC_DATA_TOKEN())
spy.mockRestore()
@@ -64,7 +64,7 @@ describe("publicDataStore", () => {
let localStorageSpy: SpyInstance
beforeAll(() => {
localStorageSpy = spyOn(Storage.prototype, "setItem")
localStorageSpy = vi.spyOn(Storage.prototype, "setItem")
})
it("sets local storage", () => {
@@ -84,7 +84,7 @@ describe("publicDataStore", () => {
describe("clear", () => {
it("clears the cookie", () => {
const spy = spyOn(stdlib, "deleteCookie")
const spy = vi.spyOn(stdlib, "deleteCookie")
getPublicDataStore().clear()
expect(spy).toHaveBeenCalledWith(COOKIE_PUBLIC_DATA_TOKEN())
})

View File

@@ -14,6 +14,7 @@ import {
RedirectError,
RouteUrlObject,
Ctx,
CSRFTokenMismatchError,
} from "blitz"
import {
COOKIE_CSRF_TOKEN,
@@ -25,6 +26,10 @@ import {
EmptyPublicData,
AuthenticatedClientSession,
ClientSession,
HEADER_CSRF,
HEADER_PUBLIC_DATA_TOKEN,
HEADER_SESSION_CREATED,
HEADER_CSRF_ERROR,
} from "../shared"
import _debug from "debug"
import {formatWithValidation} from "../shared/url-utils"
@@ -391,8 +396,50 @@ export const AuthClientPlugin = createClientPlugin((options: AuthPluginClientOpt
globalThis.__BLITZ_SESSION_COOKIE_PREFIX = options.cookiePrefix || "blitz"
return {
withProvider: withBlitzAuthPlugin,
events: {},
middleware: {},
events: {
onRpcError: async (error) => {
// We don't clear the publicDataStore for anonymous users,
// because there is not sensitive data
if (error.name === "AuthenticationError" && getPublicDataStore().getData().userId) {
getPublicDataStore().clear()
}
},
},
middleware: {
beforeHttpRequest(req) {
const headers: Record<string, any> = {
"Content-Type": "application/json",
}
const antiCSRFToken = getAntiCSRFToken()
if (antiCSRFToken) {
debug("Adding antiCSRFToken cookie header", antiCSRFToken)
headers[HEADER_CSRF] = antiCSRFToken
} else {
debug("No antiCSRFToken cookie found")
}
req.headers = {...req.headers, ...headers}
return req
},
beforeHttpResponse(res) {
if (res.headers) {
backupAntiCSRFTokenToLocalStorage()
if (res.headers.get(HEADER_PUBLIC_DATA_TOKEN)) {
getPublicDataStore().updateState()
debug("Public data updated")
}
if (res.headers.get(HEADER_SESSION_CREATED)) {
const event = new Event("blitz:session-created")
document.dispatchEvent(event)
}
if (res.headers.get(HEADER_CSRF_ERROR)) {
const err = new CSRFTokenMismatchError()
err.stack = null!
throw err
}
}
return res
},
},
exports: () => ({
useSession,
useAuthorize,

View File

@@ -118,8 +118,11 @@ export function passportAuth(config: BlitzPassportConfig): ApiHandler {
const {name, strategy, authenticateOptions} = blitzStrategy
assert(typeof strategy.name !== "undefined", `A passport strategy name was not found for: ${req.query.auth[0]}`)
assert(
typeof strategy.name !== "undefined",
`A passport strategy name was not found for: ${req.query.auth[0]}`,
)
const strategyName = name || strategy.name
passport.use(strategyName, strategy)

View File

@@ -81,6 +81,9 @@ declare module "blitz" {
export interface Ctx {
session: SessionContext
}
export interface AuthenticatedMiddlewareCtx extends Omit<Ctx, "session"> {
session: AuthenticatedSessionContext
}
}
export type BlitzCtx = Ctx

View File

@@ -1,5 +1,23 @@
# @blitzjs/next
## 2.0.0-beta.20
### Minor Changes
- 6ece0961: Decoupled Blitz RPC from Blitz Auth to allow independent use.
- 03bad317: fix Cannot read properties of null (reading 'isReady') for pnpm/yarn v3
### Patch Changes
- Updated dependencies [74a14b70]
- Updated dependencies [8c247e26]
- Updated dependencies [6ece0961]
- Updated dependencies [03bad317]
- Updated dependencies [650a157e]
- Updated dependencies [a0596279]
- blitz@2.0.0-beta.20
- @blitzjs/rpc@2.0.0-beta.20
## 2.0.0-beta.19
### Minor Changes

View File

@@ -2,17 +2,7 @@ import {BuildConfig} from "unbuild"
const config: BuildConfig = {
entries: ["./src/index-browser", "./src/index-server"],
externals: [
"index-browser.cjs",
"index-browser.mjs",
"blitz",
".blitz",
"react",
"next",
"next/head",
"next/router",
"next/dist/shared/lib/router/router",
],
externals: ["index-browser.cjs", "index-browser.mjs", "blitz", ".blitz"],
declaration: true,
rollup: {
emitCJS: true,

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/next",
"version": "2.0.0-beta.19",
"version": "2.0.0-beta.20",
"scripts": {
"build": "unbuild",
"dev": "pnpm predev && pnpm watch unbuild src --wait=0.2",
@@ -24,7 +24,7 @@
"eslint.js"
],
"dependencies": {
"@blitzjs/rpc": "2.0.0-beta.19",
"@blitzjs/rpc": "2.0.0-beta.20",
"@tanstack/react-query": "4.0.10",
"@types/hoist-non-react-statics": "3.3.1",
"debug": "4.3.3",
@@ -33,8 +33,13 @@
"superjson": "1.11.0",
"supports-color": "8.1.1"
},
"peerDependencies": {
"blitz": "2.0.0-beta.20",
"next": "*",
"react": "*"
},
"devDependencies": {
"@blitzjs/config": "workspace:2.0.0-beta.19",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@testing-library/dom": "8.13.0",
"@testing-library/jest-dom": "5.16.3",
"@testing-library/react": "13.4.0",
@@ -44,7 +49,7 @@
"@types/react": "18.0.25",
"@types/react-dom": "17.0.14",
"@types/testing-library__react-hooks": "4.0.0",
"blitz": "2.0.0-beta.19",
"blitz": "2.0.0-beta.20",
"cross-spawn": "7.0.3",
"find-up": "4.1.0",
"next": "12.2.5",

View File

@@ -2,7 +2,7 @@
* @vitest-environment jsdom
*/
import {afterEach, beforeEach, spyOn, test, expect, MockedFunction, vi} from "vitest"
import {afterEach, beforeEach, test, expect, MockedFunction, vi} from "vitest"
import {render, screen, cleanup, waitFor} from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import * as React from "react"
@@ -11,7 +11,7 @@ import {ErrorBoundary, useErrorHandler} from "./error-boundary"
beforeEach(() => {
global.IS_REACT_ACT_ENVIRONMENT = true
spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
})
afterEach(() => {
vi.resetAllMocks()

View File

@@ -2,18 +2,7 @@
* @vitest-environment jsdom
*/
import {
expect,
describe,
it,
test,
afterEach,
beforeEach,
spyOn,
MockWithArgs,
vi,
MockedFunction,
} from "vitest"
import {expect, test, afterEach, beforeEach, vi, MockedFunction} from "vitest"
import {render, screen, cleanup} from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import React, {forwardRef} from "react"
@@ -22,7 +11,7 @@ import {ErrorBoundary, withErrorBoundary} from "./error-boundary"
beforeEach(() => {
;(global as any).IS_REACT_ACT_ENVIRONMENT = true
spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
})
afterEach(() => {
vi.resetAllMocks()
@@ -60,7 +49,7 @@ export const cleanStack = (stack: any): any => {
}
test("standard use-case", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
function App() {
const [username, setUsername] = React.useState("")
@@ -131,7 +120,7 @@ test("standard use-case", () => {
})
test("fallbackRender prop", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
const workingMessage = "Phew, we are safe!"
@@ -169,7 +158,7 @@ test("fallbackRender prop", () => {
})
test("simple fallback is supported", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
const {unmount} = render(
<ErrorBoundary fallback={<div>Oh no</div>}>
@@ -229,7 +218,7 @@ test("withErrorBoundary HOC", () => {
})
test("supported but undocumented reset method", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
const children = "Boundry children"
function App() {
@@ -266,7 +255,7 @@ test("supported but undocumented reset method", () => {
})
test("requires either a fallback, fallbackRender, or FallbackComponent", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
let unmount: undefined | (() => void)
expect(() => {
@@ -286,7 +275,7 @@ test("requires either a fallback, fallbackRender, or FallbackComponent", () => {
// eslint-disable-next-line max-statements
test("supports automatic reset of error boundary when resetKeys change", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
const handleReset = vi.fn()
const TRY_AGAIN_ARG1 = "TRY_AGAIN_ARG1"
@@ -392,7 +381,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
})
test("supports reset via resetKeys right after error is triggered on component mount", async () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
const handleResetKeysChange = vi.fn()
function App() {
const [explode, setExplode] = React.useState(true)
@@ -430,7 +419,7 @@ test("supports reset via resetKeys right after error is triggered on component m
})
test("should support not only function as FallbackComponent", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
const FancyFallback = forwardRef(({error}: ErrorFallbackProps) => (
<div>
@@ -456,7 +445,7 @@ test("should support not only function as FallbackComponent", () => {
})
test("should throw error if FallbackComponent is not valid", () => {
const consoleError = console.error as MockWithArgs<(args: unknown[]) => void>
const consoleError = console.error as MockedFunction<(args: unknown[]) => void>
let unmount: undefined | (() => void)
expect(() => {

View File

@@ -1,10 +1,10 @@
import "./global"
import type {ClientPlugin, BlitzProviderComponentType, UnionToIntersection, Simplify} from "blitz"
import type {ClientPlugin, BlitzPluginWithProvider} from "blitz"
import {reduceBlitzPlugins, Ctx} from "blitz"
import Head from "next/head"
import React, {ReactNode} from "react"
import {QueryClient, QueryClientProvider, Hydrate, HydrateOptions} from "@tanstack/react-query"
import {withSuperJSONPage} from "./superjson"
import {Ctx} from "blitz"
import {UrlObject} from "url"
import {AppPropsType} from "next/dist/shared/lib/utils"
import {Router, useRouter} from "next/router"
@@ -16,18 +16,7 @@ export * from "./use-params"
export * from "./router-context"
export {Routes} from ".blitz"
const compose =
(...rest: BlitzProviderComponentType[]) =>
(x: React.ComponentType<any>) =>
rest.reduceRight((y, f) => f(y), x)
const buildWithBlitz = <TPlugins extends readonly ClientPlugin<object>[]>(plugins: TPlugins) => {
const providers = plugins.reduce((acc, plugin) => {
return plugin.withProvider ? acc.concat(plugin.withProvider) : acc
}, [] as BlitzProviderComponentType[])
const withPlugins = compose(...providers)
const buildWithBlitz = (withPlugins: BlitzPluginWithProvider) => {
return function withBlitzAppRoot(UserAppRoot: React.ComponentType<AppProps>) {
const BlitzOuterRoot = (props: AppProps) => {
const component = React.useMemo(() => withPlugins(props.Component), [props.Component])
@@ -111,14 +100,6 @@ export const BlitzProvider = ({
return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>
}
export type PluginsExports<TPlugins extends readonly ClientPlugin<object>[]> = Simplify<
UnionToIntersection<
{
[I in keyof TPlugins & number]: ReturnType<TPlugins[I]["exports"]>
}[number]
>
>
const setupBlitzClient = <TPlugins extends readonly ClientPlugin<object>[]>({
plugins,
}: {
@@ -137,9 +118,9 @@ const setupBlitzClient = <TPlugins extends readonly ClientPlugin<object>[]>({
// allMiddleware.push(middleware)
// }
const exports = plugins.reduce((acc, plugin) => ({...plugin.exports(), ...acc}), {})
const {exports, withPlugins} = reduceBlitzPlugins({plugins})
const withBlitz = buildWithBlitz(plugins)
const withBlitz = buildWithBlitz(withPlugins)
// todo: finish this
// Used to build BlitzPage type
@@ -147,7 +128,7 @@ const setupBlitzClient = <TPlugins extends readonly ClientPlugin<object>[]>({
return {
withBlitz,
...(exports as PluginsExports<TPlugins>),
...exports,
}
}

View File

@@ -1,5 +1,21 @@
# @blitzjs/rpc
## 2.0.0-beta.20
### Minor Changes
- 6ece0961: Decoupled Blitz RPC from Blitz Auth to allow independent use.
- 03bad317: fix Cannot read properties of null (reading 'isReady') for pnpm/yarn v3
### Patch Changes
- 8c247e26: Switch from jest to vitest in new app templates
- 650a157e: fix: allow `GET` requests without `params` and `meta` keys
- Updated dependencies [74a14b70]
- Updated dependencies [6ece0961]
- Updated dependencies [a0596279]
- blitz@2.0.0-beta.20
## 2.0.0-beta.19
### Minor Changes

View File

@@ -13,14 +13,8 @@ const config: BuildConfig = {
"index-browser.mjs",
"index-server.cjs",
"index-server.mjs",
"react",
"blitz",
"next",
"@blitzjs/auth",
"zod",
"next",
"next/router",
"next/dist/client/normalize-trailing-slash",
"next/dist/client/add-base-path",
],
declaration: true,
rollup: {

View File

@@ -1,6 +1,6 @@
{
"name": "@blitzjs/rpc",
"version": "2.0.0-beta.19",
"version": "2.0.0-beta.20",
"scripts": {
"build": "unbuild",
"predev": "wait-on -d 400 ../blitz/dist/index-server.d.ts && wait-on -d 400 ../blitz-auth/dist/index-browser.d.ts",
@@ -20,7 +20,6 @@
"dist/**"
],
"dependencies": {
"@blitzjs/auth": "2.0.0-beta.19",
"@swc/core": "1.3.7",
"@tanstack/react-query": "4.0.10",
"b64-lite": "1.4.0",
@@ -30,12 +29,18 @@
"superjson": "1.11.0",
"supports-color": "8.1.1"
},
"peerDependencies": {
"blitz": "2.0.0-beta.20",
"next": "*",
"react": "*"
},
"devDependencies": {
"@blitzjs/config": "workspace:2.0.0-beta.19",
"@blitzjs/auth": "2.0.0-beta.20",
"@blitzjs/config": "workspace:2.0.0-beta.20",
"@types/debug": "4.1.7",
"@types/react": "18.0.25",
"@types/react-dom": "17.0.14",
"blitz": "2.0.0-beta.19",
"blitz": "2.0.0-beta.20",
"next": "12.2.5",
"react": "18.2.0",
"react-dom": "18.2.0",

Some files were not shown because too many files have changed in this diff Show More