1
0
mirror of synced 2026-02-07 12:00:13 -05:00

Compare commits

...

29 Commits

Author SHA1 Message Date
github-actions[bot]
0fc2d71a18 Version Packages (#4403)
* Version Packages

* chore: its a minor not major

* pnpm lock fix

* Apply suggestions from code review

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2024-12-25 13:17:16 +00:00
Siddharth Suresh
3fa3a4ef30 chore: support next.js 15 (#4387)
* chore: upgrade to next.js 15

* fix: await cookies and headers in blitz auth

* chore: run codemod

* upgrade to latest react

* fix: add await to blitz rpc handler

* chore: upgrade to latest dynamic import

* fix: remaining type fixes

* Create wild-news-shop.md

* fix: react types

* upgrade testing library

* fix turbopack exports

* fix

* chore: remove outdated `@testing-library/react-hooks`

* Merge branch 'siddharth/upgrade-to-next-15' of https://github.com/blitz-js/blitz into siddharth/upgrade-to-next-15

* upgrade testing library

* fix turbopack exports

* fix

chore: remove outdated `@testing-library/react-hooks`

* upgrade to react 19

* fix unit tests

* fix

* fix

* fix again

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2024-12-23 18:02:30 +00:00
Daniel
565db3c5a8 Fix incorrect terminal message after installation (#4402)
* Fix incorrect terminal message after installation

* Updated changeset
2024-12-23 17:54:09 +00:00
github-actions[bot]
ae04524b4c Version Packages (#4397)
* Version Packages

* pnpm lock update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2024-12-23 12:03:32 +00:00
Siddharth Suresh
38d320fd28 chore: Upgrade to prisma v6 (#4400)
* test: upgrade to latest prisma version

* Create twelve-wolves-fix.md
2024-12-23 17:16:25 +05:30
Blitz.js Bot
8723d0053c (meta) added @fungilation as contributor 2024-12-06 22:37:48 -06:00
Gary Fung
ce23d4ed09 Fix turbopack to work with Next 15 (#4396)
* Update turbopack-empty.js

fix https://github.com/blitz-js/blitz/issues/4395

* Create brown-cobras-dream.md

---------

Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2024-12-07 04:37:43 +00:00
github-actions[bot]
b6c9c4ae6d Version Packages (#4383)
* Version Packages

* pnpm lock update

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2024-10-21 17:29:25 +00:00
Siddharth Suresh
0b3286468b chore: bump deps (#4380)
* chore: bump deps

* fix version

* Create honest-moons-build.md
2024-10-21 22:42:04 +05:30
Kevin Østerkilde
50f17d21ce chore: update prisma-ast version in blitz package (#4382)
* chore: align prisma-ast dependency versions

* chore: add changeset

* chore: add corrected changeset
2024-10-14 16:59:52 +05:30
Siddharth Suresh
57add5f1c5 chore: update pnpm lock file 2024-10-08 19:23:13 +05:30
github-actions[bot]
3f239e78b6 Version Packages (#4379)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-08 19:15:27 +05:30
Blitz.js Bot
3bf90c167c (meta) added @doe-base as contributor 2024-09-30 10:47:47 -05:00
Daniel Idoko
56bd32b553 Fix Incorrect Zod Schema Generation for Datetime Fields (#4377)
* Fix Zod schema generation for datetime fields

* Add changeset for datetime schema fix

* Set changeset to patch release

* chore: update unit test

---------

Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2024-09-30 15:47:42 +00:00
Kevin Østerkilde
2711291e97 fix: remove views from public Session data (again) (#4375)
* fix: remove views from public Session data (again)

* Update .changeset/small-parents-press.md

---------

Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2024-09-29 21:17:38 +00:00
Blitz.js Bot
ab29c5bf3f (meta) added @cherniavskii as contributor 2024-09-29 16:07:38 -05:00
Siddharth Suresh
a096f2cd80 chore: update pnpm-lock.yaml 2024-08-14 19:33:16 +05:30
github-actions[bot]
ded16b325b Version Packages (#4368)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-14 19:31:29 +05:30
github-actions[bot]
4494662d6d Version Packages (#4353)
* Version Packages

* make the version 2.1.0

* Update integration-tests/no-suspense/package.json

* pnpm lock fix

* use latest pnpm version

* Revert "use latest pnpm version"

This reverts commit 5fab234a4a.

* Revert "pnpm lock fix"

This reverts commit 690a467cf7.

* Revert "make the version 2.1.0"

This reverts commit 922e95156f.

* make the version 2.1.0

* pnpm lock fix

* fix

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Siddharth Suresh <siddh.suresh@gmail.com>
2024-08-14 19:27:52 +05:30
Siddharth Suresh
9a0ba87d15 add error register in rpc handler (#4356)
* add error register in rpc handler

* fix

* Create empty-pugs-prove.md

* fixes

* Update packages/blitz/src/errors.ts
2024-08-05 19:53:03 +05:30
Siddharth Suresh
c80ce51d36 chore: fix CI action versions (#4366) 2024-07-30 18:35:59 +05:30
Blitz.js Bot
b505933a16 (meta) added @bezalel6 as contributor 2024-07-30 08:03:36 -05:00
bezalel6
d53da39cbf modify parseDefaultExportName so it does not grab an HOC, but the fir… (#4359)
* modify parseDefaultExportName so it does not grab an HOC, but the first Argument it takes. presumably the actual component

* created changeset
2024-07-30 18:33:31 +05:30
Siddharth Suresh
e1055f7366 sponsor: add route4me as seedling sponsor (#4361)
* sponser: add route4me as seedling sponser

* readMe changes
2024-07-22 19:18:38 +05:30
Siddharth Suresh
3b10b13e6b feat: Blitz RPC endpoint to the app directory (#4341)
* feat: make `rpchandler` work with `Request` and return a `Response`

* rename: rpcRequestHandler

* feat: improvements

* blitz-auth now works

* return headers

* working

* cleanup

* working sveltekit, with regressin of next.js app dir

* cleanup

* pnpm lock fix

* Update packages/blitz-auth/src/server/auth-plugin.ts

* fix build

* more work

* fixes

* fix issues with auth

* maybe required breaking change

* pointless test

* Update packages/blitz-auth/package.json

* fixes

* fix

* get all tests passing

* more fixes

* changeset

* fixes

* fix

* pnpm lock update

* fix

* update pnpm lcok

* revert unnecessary changes

* imporve api naming

* cleanup

* fix

* Update integration-tests/next-13-app-dir/src/blitz-server.ts

* Apply suggestions from code review

* remove unrelated changes

* Update packages/blitz-auth/src/server/auth-sessions.ts

* fix types

* fix overload

* remove dependence on http module

* review changes

* oops

* fix

* fix types

* Revert "fix types"

This reverts commit b06a4fb3bf.

* Revert "fix"

This reverts commit 47d0cdd568.

* Revert "oops"

This reverts commit 94cb55839d.

* Revert "review changes"

This reverts commit 14d8eb2820.

* fix the logic

* sort deps

* template fixes

* chore: add changeset

* chore: remove outdated changeset

* fix changeset formatting

* Update .changeset/tidy-gorillas-confess.md

* remove `blitzAuthRpcMiddleware`

* remove uses of any

* fix jsdoc

* no var

* separate the type imports

* fix unsupported method of session in rsc

* fix

* api changes

* Apply suggestions from code review

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

* Apply suggestions from code review

* Update .changeset/tidy-gorillas-confess.md

* fix

* fic

---------

Co-authored-by: Brandon Bayer <b@bayer.ws>
2024-07-01 23:41:12 +05:30
Siddharth Suresh
25601754a4 chore: remove check for comment time in PR release action (#4351) 2024-06-26 20:29:09 +05:30
Aviv Keller
e0cfa328ec fix: use correct bash syntax (#4349) 2024-06-26 19:29:55 +05:30
Blitz.js Bot
f02469aac8 (meta) added @RedYetiDev as contributor 2024-06-13 08:38:15 -05:00
Aviv Keller
2f3c552ac3 fix: use the correct SHA for pr-release.yml (#4348) 2024-06-13 19:08:09 +05:30
160 changed files with 3255 additions and 3197 deletions

View File

@@ -4065,6 +4065,55 @@
"code", "code",
"test" "test"
] ]
},
{
"login": "RedYetiDev",
"name": "Aviv Keller",
"avatar_url": "https://avatars.githubusercontent.com/u/38299977?v=4",
"profile": "https://redyetidev.github.io",
"contributions": [
"code"
]
},
{
"login": "bezalel6",
"name": "bezalel6",
"avatar_url": "https://avatars.githubusercontent.com/u/51681171?v=4",
"profile": "https://github.com/bezalel6",
"contributions": [
"doc",
"code"
]
},
{
"login": "cherniavskii",
"name": "Andrew Cherniavskii",
"avatar_url": "https://avatars.githubusercontent.com/u/13808724?v=4",
"profile": "cherniavskii.com",
"contributions": [
"doc"
]
},
{
"login": "doe-base",
"name": "Daniel Idoko",
"avatar_url": "https://avatars.githubusercontent.com/u/95912955?v=4",
"profile": "https://danielidoko-r3zt.vercel.app/",
"contributions": [
"doc",
"code",
"test"
]
},
{
"login": "fungilation",
"name": "Gary Fung",
"avatar_url": "https://avatars.githubusercontent.com/u/3803466?v=4",
"profile": "https://garyfung.medium.com",
"contributions": [
"doc",
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -17,13 +17,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: pnpm/action-setup@v2.2.4 - uses: pnpm/action-setup@v4.0.0
with: with:
version: 8.6.5 version: 8.6.6
- name: Setup node - name: Setup node
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 20
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
@@ -38,13 +38,13 @@ jobs:
name: Build name: Build
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: pnpm/action-setup@v2.2.4 - uses: pnpm/action-setup@v4.0.0
with: with:
version: 8.6.5 version: 8.6.6
- name: Setup node - name: Setup node
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 20
cache: "pnpm" cache: "pnpm"
- run: pnpm install --frozen-lockfile - run: pnpm install --frozen-lockfile
- name: Build - name: Build
@@ -68,14 +68,14 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup PNPM - name: Setup PNPM
uses: pnpm/action-setup@v2.2.4 uses: pnpm/action-setup@v4.0.0
with: with:
version: 8.6.5 version: 8.6.6
- name: Setup node@16 - name: Setup node@16
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 20
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies
@@ -129,15 +129,15 @@ jobs:
- name: Setup PNPM - name: Setup PNPM
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest' if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
uses: pnpm/action-setup@v2.2.4 uses: pnpm/action-setup@v4.0.0
with: with:
version: 8.6.5 version: 8.6.6
- name: Setup node@18 - name: Setup node@18
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest' if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 20
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies

View File

@@ -22,16 +22,19 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: resolve pr refs - name: Fetch PR information
id: refs id: pr_info
uses: eficode/resolve-pr-refs@main env:
with: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }} run: |
pr="$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})"
head_sha="$(echo "$pr" | jq -r .head.sha)"
echo "head_sha=$head_sha" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.pull_request.head.ref }} ref: ${{ steps.pr_info.outputs.head_sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup PNPM - name: Setup PNPM
uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2 uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2

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=="> <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> </a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- 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-428-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-433-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE"> <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"> <img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
@@ -79,6 +79,9 @@ Your financial contributions help ensure Blitz continues to be developed and mai
</a></td> </a></td>
<td><a aria-label="Simon Lammes" href="https://github.com/simon-lammes"> <td><a aria-label="Simon Lammes" href="https://github.com/simon-lammes">
<img alt="" src="https://avatars.githubusercontent.com/u/46446421?v=4" width="40px"/> <img alt="" src="https://avatars.githubusercontent.com/u/46446421?v=4" width="40px"/>
</a></td>
<td><a aria-label="Route Optimizer and Route Planning Software" href="https://route4me.com">
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/main/assets/route4me.png" width="40px"/>
</a></td> </a></td>
</tr> </tr>
</table> </table>
@@ -754,6 +757,11 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://timn.tech"><img src="https://avatars.githubusercontent.com/u/6324199?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Neutkens</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timneutkens" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=timneutkens" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=timneutkens" title="Tests">⚠️</a></td> <td align="center"><a href="https://timn.tech"><img src="https://avatars.githubusercontent.com/u/6324199?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Neutkens</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timneutkens" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=timneutkens" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=timneutkens" title="Tests">⚠️</a></td>
<td align="center"><a href="https://redyetidev.github.io"><img src="https://avatars.githubusercontent.com/u/38299977?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aviv Keller</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=RedYetiDev" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/bezalel6"><img src="https://avatars.githubusercontent.com/u/51681171?v=4?s=100" width="100px;" alt=""/><br /><sub><b>bezalel6</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=bezalel6" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=bezalel6" title="Code">💻</a></td>
<td align="center"><a href="cherniavskii.com"><img src="https://avatars.githubusercontent.com/u/13808724?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Cherniavskii</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cherniavskii" title="Documentation">📖</a></td>
<td align="center"><a href="https://danielidoko-r3zt.vercel.app/"><img src="https://avatars.githubusercontent.com/u/95912955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Idoko</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=doe-base" title="Tests">⚠️</a></td>
<td align="center"><a href="https://garyfung.medium.com"><img src="https://avatars.githubusercontent.com/u/3803466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gary Fung</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=fungilation" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=fungilation" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

View File

@@ -1,5 +1,73 @@
# next-blitz-auth # next-blitz-auth
## 0.1.17
### Patch Changes
- Updated dependencies [565db3c5a]
- Updated dependencies [3fa3a4ef3]
- blitz@2.2.0
- @blitzjs/auth@2.2.0
- @blitzjs/next@2.2.0
- @blitzjs/rpc@2.2.0
- @blitzjs/config@2.2.0
## 0.1.16
### Patch Changes
- Updated dependencies [ce23d4ed0]
- @blitzjs/next@2.1.4
- blitz@2.1.4
- @blitzjs/auth@2.1.4
- @blitzjs/rpc@2.1.4
- @blitzjs/config@2.1.4
## 0.1.15
### Patch Changes
- Updated dependencies [0b3286468]
- Updated dependencies [50f17d21c]
- @blitzjs/auth@2.1.3
- @blitzjs/next@2.1.3
- @blitzjs/rpc@2.1.3
- blitz@2.1.3
- @blitzjs/config@2.1.3
## 0.1.14
### Patch Changes
- blitz@2.1.2
- @blitzjs/auth@2.1.2
- @blitzjs/next@2.1.2
- @blitzjs/rpc@2.1.2
- @blitzjs/config@2.1.2
## 0.1.13
### Patch Changes
- Updated dependencies [9a0ba87d1]
- @blitzjs/rpc@2.1.1
- blitz@2.1.1
- @blitzjs/next@2.1.1
- @blitzjs/auth@2.1.1
- @blitzjs/config@2.1.1
## 0.1.12
### Patch Changes
- Updated dependencies [d53da39cb]
- Updated dependencies [3b10b13e6]
- blitz@2.1.0
- @blitzjs/auth@2.1.0
- @blitzjs/next@2.1.0
- @blitzjs/rpc@2.1.0
- @blitzjs/config@2.1.0
## 0.1.11 ## 0.1.11
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "next-blitz-auth", "name": "next-blitz-auth",
"version": "0.1.11", "version": "0.1.17",
"private": true, "private": true,
"scripts": { "scripts": {
"blitz:dev": "next dev", "blitz:dev": "next dev",
@@ -12,28 +12,28 @@
"schema": "prisma/schema.prisma" "schema": "prisma/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "^4.5.0", "@prisma/client": "^4.5.0",
"@tanstack/react-query": "4.0.10", "@tanstack/react-query": "4.0.10",
"blitz": "2.0.10", "blitz": "2.2.0",
"flatted": "3.2.7", "flatted": "3.2.7",
"next": "canary", "next": "15.0.1",
"prisma": "^4.5.0", "prisma": "^4.5.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"react-hook-form": "7.39.1", "react-hook-form": "7.39.1",
"superjson": "1.11.0", "superjson": "1.11.0",
"zod": "3.20.2" "zod": "3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.11.7", "@types/node": "18.11.7",
"@types/react": "18.0.23", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "18.0.7", "@types/react-dom": "npm:types-react-dom@19.0.0",
"eslint": "8.26.0", "eslint": "8.26.0",
"eslint-config-next": "13.0.0", "eslint-config-next": "13.0.0",
"typescript": "4.8.4" "typescript": "4.8.4"

Binary file not shown.

View File

@@ -0,0 +1,4 @@
import {rpcAppHandler} from "@blitzjs/rpc"
import {withBlitzAuth} from "src/blitz-server"
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())

View File

@@ -6,7 +6,8 @@ import {simpleRolesIsAuthorized} from "@blitzjs/auth"
import {BlitzLogger} from "blitz" import {BlitzLogger} from "blitz"
import {RpcServerPlugin} from "@blitzjs/rpc" import {RpcServerPlugin} from "@blitzjs/rpc"
const {api, getBlitzContext, useAuthenticatedBlitzContext, invoke} = setupBlitzServer({ const {api, getBlitzContext, useAuthenticatedBlitzContext, invoke, withBlitzAuth} =
setupBlitzServer({
plugins: [ plugins: [
AuthServerPlugin({ AuthServerPlugin({
cookiePrefix: "web-cookie-prefix", cookiePrefix: "web-cookie-prefix",
@@ -23,9 +24,9 @@ const {api, getBlitzContext, useAuthenticatedBlitzContext, invoke} = setupBlitzS
}), }),
], ],
logger: BlitzLogger({}), logger: BlitzLogger({}),
}) })
export {api, getBlitzContext, useAuthenticatedBlitzContext, invoke} export {api, getBlitzContext, useAuthenticatedBlitzContext, invoke, withBlitzAuth}
export const cliConfig: BlitzCliConfig = { export const cliConfig: BlitzCliConfig = {
customTemplates: "src/templates", customTemplates: "src/templates",

View File

@@ -4,7 +4,7 @@ import {zodResolver} from "@hookform/resolvers/zod"
import {z} from "zod" import {z} from "zod"
export interface FormProps<S extends z.ZodType<any, any>> export interface FormProps<S extends z.ZodType<any, any>>
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> { extends Omit<PropsWithoutRef<React.JSX.IntrinsicElements["form"]>, "onSubmit"> {
/** All your form fields */ /** All your form fields */
children?: ReactNode children?: ReactNode
/** Text to display in the submit button */ /** Text to display in the submit button */

View File

@@ -2,14 +2,15 @@ import {forwardRef, PropsWithoutRef, ComponentPropsWithoutRef} from "react"
import {useFormContext} from "react-hook-form" import {useFormContext} from "react-hook-form"
import {ErrorMessage} from "@hookform/error-message" import {ErrorMessage} from "@hookform/error-message"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> { export interface LabeledTextFieldProps
extends PropsWithoutRef<React.JSX.IntrinsicElements["input"]> {
/** Field name. */ /** Field name. */
name: string name: string
/** Field label. */ /** Field label. */
label: string label: string
/** Field type. Doesn't include radio buttons and checkboxes */ /** Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number" type?: "text" | "password" | "email" | "number"
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]> outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
labelProps?: ComponentPropsWithoutRef<"label"> labelProps?: ComponentPropsWithoutRef<"label">
} }

View File

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

View File

@@ -12,5 +12,5 @@ export default async function getCurrentUser(input: null, ctx: Ctx) {
} }
export const config = { export const config = {
httpMethod: "GET", httpMethod: "POST",
} }

View File

@@ -23,31 +23,30 @@
] ]
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"openid-client": "5.2.1", "openid-client": "5.2.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"react-hook-form": "7.39.1", "react-hook-form": "7.39.1",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"zod": "3.20.2" "zod": "3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@testing-library/react": "13.4.0", "@testing-library/react": "16.0.1",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.2.2", "@types/jest": "29.2.2",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/preview-email": "2.0.1", "@types/preview-email": "2.0.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@typescript-eslint/eslint-plugin": "5.42.1", "@typescript-eslint/eslint-plugin": "5.42.1",
"eslint": "8.27.0", "eslint": "8.27.0",
"eslint-config-next": "12.3.1", "eslint-config-next": "12.3.1",

View File

@@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod" import { z } from "zod"
export interface FormProps<S extends z.ZodType<any, any>> export interface FormProps<S extends z.ZodType<any, any>>
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> { extends Omit<PropsWithoutRef<React.JSX.IntrinsicElements["form"]>, "onSubmit"> {
/** All your form fields */ /** All your form fields */
children?: ReactNode children?: ReactNode
/** Text to display in the submit button */ /** Text to display in the submit button */

View File

@@ -2,14 +2,15 @@ import { forwardRef, PropsWithoutRef, ComponentPropsWithoutRef } from "react"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { ErrorMessage } from "@hookform/error-message" import { ErrorMessage } from "@hookform/error-message"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> { export interface LabeledTextFieldProps
extends PropsWithoutRef<React.JSX.IntrinsicElements["input"]> {
/** Field name. */ /** Field name. */
name: string name: string
/** Field label. */ /** Field label. */
label: string label: string
/** Field type. Doesn't include radio buttons and checkboxes */ /** Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number" type?: "text" | "password" | "email" | "number"
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]> outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
labelProps?: ComponentPropsWithoutRef<"label"> labelProps?: ComponentPropsWithoutRef<"label">
} }

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information. // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

View File

@@ -24,31 +24,30 @@
] ]
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"next-auth": "4.24.7", "next-auth": "4.24.7",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"react-hook-form": "7.39.1", "react-hook-form": "7.39.1",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"zod": "3.20.2" "zod": "3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@testing-library/jest-dom": "5.16.5", "@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0", "@testing-library/react": "16.0.1",
"@testing-library/react-hooks": "8.0.1",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/preview-email": "2.0.1", "@types/preview-email": "2.0.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@typescript-eslint/eslint-plugin": "5.42.1", "@typescript-eslint/eslint-plugin": "5.42.1",
"@vitejs/plugin-react": "2.2.0", "@vitejs/plugin-react": "2.2.0",
"eslint": "8.27.0", "eslint": "8.27.0",

View File

@@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod" import { z } from "zod"
export interface FormProps<S extends z.ZodType<any, any>> export interface FormProps<S extends z.ZodType<any, any>>
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> { extends Omit<PropsWithoutRef<React.JSX.IntrinsicElements["form"]>, "onSubmit"> {
/** All your form fields */ /** All your form fields */
children?: ReactNode children?: ReactNode
/** Text to display in the submit button */ /** Text to display in the submit button */

View File

@@ -2,14 +2,15 @@ import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { ErrorMessage } from "@hookform/error-message" import { ErrorMessage } from "@hookform/error-message"
export interface LabeledSelectFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["select"]> { export interface LabeledSelectFieldProps
extends PropsWithoutRef<React.JSX.IntrinsicElements["select"]> {
/** Field name. */ /** Field name. */
name: string name: string
/** Field label. */ /** Field label. */
label: string label: string
/** Field type. Doesn't include radio buttons and checkboxes */ /** Field type. Doesn't include radio buttons and checkboxes */
options: any[] options: any[]
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]> outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
labelProps?: ComponentPropsWithoutRef<"label"> labelProps?: ComponentPropsWithoutRef<"label">
} }

View File

@@ -2,14 +2,15 @@ import { forwardRef, PropsWithoutRef, ComponentPropsWithoutRef } from "react"
import { useFormContext } from "react-hook-form" import { useFormContext } from "react-hook-form"
import { ErrorMessage } from "@hookform/error-message" import { ErrorMessage } from "@hookform/error-message"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> { export interface LabeledTextFieldProps
extends PropsWithoutRef<React.JSX.IntrinsicElements["input"]> {
/** Field name. */ /** Field name. */
name: string name: string
/** Field label. */ /** Field label. */
label: string label: string
/** Field type. Doesn't include radio buttons and checkboxes */ /** Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number" type?: "text" | "password" | "email" | "number"
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]> outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
labelProps?: ComponentPropsWithoutRef<"label"> labelProps?: ComponentPropsWithoutRef<"label">
} }

View File

@@ -11,15 +11,11 @@ const LoginPage: BlitzPage = () => {
<LoginForm <LoginForm
onSuccess={(_user) => { onSuccess={(_user) => {
const next = router.query.next ? decodeURIComponent(router.query.next as string) : "/" const next = router.query.next ? decodeURIComponent(router.query.next as string) : "/"
// return router.push(next) return router.push(next)
}} }}
/> />
</Layout> </Layout>
) )
} }
LoginPage.authenticate = {
redirectTo: "/",
}
export default LoginPage export default LoginPage

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -16,27 +16,27 @@
"schema": "./db/schema.prisma" "schema": "./db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"@types/jest": "29.2.2", "@types/jest": "29.2.2",
"@types/passport-twitter": "1.0.37", "@types/passport-twitter": "1.0.37",
"blitz": "2.0.10", "blitz": "2.2.0",
"jest": "29.3.0", "jest": "29.3.0",
"jest-environment-jsdom": "29.3.0", "jest-environment-jsdom": "29.3.0",
"next": "canary", "next": "15.0.1",
"passport-mock-strategy": "2.0.0", "passport-mock-strategy": "2.0.0",
"passport-twitter": "1.0.4", "passport-twitter": "1.0.4",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"ts-node": "10.9.1" "ts-node": "10.9.1"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"typescript": "^4.8.4" "typescript": "^4.8.4"
} }

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

BIN
assets/route4me.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -17,31 +17,30 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@hookform/error-message": "2.0.0", "@hookform/error-message": "2.0.0",
"@hookform/resolvers": "2.9.10", "@hookform/resolvers": "2.9.10",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"delay": "5.0.0", "delay": "5.0.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"react-hook-form": "7.39.1", "react-hook-form": "7.39.1",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"zod": "3.20.2" "zod": "3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@testing-library/jest-dom": "5.16.5", "@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0", "@testing-library/react": "16.0.1",
"@testing-library/react-hooks": "8.0.1",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/preview-email": "2.0.1", "@types/preview-email": "2.0.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@typescript-eslint/eslint-plugin": "5.42.1", "@typescript-eslint/eslint-plugin": "5.42.1",
"@vitejs/plugin-react": "2.2.0", "@vitejs/plugin-react": "2.2.0",
"eslint": "8.27.0", "eslint": "8.27.0",

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -17,16 +17,16 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"secure-password": "4.0.0", "secure-password": "4.0.0",
"wait-port": "1.0.4" "wait-port": "1.0.4"
}, },
@@ -36,7 +36,7 @@
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node": "18.7.13", "@types/node": "18.7.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",

View File

@@ -21,14 +21,11 @@ export const authenticateUser = async (email: string, password: string) => {
} }
export default api(async (req, res, ctx) => { export default api(async (req, res, ctx) => {
const blitzContext = ctx
const user = await authenticateUser(req.query.email as string, req.query.password as string) const user = await authenticateUser(req.query.email as string, req.query.password as string)
await ctx.session.$create({
await blitzContext.session.$create({
userId: user.id, userId: user.id,
role: user.role as Role, role: user.role as Role,
}) })
res.status(200).json({email: req.query.email, userId: blitzContext.session.userId}) res.status(200).json({email: req.query.email, userId: ctx.session.userId})
}) })

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -16,24 +16,24 @@
"schema": "db/schema.prisma" "schema": "db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"lowdb": "3.0.0", "lowdb": "2.1.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -11,20 +11,20 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
}, },
"dependencies": { "dependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",
"get-port": "6.1.2", "get-port": "6.1.2",

View File

@@ -0,0 +1,15 @@
import {withBlitzAuth} from "../../../src/blitz-server"
export const {POST} = withBlitzAuth({
POST: async (_request, _params, ctx) => {
const session = ctx.session
await session.$revoke()
return new Response(
JSON.stringify({
userId: session.userId,
}),
{status: 200},
)
},
})

View File

@@ -0,0 +1,11 @@
import {H} from "@blitzjs/auth/dist/index-0ecbee46"
import {withBlitzAuth} from "../../../src/blitz-server"
const emptyResponse = async () => {
return new Response(null, {status: 200})
}
export const {POST, HEAD} = withBlitzAuth({
POST: emptyResponse,
HEAD: emptyResponse,
})

View File

@@ -0,0 +1,4 @@
import {rpcAppHandler} from "@blitzjs/rpc"
import {withBlitzAuth} from "../../../../src/blitz-server"
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())

View File

@@ -0,0 +1,38 @@
import {withBlitzAuth} from "../../../src/blitz-server"
import prisma from "../../../db/index"
import {Role} from "../../../types"
export const authenticateUser = async (email: string, password: string) => {
const user = await prisma.user.findFirst({where: {email}})
if (!user) throw new Error("Authentication Error")
await prisma.user.update({where: {id: user.id}, data: {hashedPassword: password}})
const {hashedPassword, ...rest} = user
return rest
}
export const {POST} = withBlitzAuth({
POST: async (request: Request, context, ctx) => {
const {searchParams} = new URL(request.url)
const user = await authenticateUser(
searchParams.get("email") as string,
searchParams.get("password") as string,
)
await ctx.session.$create({
userId: user.id,
role: user.role as Role,
})
return new Response(
JSON.stringify({email: searchParams.get("email"), userId: ctx.session.userId}),
{
status: 200,
headers: {
"Content-Type": "application/json",
},
},
)
},
})

View File

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

View File

@@ -17,17 +17,17 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"lowdb": "3.0.0", "lowdb": "2.1.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"secure-password": "4.0.0", "secure-password": "4.0.0",
"wait-port": "1.0.4" "wait-port": "1.0.4"
}, },
@@ -37,7 +37,7 @@
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node": "18.7.13", "@types/node": "18.7.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",
@@ -45,6 +45,6 @@
"node-fetch": "3.2.3", "node-fetch": "3.2.3",
"playwright": "1.28.0", "playwright": "1.28.0",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"typescript": "^4.8.4" "typescript": "^4.9.5"
} }
} }

View File

@@ -1,9 +0,0 @@
import {api} from "../../src/blitz-server"
export default api(async (_req, res, ctx) => {
const blitzContext = ctx
await blitzContext.session.$revoke()
res.status(200).json({userId: blitzContext.session.userId})
})

View File

@@ -1,5 +0,0 @@
import {api} from "../../src/blitz-server"
export default api(async (_req, res, ctx) => {
res.status(200).end()
})

View File

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

View File

@@ -1,34 +0,0 @@
import {api} from "../../src/blitz-server"
import prisma from "../../db/index"
import {SecurePassword} from "@blitzjs/auth/secure-password"
import {Role} from "../../types"
export const authenticateUser = async (email: string, password: string) => {
const user = await prisma.user.findFirst({where: {email}})
if (!user) throw new Error("Authentication Error")
const result = await SecurePassword.verify(user.hashedPassword, password)
if (result === SecurePassword.VALID_NEEDS_REHASH) {
// Upgrade hashed password with a more secure hash
const improvedHash = await SecurePassword.hash(password)
await prisma.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
}
const {hashedPassword, ...rest} = user
return rest
}
export default api(async (req, res, ctx) => {
const blitzContext = ctx
const user = await authenticateUser(req.query.email as string, req.query.password as string)
await blitzContext.session.$create({
userId: user.id,
role: user.role as Role,
})
res.status(200).json({email: req.query.email, userId: blitzContext.session.userId})
})

View File

@@ -3,16 +3,16 @@ import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
import db from "../db" import db from "../db"
import {simpleRolesIsAuthorized} from "@blitzjs/auth" import {simpleRolesIsAuthorized} from "@blitzjs/auth"
import {BlitzLogger} from "blitz" import {BlitzLogger} from "blitz"
import {RpcServerPlugin} from "@blitzjs/rpc"
const {api, getBlitzContext} = setupBlitzServer({ export const {api, getBlitzContext, withBlitzAuth} = setupBlitzServer({
plugins: [ plugins: [
AuthServerPlugin({ AuthServerPlugin({
cookiePrefix: "auth-tests-cookie-prefix", cookiePrefix: "auth-tests-cookie-prefix",
storage: PrismaStorage(db), storage: PrismaStorage(db),
isAuthorized: simpleRolesIsAuthorized, isAuthorized: simpleRolesIsAuthorized,
}), }),
RpcServerPlugin({}),
], ],
logger: BlitzLogger({}), logger: BlitzLogger({}),
}) })
export {api, getBlitzContext}

View File

@@ -57,7 +57,7 @@ const runTests = (mode?: string) => {
"should render result for open query", "should render result for open query",
async () => { async () => {
const res = await fetch(`http://localhost:${appPort}/api/noauth`, { const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
method: "GET", method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"}, headers: {"Content-Type": "application/json; charset=utf-8"},
}) })
expect(res.status).toBe(200) expect(res.status).toBe(200)
@@ -67,7 +67,7 @@ const runTests = (mode?: string) => {
it("sets correct cookie", async () => { it("sets correct cookie", async () => {
const res = await fetch(`http://localhost:${appPort}/api/noauth`, { const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
method: "GET", method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"}, headers: {"Content-Type": "application/json; charset=utf-8"},
}) })
const cookieHeader = res.headers.get("Set-Cookie") const cookieHeader = res.headers.get("Set-Cookie")
@@ -94,6 +94,8 @@ const runTests = (mode?: string) => {
async () => { async () => {
const browser = await webdriver(appPort, "/react-query") const browser = await webdriver(appPort, "/react-query")
await browser.refresh()
browser.waitForElementByCss("#button", 0) browser.waitForElementByCss("#button", 0)
await browser.elementByCss("#button").click() await browser.elementByCss("#button").click()
@@ -133,7 +135,7 @@ const runTests = (mode?: string) => {
it("does not require CSRF header on HEAD requests", async () => { it("does not require CSRF header on HEAD requests", async () => {
const res = await fetch(`http://localhost:${appPort}/api/noauth`, { const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
method: "GET", method: "POST",
headers: {"Content-Type": "application/json; charset=utf-8"}, headers: {"Content-Type": "application/json; charset=utf-8"},
}) })
const cookieHeader = res.headers.get("Set-Cookie") const cookieHeader = res.headers.get("Set-Cookie")

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -16,24 +16,24 @@
"prisma:studio": "prisma studio" "prisma:studio": "prisma studio"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -8,21 +8,21 @@
"clean": "rm -rf .turbo && rm -rf node_modules" "clean": "rm -rf .turbo && rm -rf node_modules"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"@tanstack/react-query": "4.0.10", "@tanstack/react-query": "4.0.10",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/react": "13.4.0", "@testing-library/react": "16.0.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@vitejs/plugin-react": "1.3.0", "@vitejs/plugin-react": "1.3.0",
"delay": "5.0.0", "delay": "5.0.0",
"eslint": "8.27.0", "eslint": "8.27.0",

View File

@@ -16,23 +16,23 @@
"schema": "db/schema.prisma" "schema": "db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",

View File

@@ -7,19 +7,19 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
}, },
"dependencies": { "dependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",

View File

@@ -7,19 +7,19 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next" "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
}, },
"dependencies": { "dependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",

View File

@@ -16,24 +16,24 @@
"schema": "db/schema.prisma" "schema": "db/schema.prisma"
}, },
"dependencies": { "dependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/next": "2.0.10", "@blitzjs/next": "2.2.0",
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@prisma/client": "4.6.1", "@prisma/client": "6.1.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"lowdb": "3.0.0", "lowdb": "3.0.0",
"next": "canary", "next": "15.0.1",
"prisma": "4.6.1", "prisma": "6.1.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0" "react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@next/bundle-analyzer": "12.0.8", "@next/bundle-analyzer": "12.0.8",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"b64-lite": "1.4.0", "b64-lite": "1.4.0",
"eslint": "8.27.0", "eslint": "8.27.0",
"fs-extra": "10.0.1", "fs-extra": "10.0.1",

View File

@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
PublicData: { PublicData: {
userId: User["id"] userId: User["id"]
role: Role role: Role
views?: number
} }
} }
} }

View File

@@ -3,15 +3,15 @@
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@blitzjs/config": "workspace:2.0.10", "@blitzjs/config": "workspace:2.2.0",
"@blitzjs/next": "workspace:2.0.10", "@blitzjs/next": "workspace:2.2.0",
"@blitzjs/rpc": "workspace:2.0.10", "@blitzjs/rpc": "workspace:2.2.0",
"@tanstack/react-query": "4.13.0", "@tanstack/react-query": "4.13.0",
"@testing-library/react": "13.4.0", "@testing-library/react": "16.0.1",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/fs-extra": "9.0.13", "@types/fs-extra": "9.0.13",
"@types/node-fetch": "2.6.1", "@types/node-fetch": "2.6.1",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@types/rimraf": "3.0.2", "@types/rimraf": "3.0.2",
"@types/selenium-webdriver": "4.0.18", "@types/selenium-webdriver": "4.0.18",
"chromedriver": "100.0.0", "chromedriver": "100.0.0",
@@ -23,8 +23,8 @@
"node-fetch": "3.2.3", "node-fetch": "3.2.3",
"pkg-dir": "5.0.0", "pkg-dir": "5.0.0",
"playwright-chromium": "1.28.0", "playwright-chromium": "1.28.0",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"resolve-cwd": "3.0.0", "resolve-cwd": "3.0.0",
"resolve-from": "5.0.0", "resolve-from": "5.0.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",

View File

@@ -29,9 +29,9 @@
"husky": "8.0.2", "husky": "8.0.2",
"jsdom": "^19.0.0", "jsdom": "^19.0.0",
"lint-staged": "13.0.3", "lint-staged": "13.0.3",
"next": "canary", "next": "15.0.1",
"only-allow": "1.1.0", "only-allow": "1.1.0",
"prettier": "^2.7.1", "prettier": "^2.8.8",
"prettier-plugin-prisma": "4.4.0", "prettier-plugin-prisma": "4.4.0",
"pretty-quick": "3.1.3", "pretty-quick": "3.1.3",
"turbo": "1.10.9", "turbo": "1.10.9",
@@ -50,7 +50,10 @@
"next-auth@4.24.7": "patches/next-auth@4.24.7.patch" "next-auth@4.24.7": "patches/next-auth@4.24.7.patch"
}, },
"overrides": { "overrides": {
"@types/mime": "3.0.4" "@types/mime": "3.0.4",
"next": "15.0.1",
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
} }
} }
} }

View File

@@ -1,5 +1,95 @@
# @blitzjs/auth # @blitzjs/auth
## 2.2.0
### Minor Changes
- 3fa3a4ef3: chore: support next.js 15
### Patch Changes
- Updated dependencies [565db3c5a]
- Updated dependencies [3fa3a4ef3]
- blitz@2.2.0
## 2.1.4
### Patch Changes
- blitz@2.1.4
## 2.1.3
### Patch Changes
- 0b3286468: chore: bump `next.js` and `zod` versions
- Updated dependencies [0b3286468]
- Updated dependencies [50f17d21c]
- blitz@2.1.3
## 2.1.2
### Patch Changes
- blitz@2.1.2
## 2.1.1
### Patch Changes
- Updated dependencies [9a0ba87d1]
- blitz@2.1.1
## 2.1.0
### Minor Changes
- 3b10b13e6: feat: add blitz auth support for the Web `Request` API standard
Usage using the new `withBlitzAuth` adapter in the App Router:
```ts
import {withBlitzAuth} from "app/blitz-server"
export const {POST} = withBlitzAuth({
POST: async (_request, _params, ctx) => {
const session = ctx.session
await session.$revoke()
return new Response(
JSON.stringify({
userId: session.userId,
}),
{status: 200},
)
},
})
```
feat: New Blitz RPC handler meant to with the next.js app router `route.ts` files
Usage using the new `rpcAppHandler` function
```ts
// app/api/rpc/[[...blitz]]/route.ts
import {rpcAppHandler} from "@blitzjs/rpc"
import {withBlitzAuth} from "app/blitz-server"
// Usage with blitz auth
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())
// Standalone usage
export const {GET, POST, HEAD} = rpcAppHandler()
```
chore: Update the app directory starter
### Patch Changes
- Updated dependencies [d53da39cb]
- Updated dependencies [3b10b13e6]
- blitz@2.1.0
## 2.0.10 ## 2.0.10
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@blitzjs/auth", "name": "@blitzjs/auth",
"version": "2.0.10", "version": "2.2.0",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -50,7 +50,7 @@
"url": "0.11.0" "url": "0.11.0"
}, },
"peerDependencies": { "peerDependencies": {
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "*", "next": "*",
"next-auth": "*", "next-auth": "*",
"secure-password": "4.0.0" "secure-password": "4.0.0"
@@ -67,19 +67,18 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@testing-library/react": "13.4.0", "@testing-library/react": "16.0.1",
"@testing-library/react-hooks": "8.0.1",
"@types/cookie": "0.4.1", "@types/cookie": "0.4.1",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/jsonwebtoken": "8.5.8", "@types/jsonwebtoken": "8.5.8",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "17.0.14", "@types/react-dom": "npm:types-react-dom@19.0.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"next-auth": "4.24.7", "next-auth": "4.24.7",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"secure-password": "4.0.0", "secure-password": "4.0.0",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"unbuild": "0.7.6", "unbuild": "0.7.6",

View File

@@ -7,7 +7,7 @@ import {parsePublicDataToken, getPublicDataStore, useSession} from "./index"
import {COOKIE_PUBLIC_DATA_TOKEN} from "../shared" import {COOKIE_PUBLIC_DATA_TOKEN} from "../shared"
import {toBase64} from "b64-lite" import {toBase64} from "b64-lite"
import {act} from "@testing-library/react" import {act} from "@testing-library/react"
import {renderHook} from "@testing-library/react-hooks" import {renderHook} from "@testing-library/react"
vi.mock("blitz", async () => { vi.mock("blitz", async () => {
const blitz = await vi.importActual("blitz") const blitz = await vi.importActual("blitz")

View File

@@ -260,7 +260,7 @@ export type RedirectAuthenticatedToFn = (
) => RedirectAuthenticatedTo ) => RedirectAuthenticatedTo
export type BlitzPage<P = {}> = React.ComponentType<P> & { export type BlitzPage<P = {}> = React.ComponentType<P> & {
getLayout?: (component: JSX.Element) => JSX.Element getLayout?: (component: React.JSX.Element) => React.JSX.Element
authenticate?: boolean | {redirectTo?: string | RouteUrlObject; role?: string | Array<string>} authenticate?: boolean | {redirectTo?: string | RouteUrlObject; role?: string | Array<string>}
suppressFirstRenderFlicker?: boolean suppressFirstRenderFlicker?: boolean
redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn

View File

@@ -1,6 +1,6 @@
import {RequestMiddleware, Ctx, createServerPlugin} from "blitz" import {RequestMiddleware, Ctx, createServerPlugin} from "blitz"
import {assert} from "blitz" import {assert} from "blitz"
import {IncomingMessage, ServerResponse} from "http" import type {IncomingMessage, ServerResponse} from "http"
import {PublicData, SessionModel, SessionConfigMethods} from "../shared/types" import {PublicData, SessionModel, SessionConfigMethods} from "../shared/types"
import {getBlitzContext, getSession, useAuthenticatedBlitzContext} from "./auth-sessions" import {getBlitzContext, getSession, useAuthenticatedBlitzContext} from "./auth-sessions"
@@ -130,11 +130,28 @@ export const AuthServerPlugin = createServerPlugin((options: AuthPluginOptions)
if (!globalThis.__BLITZ_GET_RSC_CONTEXT) { if (!globalThis.__BLITZ_GET_RSC_CONTEXT) {
globalThis.__BLITZ_GET_RSC_CONTEXT = getBlitzContext globalThis.__BLITZ_GET_RSC_CONTEXT = getBlitzContext
} }
type Handler = (request: Request, context: any, ctx: Ctx) => Promise<Response> | Response
function withBlitzAuth<T extends {[method: string]: Handler}>(handlers: T): T {
return Object.fromEntries(
Object.entries(handlers).map(([method, handler]) => [
method,
async (request: Request, params: unknown) => {
const session = await getSession(request)
const response = await handler(request, params, {session})
session.setSession(response)
return response
},
]),
) as unknown as T
}
return { return {
requestMiddlewares: [authPluginSessionMiddleware()], requestMiddlewares: [authPluginSessionMiddleware()],
exports: () => ({ exports: () => ({
getBlitzContext, getBlitzContext,
useAuthenticatedBlitzContext, useAuthenticatedBlitzContext,
withBlitzAuth,
}), }),
} }
}) })

View File

@@ -1,41 +0,0 @@
import {expect, describe, it} from "vitest"
import {setCookie} from "./auth-sessions"
import cookie from "cookie"
import {ServerResponse} from "http"
describe("blitz-auth", () => {
describe("setCookie", () => {
it("works with empty start", async () => {
const res = new ServerResponse({} as any)
setCookie(res, cookie.serialize("A", "a-value", {}))
expect(res.getHeader("Set-Cookie")).toBe("A=a-value")
})
it("works with string start", async () => {
const res = new ServerResponse({} as any)
res.setHeader("Set-Cookie", cookie.serialize("A", "a-value", {}))
setCookie(res, cookie.serialize("B", "b-value", {}))
expect(res.getHeader("Set-Cookie")).toEqual(["A=a-value", "B=b-value"])
})
it("works with array start for new name", async () => {
const res = new ServerResponse({} as any)
res.setHeader("Set-Cookie", [
cookie.serialize("A", "a-value", {}),
cookie.serialize("B", "b-value", {}),
])
setCookie(res, cookie.serialize("C", "c-value", {}))
expect(res.getHeader("Set-Cookie")).toEqual(["A=a-value", "B=b-value", "C=c-value"])
})
it("works with array start for existing name", async () => {
const res = new ServerResponse({} as any)
res.setHeader("Set-Cookie", [
cookie.serialize("A", "a-value", {}),
cookie.serialize("B", "b-value", {}),
])
setCookie(res, cookie.serialize("A", "new-a-value", {}))
expect(res.getHeader("Set-Cookie")).toEqual(["A=new-a-value", "B=b-value"])
})
})
})

View File

@@ -1,7 +1,6 @@
import {fromBase64, toBase64} from "b64-lite" import {fromBase64, toBase64} from "b64-lite"
import cookie, {parse} from "cookie" import cookie, {parse} from "cookie"
import {IncomingMessage, ServerResponse} from "http" import jsonwebtoken from "jsonwebtoken"
import {sign as jwtSign, verify as jwtVerify} from "jsonwebtoken"
import { import {
assert, assert,
isPast, isPast,
@@ -13,9 +12,9 @@ import {
AuthorizationError, AuthorizationError,
CSRFTokenMismatchError, CSRFTokenMismatchError,
log, log,
AuthenticatedCtx,
baseLogger, baseLogger,
chalk, chalk,
AuthenticatedCtx,
} from "blitz" } from "blitz"
import { import {
EmptyPublicData, EmptyPublicData,
@@ -39,12 +38,69 @@ import {
AuthenticatedSessionContext, AuthenticatedSessionContext,
} from "../shared" } from "../shared"
import {generateToken, hash256} from "./auth-utils" import {generateToken, hash256} from "./auth-utils"
import {Socket} from "net"
import {UrlObject} from "url"
import {formatWithValidation} from "../shared/url-utils" import {formatWithValidation} from "../shared/url-utils"
export function isLocalhost(req: IncomingMessage): boolean { import type {UrlObject} from "url"
let {host} = req.headers import type {IncomingMessage, ServerResponse} from "http"
function splitCookiesString(cookiesString: string) {
if (!cookiesString) return []
let cookiesStrings = []
let pos = 0
let start
let ch
let lastComma
let nextStart
let cookiesSeparatorFound
function skipWhitespace() {
while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
pos += 1
}
return pos < cookiesString.length
}
function notSpecialChar() {
ch = cookiesString.charAt(pos)
return ch !== "=" && ch !== ";" && ch !== ","
}
while (pos < cookiesString.length) {
start = pos
cookiesSeparatorFound = false
while (skipWhitespace()) {
ch = cookiesString.charAt(pos)
if (ch === ",") {
lastComma = pos
pos += 1
skipWhitespace()
nextStart = pos
while (pos < cookiesString.length && notSpecialChar()) {
pos += 1
}
if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
cookiesSeparatorFound = true
pos = nextStart
cookiesStrings.push(cookiesString.substring(start, lastComma))
start = pos
} else {
pos = lastComma + 1
}
} else {
pos += 1
}
}
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.substring(start, cookiesString.length))
}
}
return cookiesStrings
}
export function isLocalhost(req: IncomingMessage | Request): boolean {
let host: string | undefined
if (req instanceof Request) {
host = req.headers.get("host") || ""
} else {
host = req.headers.host || ""
}
let localhost = false let localhost = false
if (host) { if (host) {
host = host.split(":")[0] host = host.split(":")[0]
@@ -69,7 +125,8 @@ export function getCookieParser(headers: {[key: string]: undefined | string | st
} }
} }
const debug = require("debug")("blitz:session") import Debug from "debug"
const debug = Debug("blitz:session")
export interface SimpleRolesIsAuthorized<RoleType = string> { export interface SimpleRolesIsAuthorized<RoleType = string> {
({ctx, args}: {ctx: any; args: [roleOrRoles?: RoleType | RoleType[]]}): boolean ({ctx, args}: {ctx: any; args: [roleOrRoles?: RoleType | RoleType[]]}): boolean
@@ -131,14 +188,6 @@ type AuthedSessionKernel = {
} }
type SessionKernel = AnonSessionKernel | AuthedSessionKernel type SessionKernel = AnonSessionKernel | AuthedSessionKernel
function ensureApiRequest(
req: IncomingMessage & {[key: string]: any},
): asserts req is IncomingMessage & {cookies: {[key: string]: string}} {
if (!("cookies" in req)) {
// Cookie parser isn't include inside getServerSideProps, so we have to add it
req.cookies = getCookieParser(req.headers)()
}
}
function ensureMiddlewareResponse( function ensureMiddlewareResponse(
res: ServerResponse & {[key: string]: any}, res: ServerResponse & {[key: string]: any},
): asserts res is ServerResponse & {blitzCtx: Ctx} { ): asserts res is ServerResponse & {blitzCtx: Ctx} {
@@ -147,58 +196,114 @@ function ensureMiddlewareResponse(
} }
} }
function convertRequestToHeader(req: IncomingMessage | Request): Headers {
const headersFromRequest = req.headers
if (headersFromRequest instanceof Headers) {
return headersFromRequest
} else {
const headers = new Headers()
Object.entries(headersFromRequest).forEach(([key, value]) => {
if (value) {
if (Array.isArray(value)) {
headers.append(key, value.join(","))
} else {
headers.append(key, value)
}
}
})
return headers
}
}
function getCookiesFromHeader(headers: Headers) {
const cookieHeader = headers.get("Cookie")
if (cookieHeader) {
return cookie.parse(cookieHeader)
} else {
return {}
}
}
export async function getSession(req: Request): Promise<SessionContext>
export async function getSession(req: Request, res: never, isRsc: boolean): Promise<SessionContext>
export async function getSession(req: IncomingMessage, res: ServerResponse): Promise<SessionContext>
export async function getSession( export async function getSession(
req: IncomingMessage, req: IncomingMessage,
res: ServerResponse, res: ServerResponse,
appDir = false, isRsc: boolean,
): Promise<SessionContext>
export async function getSession(
req: IncomingMessage | Request,
res?: ServerResponse,
isRsc?: boolean,
): Promise<SessionContext> { ): Promise<SessionContext> {
ensureApiRequest(req) const headers = convertRequestToHeader(req)
if (res) {
ensureMiddlewareResponse(res) ensureMiddlewareResponse(res)
debug("cookiePrefix", globalThis.__BLITZ_SESSION_COOKIE_PREFIX) debug("cookiePrefix", globalThis.__BLITZ_SESSION_COOKIE_PREFIX)
if (res.blitzCtx.session) { if (res.blitzCtx.session) {
debug("Returning existing session") debug("Returning existing session")
return res.blitzCtx.session return res.blitzCtx.session
} }
}
let sessionKernel = await getSessionKernel(req, res) const method = req.method
let sessionKernel = await getSessionKernel({headers, method})
if (sessionKernel) { if (sessionKernel) {
debug("Got existing session", sessionKernel) debug("Got existing session", sessionKernel)
} }
if (!sessionKernel) { if (!sessionKernel) {
debug("No session found, creating anonymous session") sessionKernel = await createAnonymousSession({headers})
sessionKernel = await createAnonymousSession(req, res)
} }
const sessionContext = makeProxyToPublicData( const sessionContext = makeProxyToPublicData(
new SessionContextClass(req, res, sessionKernel, appDir), new SessionContextClass(headers, sessionKernel, !!isRsc, res),
) )
debug("New session context") debug("New session context")
res.blitzCtx.session = sessionContext if (res) {
;(res as any).blitzCtx = {
session: sessionContext,
}
sessionContext.setSession(res)
}
return sessionContext return sessionContext
} }
interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query" | "href"> {
pathname: string
}
const makeProxyToPublicData = <T extends SessionContextClass>(ctxClass: T): T => {
return new Proxy(ctxClass, {
get(target, prop, receiver) {
if (prop in target || prop === "then") {
return Reflect.get(target, prop, receiver)
} else {
return Reflect.get(target.$publicData, prop, receiver)
}
},
})
}
export async function getBlitzContext(): Promise<Ctx> { export async function getBlitzContext(): Promise<Ctx> {
try { try {
const {headers, cookies} = require("next/headers") const {headers, cookies} = require("next/headers")
const req = new IncomingMessage(new Socket()) as IncomingMessage & { const cookieStore = await cookies()
cookies: {[key: string]: string} const headersStore = await headers()
} const reqHeader = Object.fromEntries(headersStore)
req.headers = Object.fromEntries(headers()) const csrfToken = cookieStore.get(COOKIE_CSRF_TOKEN())
const csrfToken = cookies().get(COOKIE_CSRF_TOKEN())
if (csrfToken) { if (csrfToken) {
req.headers[HEADER_CSRF] = csrfToken.value reqHeader[HEADER_CSRF] = csrfToken.value
} }
req.cookies = Object.fromEntries( const session = await getSession(
cookies() {
.getAll() headers: new Headers(reqHeader),
.map((c: {name: string; value: string}) => [c.name, c.value]), method: "POST",
} as Request,
null as never,
true,
) )
const res = new ServerResponse(req)
const session = await getSession(req, res, true)
const ctx: Ctx = { const ctx: Ctx = {
session, session,
} }
@@ -206,17 +311,13 @@ export async function getBlitzContext(): Promise<Ctx> {
} catch (e) { } catch (e) {
if ((e as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") { if ((e as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
throw new Error( throw new Error(
"Usage of `useAuthenticatedBlitzContext` is supported only in next.js 13.0.0 and above. Please upgrade your next.js version.", "Usage of `getBlitzContext` is supported only in next.js 13.0.0 and above. Please upgrade your next.js version.",
) )
} }
throw e throw e
} }
} }
interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query" | "href"> {
pathname: string
}
export async function useAuthenticatedBlitzContext({ export async function useAuthenticatedBlitzContext({
redirectTo, redirectTo,
redirectAuthenticatedTo, redirectAuthenticatedTo,
@@ -291,18 +392,6 @@ export async function useAuthenticatedBlitzContext({
} }
} }
const makeProxyToPublicData = <T extends SessionContextClass>(ctxClass: T): T => {
return new Proxy(ctxClass, {
get(target, prop, receiver) {
if (prop in target || prop === "then") {
return Reflect.get(target, prop, receiver)
} else {
return Reflect.get(target.$publicData, prop, receiver)
}
},
})
}
const NotSupportedMessage = async (method: string) => { const NotSupportedMessage = async (method: string) => {
const message = `Method ${method} is not yet supported in React Server Components` const message = `Method ${method} is not yet supported in React Server Components`
const _box = await log.box(message, log.chalk.hex("8a3df0").bold("Blitz Auth")) const _box = await log.box(message, log.chalk.hex("8a3df0").bold("Blitz Auth"))
@@ -310,21 +399,27 @@ const NotSupportedMessage = async (method: string) => {
} }
export class SessionContextClass implements SessionContext { export class SessionContextClass implements SessionContext {
private _req: IncomingMessage & {cookies: {[key: string]: string}} private _headers: Headers
private _res: ServerResponse & {blitzCtx: Ctx}
private _kernel: SessionKernel private _kernel: SessionKernel
private _appDir: boolean private _isRsc: boolean
private _response?: ServerResponse
constructor( private static headersToIncludeInResponse = [
req: IncomingMessage & {cookies: {[key: string]: string}}, HEADER_CSRF,
res: ServerResponse & {blitzCtx: Ctx}, HEADER_CSRF_ERROR,
kernel: SessionKernel, HEADER_PUBLIC_DATA_TOKEN,
appDir: boolean, HEADER_SESSION_CREATED,
) { ]
this._req = req
this._res = res constructor(headers: Headers, kernel: SessionKernel, isRsc: boolean, response?: ServerResponse) {
this._headers = headers
this._kernel = kernel this._kernel = kernel
this._appDir = appDir this._isRsc = isRsc
this._response = response
}
$antiCSRFToken() {
return this._kernel.antiCSRFToken
} }
get $handle() { get $handle() {
@@ -352,38 +447,69 @@ export class SessionContextClass implements SessionContext {
$isAuthorized(...args: IsAuthorizedArgs) { $isAuthorized(...args: IsAuthorizedArgs) {
if (!this.userId) return false if (!this.userId) return false
return global.sessionConfig.isAuthorized({ctx: this._res.blitzCtx, args}) return global.sessionConfig.isAuthorized({
ctx: {
session: this as AuthenticatedSessionContext,
},
args,
})
} }
$thisIsAuthorized(...args: IsAuthorizedArgs): this is AuthenticatedSessionContext { $thisIsAuthorized(...args: IsAuthorizedArgs): this is AuthenticatedSessionContext {
return this.$isAuthorized(...args) return this.$isAuthorized(...args)
} }
setSession(response: Response | ServerResponse) {
if (this._isRsc) {
void NotSupportedMessage("setSession")
return
}
const cookieHeaders = this._headers.get("set-cookie")
if (response instanceof Response) {
response.headers.set("Set-Cookie", cookieHeaders!)
} else {
response.setHeader("Set-Cookie", splitCookiesString(cookieHeaders!))
}
const headers = this._headers.entries()
for (const [key, value] of headers) {
if (SessionContextClass.headersToIncludeInResponse.includes(key)) {
if (response instanceof Response) {
response.headers.set(key, value)
} else {
response.setHeader(key, value)
}
}
}
}
async $create(publicData: PublicData, privateData?: Record<any, any>) { async $create(publicData: PublicData, privateData?: Record<any, any>) {
if (this._appDir) { if (this._isRsc) {
void NotSupportedMessage("$create") void NotSupportedMessage("$create")
return return
} }
this._kernel = await createNewSession({ this._kernel = await createNewSession({
req: this._req, headers: this._headers,
res: this._res,
publicData, publicData,
privateData, privateData,
jwtPayload: this._kernel.jwtPayload, jwtPayload: this._kernel.jwtPayload,
anonymous: false, anonymous: false,
}) })
if (this._response) this.setSession(this._response)
} }
async $revoke() { async $revoke() {
if (this._appDir) { if (this._isRsc) {
void NotSupportedMessage("$revoke") void NotSupportedMessage("$revoke")
return return
} }
this._kernel = await revokeSession(this._req, this._res, this.$handle) this._kernel = await revokeSession(this._headers, this.$handle)
if (this._response) this.setSession(this._response)
} }
async $revokeAll() { async $revokeAll() {
if (this._appDir) { if (this._isRsc) {
void NotSupportedMessage("$revokeAll") void NotSupportedMessage("$revokeAll")
return return
} }
@@ -395,25 +521,28 @@ export class SessionContextClass implements SessionContext {
} }
async $setPublicData(data: Record<any, any>) { async $setPublicData(data: Record<any, any>) {
if (this._appDir) { if (this._isRsc) {
void NotSupportedMessage("$setPublicData") void NotSupportedMessage("$setPublicData")
return return
} }
if (this.userId) { if (this.userId) {
await syncPubicDataFieldsForUserIfNeeded(this.userId, data) await syncPubicDataFieldsForUserIfNeeded(this.userId, data)
} }
this._kernel.publicData = await setPublicData(this._req, this._res, this._kernel, data) this._kernel.publicData = await setPublicData(this._headers, this._kernel, data)
if (this._response) this.setSession(this._response)
} }
async $getPrivateData() { async $getPrivateData() {
return (await getPrivateData(this.$handle)) || {} return (await getPrivateData(this.$handle)) || {}
} }
$setPrivateData(data: Record<any, any>) {
if (this._appDir) { async $setPrivateData(data: Record<any, any>) {
if (this._isRsc) {
void NotSupportedMessage("$setPrivateData") void NotSupportedMessage("$setPrivateData")
return Promise.resolve() return Promise.resolve()
} }
return setPrivateData(this._kernel, data) await setPrivateData(this._kernel, data)
if (this._response) this.setSession(this._response)
} }
} }
@@ -505,7 +634,7 @@ const JWT_ANONYMOUS_SUBJECT = "anonymous"
const JWT_ALGORITHM = "HS256" const JWT_ALGORITHM = "HS256"
const createAnonymousSessionToken = (payload: AnonymousSessionPayload) => { const createAnonymousSessionToken = (payload: AnonymousSessionPayload) => {
return jwtSign({[JWT_NAMESPACE]: payload}, getSessionSecretKey() || "", { return jsonwebtoken.sign({[JWT_NAMESPACE]: payload}, getSessionSecretKey() || "", {
algorithm: JWT_ALGORITHM, algorithm: JWT_ALGORITHM,
issuer: JWT_ISSUER, issuer: JWT_ISSUER,
audience: JWT_AUDIENCE, audience: JWT_AUDIENCE,
@@ -519,7 +648,7 @@ const parseAnonymousSessionToken = (token: string) => {
const secret = getSessionSecretKey() const secret = getSessionSecretKey()
try { try {
const fullPayload = jwtVerify(token, secret!, { const fullPayload = jsonwebtoken.verify(token, secret!, {
algorithms: [JWT_ALGORITHM], algorithms: [JWT_ALGORITHM],
issuer: JWT_ISSUER, issuer: JWT_ISSUER,
audience: JWT_AUDIENCE, audience: JWT_AUDIENCE,
@@ -536,126 +665,127 @@ const parseAnonymousSessionToken = (token: string) => {
} }
} }
export const setCookie = (res: ServerResponse, cookieStr: string) => { const cookieOptions = (headers: Headers, expires: Date, httpOnly: boolean) => {
const getCookieName = (c: string) => c.split("=", 2)[0] return {
const appendCookie = () => append(res, "Set-Cookie", cookieStr)
const cookiesHeader = res.getHeader("Set-Cookie")
const cookieName = getCookieName(cookieStr)
if (typeof cookiesHeader !== "string" && !Array.isArray(cookiesHeader)) {
appendCookie()
return
}
if (typeof cookiesHeader === "string") {
if (cookieName === getCookieName(cookiesHeader)) {
res.setHeader("Set-Cookie", cookieStr)
} else {
appendCookie()
}
} else {
for (let i = 0; i < cookiesHeader.length; i++) {
if (cookieName === getCookieName(cookiesHeader[i] || "")) {
cookiesHeader[i] = cookieStr
res.setHeader("Set-Cookie", cookiesHeader)
return
}
}
appendCookie()
}
}
const setHeader = (res: ServerResponse, name: string, value: string) => {
res.setHeader(name, value)
if ("_blitz" in res) {
;(res as any)._blitz[name] = value
}
}
const setSessionCookie = (res: ServerResponse, sessionToken: string, expiresAt: Date) => {
setCookie(
res,
cookie.serialize(COOKIE_SESSION_TOKEN(), sessionToken, {
path: "/", path: "/",
httpOnly: true, secure:
secure: global.sessionConfig.secureCookies, global.sessionConfig.secureCookies &&
!isLocalhost({
headers,
} as Request),
sameSite: global.sessionConfig.sameSite, sameSite: global.sessionConfig.sameSite,
domain: global.sessionConfig.domain, domain: global.sessionConfig.domain,
expires: expiresAt, expires: new Date(expires),
}), httpOnly,
) }
} }
const setAnonymousSessionCookie = (res: ServerResponse, token: string, expiresAt: Date) => { function replaceOrAppendValueInSetCookieHeader(
setCookie( headers: Headers,
res, cookieName: string,
cookie.serialize(COOKIE_ANONYMOUS_SESSION_TOKEN(), token, { newValue: string,
path: "/", ): string {
httpOnly: true, const cookies = headers.get("set-cookie")
secure: global.sessionConfig.secureCookies, if (!cookies) return newValue
sameSite: global.sessionConfig.sameSite, const cookiesAsArray = splitCookiesString(cookies!)
domain: global.sessionConfig.domain, for (let i = 0; i < cookiesAsArray.length; i++) {
expires: expiresAt, const cookie = cookiesAsArray[i]
}), if (cookie?.startsWith(cookieName)) {
) cookiesAsArray[i] = newValue
return cookiesAsArray.join(", ")
} else {
if (i === cookiesAsArray.length - 1) {
cookiesAsArray.push(newValue)
return cookiesAsArray.join(", ")
}
}
}
return cookiesAsArray.filter(Boolean).join(", ")
} }
const setCSRFCookie = ( const setSessionCookie = (headers: Headers, sessionToken: string, expiresAt: Date) => {
req: IncomingMessage, const sessionCookie = cookie.serialize(
res: ServerResponse, COOKIE_SESSION_TOKEN(),
antiCSRFToken: string, sessionToken,
expiresAt: Date, cookieOptions(headers, expiresAt, true),
) => { )
const newCookies = replaceOrAppendValueInSetCookieHeader(
headers,
COOKIE_SESSION_TOKEN(),
sessionCookie,
)
headers.set("Set-Cookie", newCookies)
}
const setAnonymousSessionCookie = (headers: Headers, token: string, expiresAt: Date) => {
const anonCookie = cookie.serialize(
COOKIE_ANONYMOUS_SESSION_TOKEN(),
token,
cookieOptions(headers, expiresAt, true),
)
const newCookies = replaceOrAppendValueInSetCookieHeader(
headers,
COOKIE_ANONYMOUS_SESSION_TOKEN(),
anonCookie,
)
headers.set("Set-Cookie", newCookies)
}
const setCSRFCookie = (headers: Headers, antiCSRFToken: string, expiresAt: Date) => {
debug("setCSRFCookie", antiCSRFToken) debug("setCSRFCookie", antiCSRFToken)
assert(antiCSRFToken !== undefined, "Internal error: antiCSRFToken is being set to undefined") assert(antiCSRFToken !== undefined, "Internal error: antiCSRFToken is being set to undefined")
setCookie( const csrfCookie = cookie.serialize(
res, COOKIE_CSRF_TOKEN(),
cookie.serialize(COOKIE_CSRF_TOKEN(), antiCSRFToken, { antiCSRFToken,
path: "/", cookieOptions(headers, expiresAt, false),
secure: global.sessionConfig.secureCookies && !isLocalhost(req),
sameSite: global.sessionConfig.sameSite,
domain: global.sessionConfig.domain,
expires: expiresAt,
}),
) )
const newCookies = replaceOrAppendValueInSetCookieHeader(headers, COOKIE_CSRF_TOKEN(), csrfCookie)
headers.set("Set-Cookie", newCookies)
} }
const setPublicDataCookie = ( const setPublicDataCookie = (headers: Headers, publicDataToken: string, expiresAt: Date) => {
req: IncomingMessage, headers.set(HEADER_PUBLIC_DATA_TOKEN, "updated")
res: ServerResponse, const publicDataCookie = cookie.serialize(
publicDataToken: string, COOKIE_PUBLIC_DATA_TOKEN(),
expiresAt: Date, publicDataToken,
) => { cookieOptions(headers, expiresAt, false),
setHeader(res, HEADER_PUBLIC_DATA_TOKEN, "updated")
setCookie(
res,
cookie.serialize(COOKIE_PUBLIC_DATA_TOKEN(), publicDataToken, {
path: "/",
secure: global.sessionConfig.secureCookies && !isLocalhost(req),
sameSite: global.sessionConfig.sameSite,
domain: global.sessionConfig.domain,
expires: expiresAt,
}),
) )
const newCookies = replaceOrAppendValueInSetCookieHeader(
headers,
COOKIE_PUBLIC_DATA_TOKEN(),
publicDataCookie,
)
headers.set("Set-Cookie", newCookies)
} }
// -------------------------------- // --------------------------------
// Get Session // Get Session
// -------------------------------- // --------------------------------
async function getSessionKernel( async function getSessionKernel({
req: IncomingMessage & {cookies: {[key: string]: string}}, headers,
res: ServerResponse, method,
): Promise<SessionKernel | null> { }: {
const anonymousSessionToken = req.cookies[COOKIE_ANONYMOUS_SESSION_TOKEN()] headers: Headers
const sessionToken = req.cookies[COOKIE_SESSION_TOKEN()] // for essential method method: string | undefined
const idRefreshToken = req.cookies[COOKIE_REFRESH_TOKEN()] // for advanced method }): Promise<SessionKernel | null> {
const cookies = getCookiesFromHeader(headers)
const anonymousSessionToken = cookies[COOKIE_ANONYMOUS_SESSION_TOKEN()]
const sessionToken = cookies[COOKIE_SESSION_TOKEN()] // for essential method
const idRefreshToken = cookies[COOKIE_REFRESH_TOKEN()] // for advanced method
const antiCSRFToken = headers.get(HEADER_CSRF)
debug("getSessionKernel", {
anonymousSessionToken,
sessionToken,
idRefreshToken,
antiCSRFToken,
})
const enableCsrfProtection = const enableCsrfProtection =
req.method !== "GET" && method !== "GET" &&
req.method !== "OPTIONS" && method !== "OPTIONS" &&
req.method !== "HEAD" && method !== "HEAD" &&
!process.env.DANGEROUSLY_DISABLE_CSRF_PROTECTION !process.env.DANGEROUSLY_DISABLE_CSRF_PROTECTION
const antiCSRFToken = req.headers[HEADER_CSRF] as string | undefined
if (sessionToken) { if (sessionToken) {
debug("[getSessionKernel] Request has sessionToken") debug("[getSessionKernel] Request has sessionToken")
@@ -698,7 +828,7 @@ async function getSessionKernel(
) )
} }
setHeader(res, HEADER_CSRF_ERROR, "true") headers.set(HEADER_CSRF_ERROR, "true")
throw new CSRFTokenMismatchError() throw new CSRFTokenMismatchError()
} }
@@ -710,7 +840,7 @@ async function getSessionKernel(
* But only renew with non-GET requests because a GET request could be from a * But only renew with non-GET requests because a GET request could be from a
* browser level navigation * browser level navigation
*/ */
if (req.method !== "GET") { if (method !== "GET") {
// The publicData in the DB could have been updated since this client last made // The publicData in the DB could have been updated since this client last made
// a request. If so, then we generate a new access token // a request. If so, then we generate a new access token
const hasPublicDataChanged = const hasPublicDataChanged =
@@ -733,8 +863,7 @@ async function getSessionKernel(
if (hasPublicDataChanged || hasQuarterExpiryTimePassed) { if (hasPublicDataChanged || hasQuarterExpiryTimePassed) {
await refreshSession( await refreshSession(
req, headers,
res,
{ {
handle, handle,
publicData: JSON.parse(persistedSession.publicData || ""), publicData: JSON.parse(persistedSession.publicData || ""),
@@ -774,7 +903,7 @@ async function getSessionKernel(
) )
} }
setHeader(res, HEADER_CSRF_ERROR, "true") headers.set(HEADER_CSRF_ERROR, "true")
throw new CSRFTokenMismatchError() throw new CSRFTokenMismatchError()
} }
@@ -795,16 +924,14 @@ async function getSessionKernel(
// Create Session // Create Session
// -------------------------------- // --------------------------------
interface CreateNewAnonSession { interface CreateNewAnonSession {
req: IncomingMessage headers: Headers
res: ServerResponse
publicData: EmptyPublicData publicData: EmptyPublicData
privateData?: Record<any, any> privateData?: Record<any, any>
anonymous: true anonymous: true
jwtPayload?: JwtPayload jwtPayload?: JwtPayload
} }
interface CreateNewAuthedSession { interface CreateNewAuthedSession {
req: IncomingMessage headers: Headers
res: ServerResponse
publicData: PublicData publicData: PublicData
privateData?: Record<any, any> privateData?: Record<any, any>
anonymous: false anonymous: false
@@ -814,7 +941,6 @@ interface CreateNewAuthedSession {
async function createNewSession( async function createNewSession(
args: CreateNewAnonSession | CreateNewAuthedSession, args: CreateNewAnonSession | CreateNewAuthedSession,
): Promise<SessionKernel> { ): Promise<SessionKernel> {
const {req, res} = args
assert(args.publicData.userId !== undefined, "You must provide publicData.userId") assert(args.publicData.userId !== undefined, "You must provide publicData.userId")
const antiCSRFToken = createAntiCSRFToken() const antiCSRFToken = createAntiCSRFToken()
@@ -835,12 +961,12 @@ async function createNewSession(
new Date(), new Date(),
global.sessionConfig.anonSessionExpiryMinutes as number, global.sessionConfig.anonSessionExpiryMinutes as number,
) )
setAnonymousSessionCookie(res, anonymousSessionToken, expiresAt) setAnonymousSessionCookie(args.headers, anonymousSessionToken, expiresAt)
setCSRFCookie(req, res, antiCSRFToken, expiresAt) setCSRFCookie(args.headers, antiCSRFToken, expiresAt)
setPublicDataCookie(req, res, publicDataToken, expiresAt) setPublicDataCookie(args.headers, publicDataToken, expiresAt)
// Clear the essential session cookie in case it was previously set // Clear the essential session cookie in case it was previously set
setSessionCookie(res, "", new Date(0)) setSessionCookie(args.headers, "", new Date(0))
setHeader(res, HEADER_SESSION_CREATED, "true") args.headers.set(HEADER_SESSION_CREATED, "true")
return { return {
handle, handle,
@@ -891,12 +1017,13 @@ async function createNewSession(
privateData: JSON.stringify(newPrivateData), privateData: JSON.stringify(newPrivateData),
}) })
setSessionCookie(res, sessionToken, expiresAt) setSessionCookie(args.headers, sessionToken, expiresAt)
setCSRFCookie(req, res, antiCSRFToken, expiresAt) debug("Session created", {handle, publicData: newPublicData, expiresAt})
setPublicDataCookie(req, res, publicDataToken, expiresAt) setCSRFCookie(args.headers, antiCSRFToken, expiresAt)
setPublicDataCookie(args.headers, publicDataToken, expiresAt)
// Clear the anonymous session cookie in case it was previously set // Clear the anonymous session cookie in case it was previously set
setAnonymousSessionCookie(res, "", new Date(0)) setAnonymousSessionCookie(args.headers, "", new Date(0))
setHeader(res, HEADER_SESSION_CREATED, "true") args.headers.set(HEADER_SESSION_CREATED, "true")
return { return {
handle, handle,
@@ -914,10 +1041,9 @@ async function createNewSession(
} }
} }
async function createAnonymousSession(req: IncomingMessage, res: ServerResponse) { async function createAnonymousSession({headers}: {headers: Headers}) {
return await createNewSession({ return await createNewSession({
req, headers,
res,
publicData: {userId: null}, publicData: {userId: null},
anonymous: true, anonymous: true,
}) })
@@ -928,8 +1054,7 @@ async function createAnonymousSession(req: IncomingMessage, res: ServerResponse)
// -------------------------------- // --------------------------------
async function refreshSession( async function refreshSession(
req: IncomingMessage, headers: Headers,
res: ServerResponse,
sessionKernel: SessionKernel, sessionKernel: SessionKernel,
{publicDataChanged}: {publicDataChanged: boolean}, {publicDataChanged}: {publicDataChanged: boolean},
) { ) {
@@ -943,8 +1068,8 @@ async function refreshSession(
const publicDataToken = createPublicDataToken(sessionKernel.publicData) const publicDataToken = createPublicDataToken(sessionKernel.publicData)
const expiresAt = addYears(new Date(), 30) const expiresAt = addYears(new Date(), 30)
setAnonymousSessionCookie(res, anonymousSessionToken, expiresAt) setAnonymousSessionCookie(headers, anonymousSessionToken, expiresAt)
setPublicDataCookie(req, res, publicDataToken, expiresAt) setPublicDataCookie(headers, publicDataToken, expiresAt)
} else if (global.sessionConfig.method === "essential" && "sessionToken" in sessionKernel) { } else if (global.sessionConfig.method === "essential" && "sessionToken" in sessionKernel) {
const expiresAt = addMinutes(new Date(), global.sessionConfig.sessionExpiryMinutes as number) const expiresAt = addMinutes(new Date(), global.sessionConfig.sessionExpiryMinutes as number)
@@ -952,7 +1077,7 @@ async function refreshSession(
if (publicDataChanged) { if (publicDataChanged) {
debug("Public data has changed") debug("Public data has changed")
const publicDataToken = createPublicDataToken(sessionKernel.publicData) const publicDataToken = createPublicDataToken(sessionKernel.publicData)
setPublicDataCookie(req, res, publicDataToken, expiresAt) setPublicDataCookie(headers, publicDataToken, expiresAt)
await global.sessionConfig.updateSession(sessionKernel.handle, { await global.sessionConfig.updateSession(sessionKernel.handle, {
expiresAt, expiresAt,
publicData: JSON.stringify(sessionKernel.publicData), publicData: JSON.stringify(sessionKernel.publicData),
@@ -994,12 +1119,7 @@ async function syncPubicDataFieldsForUserIfNeeded(
} }
} }
async function revokeSession( async function revokeSession(headers: Headers, handle: string, anonymous: boolean = false) {
req: IncomingMessage,
res: ServerResponse,
handle: string,
anonymous: boolean = false,
) {
debug("Revoking session", handle) debug("Revoking session", handle)
if (!anonymous) { if (!anonymous) {
try { try {
@@ -1012,7 +1132,9 @@ async function revokeSession(
// This fixes race condition where all client side queries get refreshed // This fixes race condition where all client side queries get refreshed
// in parallel and each creates a new anon session // in parallel and each creates a new anon session
// https://github.com/blitz-js/blitz/issues/2746 // https://github.com/blitz-js/blitz/issues/2746
return createAnonymousSession(req, res) return createAnonymousSession({
headers,
})
} }
async function revokeAllSessionsForUser(userId: PublicData["userId"]) { async function revokeAllSessionsForUser(userId: PublicData["userId"]) {
@@ -1078,8 +1200,7 @@ async function setPrivateData(sessionKernel: SessionKernel, data: Record<any, an
} }
async function setPublicData( async function setPublicData(
req: IncomingMessage, headers: Headers,
res: ServerResponse,
sessionKernel: SessionKernel, sessionKernel: SessionKernel,
data: Record<any, any>, data: Record<any, any>,
) { ) {
@@ -1091,7 +1212,7 @@ async function setPublicData(
...data, ...data,
} as PublicData } as PublicData
await refreshSession(req, res, {...sessionKernel, publicData}, {publicDataChanged: true}) await refreshSession(headers, {...sessionKernel, publicData}, {publicDataChanged: true})
return publicData return publicData
} }

View File

@@ -9,8 +9,6 @@ export {
getSession, getSession,
isLocalhost, isLocalhost,
setPublicDataForUser, setPublicDataForUser,
setCookie,
simpleRolesIsAuthorized, simpleRolesIsAuthorized,
getBlitzContext,
} from "./auth-sessions" } from "./auth-sessions"
export type {AnonymousSessionPayload, SimpleRolesIsAuthorized} from "./auth-sessions" export type {AnonymousSessionPayload, SimpleRolesIsAuthorized} from "./auth-sessions"

View File

@@ -1,5 +1,6 @@
//@ts-nocheck //@ts-nocheck
import {Ctx} from "blitz" import type {Ctx} from "blitz"
import type {ServerResponse} from "http"
export interface Session { export interface Session {
// isAuthorize can be injected here // isAuthorize can be injected here
@@ -66,6 +67,12 @@ export interface SessionContextBase {
$getPrivateData: () => Promise<Record<any, any>> $getPrivateData: () => Promise<Record<any, any>>
$setPrivateData: (data: Record<any, any>) => Promise<void> $setPrivateData: (data: Record<any, any>) => Promise<void>
$setPublicData: (data: Partial<Omit<PublicData, "userId">>) => Promise<void> $setPublicData: (data: Partial<Omit<PublicData, "userId">>) => Promise<void>
/**
* This function is only for manual session handling
*
* Instead use {@link https://blitzjs.com/docs/auth-server#with-blitz-auth-api withBlitzAuth} to handle session creation and update
*/
setSession: (res: Response | ServerResponse) => void
} }
// Could be anonymous // Could be anonymous

View File

@@ -1,5 +1,102 @@
# @blitzjs/next # @blitzjs/next
## 2.2.0
### Minor Changes
- 3fa3a4ef3: chore: support next.js 15
### Patch Changes
- Updated dependencies [565db3c5a]
- Updated dependencies [3fa3a4ef3]
- blitz@2.2.0
- @blitzjs/rpc@2.2.0
## 2.1.4
### Patch Changes
- ce23d4ed0: fix: Update `turbopack-empty.js` syntax to support latest turbopack and next.js versions
- blitz@2.1.4
- @blitzjs/rpc@2.1.4
## 2.1.3
### Patch Changes
- 0b3286468: chore: bump `next.js` and `zod` versions
- Updated dependencies [0b3286468]
- Updated dependencies [50f17d21c]
- @blitzjs/rpc@2.1.3
- blitz@2.1.3
## 2.1.2
### Patch Changes
- blitz@2.1.2
- @blitzjs/rpc@2.1.2
## 2.1.1
### Patch Changes
- Updated dependencies [9a0ba87d1]
- @blitzjs/rpc@2.1.1
- blitz@2.1.1
## 2.1.0
### Minor Changes
- 3b10b13e6: feat: add blitz auth support for the Web `Request` API standard
Usage using the new `withBlitzAuth` adapter in the App Router:
```ts
import {withBlitzAuth} from "app/blitz-server"
export const {POST} = withBlitzAuth({
POST: async (_request, _params, ctx) => {
const session = ctx.session
await session.$revoke()
return new Response(
JSON.stringify({
userId: session.userId,
}),
{status: 200},
)
},
})
```
feat: New Blitz RPC handler meant to with the next.js app router `route.ts` files
Usage using the new `rpcAppHandler` function
```ts
// app/api/rpc/[[...blitz]]/route.ts
import {rpcAppHandler} from "@blitzjs/rpc"
import {withBlitzAuth} from "app/blitz-server"
// Usage with blitz auth
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())
// Standalone usage
export const {GET, POST, HEAD} = rpcAppHandler()
```
chore: Update the app directory starter
### Patch Changes
- Updated dependencies [d53da39cb]
- Updated dependencies [3b10b13e6]
- blitz@2.1.0
- @blitzjs/rpc@2.1.0
## 2.0.10 ## 2.0.10
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@blitzjs/next", "name": "@blitzjs/next",
"version": "2.0.10", "version": "2.2.0",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -29,7 +29,7 @@
"eslint.js" "eslint.js"
], ],
"dependencies": { "dependencies": {
"@blitzjs/rpc": "2.0.10", "@blitzjs/rpc": "2.2.0",
"@types/hoist-non-react-statics": "3.3.1", "@types/hoist-non-react-statics": "3.3.1",
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "11.0.0",
"debug": "4.3.3", "debug": "4.3.3",
@@ -39,30 +39,29 @@
"supports-color": "8.1.1" "supports-color": "8.1.1"
}, },
"peerDependencies": { "peerDependencies": {
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "*", "next": "*",
"react": "*", "react": "*",
"tslog": "4.9.0" "tslog": "4.9.0"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@testing-library/dom": "8.13.0", "@testing-library/dom": "8.13.0",
"@testing-library/jest-dom": "5.16.3", "@testing-library/jest-dom": "5.16.3",
"@testing-library/react": "13.4.0", "@testing-library/react": "16.0.1",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "13.5.0", "@testing-library/user-event": "13.5.0",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "17.0.14", "@types/react-dom": "npm:types-react-dom@19.0.0",
"@types/testing-library__react-hooks": "4.0.0", "@types/testing-library__react-hooks": "4.0.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"cross-spawn": "7.0.3", "cross-spawn": "7.0.3",
"find-up": "4.1.0", "find-up": "4.1.0",
"next": "canary", "next": "15.0.1",
"next-router-mock": "0.9.1", "next-router-mock": "0.9.1",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"resolve-from": "5.0.0", "resolve-from": "5.0.0",
"ts-jest": "27.1.4", "ts-jest": "27.1.4",
"tslog": "4.9.0", "tslog": "4.9.0",

View File

@@ -1 +1,8 @@
export {} const exports = {
"npm-which": {},
"cross-spawn": {},
fs: {},
child_process: {},
}
module.exports = exports

View File

@@ -70,7 +70,7 @@ test("handleError forwards along async errors", async () => {
// //
// React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary." // React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary."
// `) // `)
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// can recover // can recover
@@ -116,7 +116,7 @@ test("can pass an error to useErrorHandler", async () => {
// //
// React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary." // React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary."
// `) // `)
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// can recover // can recover

View File

@@ -77,25 +77,10 @@ test("standard use-case", () => {
const {unmount} = render(<App />) const {unmount} = render(<App />)
userEvent.type(screen.getByRole("textbox", {name: /username/i}), "fail") userEvent.type(screen.getByRole("textbox", {name: /username/i}), "fail")
const calls = consoleError.mock.calls[0]
const [[actualError], [componentStack]] = consoleError.mock.calls //@ts-expect-error - it's a mock
expect(firstLine(actualError as string)).toMatchInlineSnapshot( expect(calls[1]).toMatchInlineSnapshot("[Error: 💥 CABOOM 💥]")
`"Error: Uncaught [Error: 💥 CABOOM 💥]"`, expect(consoleError).toHaveBeenCalledTimes(1)
)
expect(cleanStack(componentStack)).toMatchInlineSnapshot(`
"Error: Uncaught [Error: 💥 CABOOM 💥]
at reportException
at innerInvokeEventListeners
at invokeEventListeners
at HTMLUnknownElementImpl._dispatch
at HTMLUnknownElementImpl.dispatchEvent
at HTMLUnknownElement.dispatchEvent
at Object.invokeGuardedCallbackDev
at invokeGuardedCallback
at beginWork\$1
at performUnitOfWork "
`)
expect(consoleError).toHaveBeenCalledTimes(3)
consoleError.mockClear() consoleError.mockClear()
expect(screen.getByRole("alert")).toMatchInlineSnapshot(` expect(screen.getByRole("alert")).toMatchInlineSnapshot(`
@@ -149,7 +134,7 @@ test("fallbackRender prop", () => {
} }
const {unmount} = render(<App />) const {unmount} = render(<App />)
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// the render prop API allows a single action to reset the app state // the render prop API allows a single action to reset the app state
@@ -168,7 +153,7 @@ test("simple fallback is supported", () => {
<span>child</span> <span>child</span>
</ErrorBoundary>, </ErrorBoundary>,
) )
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
expect(screen.getByText(/oh no/i)).to.exist expect(screen.getByText(/oh no/i)).to.exist
expect(screen.queryByText(/child/i)).to.not.exist expect(screen.queryByText(/child/i)).to.not.exist
@@ -183,27 +168,16 @@ test("withErrorBoundary HOC", () => {
() => { () => {
throw new Error("💥 CABOOM 💥") throw new Error("💥 CABOOM 💥")
}, },
{FallbackComponent: ErrorFallback, onError: onErrorHandler}, {
FallbackComponent: ErrorFallback,
onError: onErrorHandler,
},
) )
const {unmount} = render(<Boundary />) const {unmount} = render(<Boundary />)
const calls = consoleError.mock.calls[0]
const [[actualError], [componentStack]] = consoleError.mock.calls //@ts-expect-error - it's a mock
const firstLineOfError = firstLine(actualError as string) expect(calls[1]).toMatchInlineSnapshot("[Error: 💥 CABOOM 💥]")
expect(firstLineOfError).toMatchInlineSnapshot(`"Error: Uncaught [Error: 💥 CABOOM 💥]"`) expect(consoleError).toHaveBeenCalledTimes(1)
expect(cleanStack(componentStack)).toMatchInlineSnapshot(`
"Error: Uncaught [Error: 💥 CABOOM 💥]
at reportException
at innerInvokeEventListeners
at invokeEventListeners
at HTMLUnknownElementImpl._dispatch
at HTMLUnknownElementImpl.dispatchEvent
at HTMLUnknownElement.dispatchEvent
at Object.invokeGuardedCallbackDev
at invokeGuardedCallback
at beginWork\$1
at performUnitOfWork "
`)
expect(consoleError).toHaveBeenCalledTimes(3)
consoleError.mockClear() consoleError.mockClear()
const [error, onErrorComponentStack] = (onErrorHandler.mock.calls as [[Error, string]])[0] const [error, onErrorComponentStack] = (onErrorHandler.mock.calls as [[Error, string]])[0]
@@ -265,7 +239,6 @@ test("requires either a fallback, fallbackRender, or FallbackComponent", () => {
let unmount: undefined | (() => void) let unmount: undefined | (() => void)
expect(() => { expect(() => {
const result = render( const result = render(
// @ts-expect-error we're testing the runtime check of missing props here
<ErrorBoundary> <ErrorBoundary>
<Bomb /> <Bomb />
</ErrorBoundary>, </ErrorBoundary>,
@@ -318,7 +291,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
// blow it up // blow it up
userEvent.click(screen.getByText("toggle explode")) userEvent.click(screen.getByText("toggle explode"))
expect(screen.getByRole("alert")).to.exist expect(screen.getByRole("alert")).to.exist
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// recover via try again button // recover via try again button
@@ -333,7 +306,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
// blow it up again // blow it up again
userEvent.click(screen.getByText("toggle explode")) userEvent.click(screen.getByText("toggle explode"))
expect(screen.getByRole("alert")).to.exist expect(screen.getByRole("alert")).to.exist
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// recover via resetKeys change // recover via resetKeys change
@@ -348,7 +321,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
// blow it up again // blow it up again
userEvent.click(screen.getByText("toggle explode")) userEvent.click(screen.getByText("toggle explode"))
expect(screen.getByRole("alert")).to.exist expect(screen.getByRole("alert")).to.exist
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// toggles adding an extra resetKey to the array // toggles adding an extra resetKey to the array
@@ -358,7 +331,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
expect(handleResetKeysChange).toHaveBeenCalledWith([true], [true, true]) expect(handleResetKeysChange).toHaveBeenCalledWith([true], [true, true])
handleResetKeysChange.mockClear() handleResetKeysChange.mockClear()
expect(screen.getByRole("alert")).to.exist expect(screen.getByRole("alert")).to.exist
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// toggle explode back to false // toggle explode back to false
@@ -369,7 +342,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
expect(handleResetKeysChange).toHaveBeenCalledWith([true, true], [false, true]) expect(handleResetKeysChange).toHaveBeenCalledWith([true, true], [false, true])
expect(screen.getByRole("alert")).to.exist expect(screen.getByRole("alert")).to.exist
handleResetKeysChange.mockClear() handleResetKeysChange.mockClear()
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// toggle extra resetKey // toggle extra resetKey
@@ -411,7 +384,7 @@ test("supports reset via resetKeys right after error is triggered on component m
// it blows up on render // it blows up on render
expect(screen.queryByRole("alert", {})).to.exist expect(screen.queryByRole("alert", {})).to.exist
expect(consoleError).toHaveBeenCalledTimes(3) expect(consoleError).toHaveBeenCalledTimes(1)
consoleError.mockClear() consoleError.mockClear()
// recover via "toggle explode" button // recover via "toggle explode" button

View File

@@ -1,7 +1,7 @@
import {RedirectError} from "blitz" import {RedirectError} from "blitz"
import {useRouter} from "next/compat/router" import {useRouter} from "next/compat/router"
import type {NextRouter} from "next/router" import type {NextRouter} from "next/router"
import * as React from "react" import React from "react"
import {RouterContext} from "./router-context" import {RouterContext} from "./router-context"
import _debug from "debug" import _debug from "debug"
import type {ExcludeRouterProps, WithRouterProps} from "next/dist/client/with-router" import type {ExcludeRouterProps, WithRouterProps} from "next/dist/client/with-router"
@@ -77,7 +77,7 @@ const initialState: ErrorBoundaryState = {error: null}
function withRouter<P extends WithRouterProps>( function withRouter<P extends WithRouterProps>(
ComposedComponent: React.ComponentType<P>, ComposedComponent: React.ComponentType<P>,
): React.ComponentType<ExcludeRouterProps<P>> { ): React.ComponentType<ExcludeRouterProps<P>> {
function WithRouterWrapper(props: any): JSX.Element { function WithRouterWrapper(props: any): React.JSX.Element {
return <ComposedComponent router={useRouter()} {...props} /> return <ComposedComponent router={useRouter()} {...props} />
} }
@@ -114,7 +114,13 @@ export const ErrorBoundary = withRouter(
await this.props.router.push(error.url) await this.props.router.push(error.url)
return return
} }
this.props.onError?.(error, info) if (this.props.onError) {
let componentStack = info.componentStack
if (!componentStack) {
componentStack = new Error("Stack trace").stack || ""
}
this.props.onError(error, {componentStack})
}
} }
componentDidMount() { componentDidMount() {
@@ -190,7 +196,7 @@ export const ErrorBoundary = withRouter(
}, },
) )
function withErrorBoundary<P extends JSX.IntrinsicAttributes>( function withErrorBoundary<P extends React.JSX.IntrinsicAttributes>(
Component: React.ComponentType<P>, Component: React.ComponentType<P>,
errorBoundaryProps: ErrorBoundaryProps, errorBoundaryProps: ErrorBoundaryProps,
): React.ComponentType<P> { ): React.ComponentType<P> {

View File

@@ -1,10 +1,16 @@
import React from "react" import React from "react"
import {NextPageContext} from "next" import {NextPageContext} from "next"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
const Head = dynamic(() => import("next/head").then((mod) => mod.default), { const Head = dynamic(
() =>
import("next/head").then((mod) => ({
default: mod.default,
})),
{
ssr: false, ssr: false,
loading: () => null, loading: () => null,
}) },
)
const statusCodes: {[code: number]: string} = { const statusCodes: {[code: number]: string} = {
400: "Bad Request", 400: "Bad Request",

View File

@@ -9,10 +9,16 @@ import type {Router} from "next/router"
import {BlitzProvider} from "./provider" import {BlitzProvider} from "./provider"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
export {Routes} from ".blitz" export {Routes} from ".blitz"
const Head = dynamic(() => import("next/head").then((mod) => mod.default), { const Head = dynamic(
() =>
import("next/head").then((mod) => ({
default: mod.default,
})),
{
ssr: false, ssr: false,
loading: () => null, loading: () => null,
}) },
)
export {BlitzProvider} from "./provider" export {BlitzProvider} from "./provider"
@@ -55,7 +61,7 @@ type RedirectAuthenticatedToFnCtx = {
} }
type RedirectAuthenticatedToFn = (args: RedirectAuthenticatedToFnCtx) => RedirectAuthenticatedTo type RedirectAuthenticatedToFn = (args: RedirectAuthenticatedToFnCtx) => RedirectAuthenticatedTo
export type BlitzPage<P = {}> = React.ComponentType<P> & { export type BlitzPage<P = {}> = React.ComponentType<P> & {
getLayout?: (component: JSX.Element) => JSX.Element getLayout?: (component: React.JSX.Element) => React.JSX.Element
authenticate?: boolean | {redirectTo?: string | RouteUrlObject; role?: string | Array<string>} authenticate?: boolean | {redirectTo?: string | RouteUrlObject; role?: string | Array<string>}
suppressFirstRenderFlicker?: boolean suppressFirstRenderFlicker?: boolean
redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn

View File

@@ -243,6 +243,7 @@ export interface BlitzConfig extends NextConfig {
} }
} }
export function withBlitz(nextConfig: BlitzConfig = {}): NextConfig { export function withBlitz(nextConfig: BlitzConfig = {}): NextConfig {
if ( if (
process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "production" &&

View File

@@ -3,7 +3,7 @@ import type {QueryClient, HydrateOptions} from "@blitzjs/rpc"
import React from "react" import React from "react"
export type BlitzProviderProps = { export type BlitzProviderProps = {
children: JSX.Element children: React.JSX.Element
client?: QueryClient client?: QueryClient
contextSharing?: boolean contextSharing?: boolean
dehydratedState?: unknown dehydratedState?: unknown

View File

@@ -4,7 +4,7 @@
import React from "react" import React from "react"
import {describe, it, expect, vi, afterEach} from "vitest" import {describe, it, expect, vi, afterEach} from "vitest"
import {extractRouterParams, useParam, useParams} from "./use-params" import {extractRouterParams, useParam, useParams} from "./use-params"
import {renderHook as defaultRenderHook} from "@testing-library/react-hooks" import {renderHook as defaultRenderHook} from "@testing-library/react"
import {NextRouter} from "next/router" import {NextRouter} from "next/router"
import {RouterContext} from "./router-context" import {RouterContext} from "./router-context"

View File

@@ -1,5 +1,96 @@
# @blitzjs/rpc # @blitzjs/rpc
## 2.2.0
### Minor Changes
- 3fa3a4ef3: chore: support next.js 15
### Patch Changes
- Updated dependencies [565db3c5a]
- Updated dependencies [3fa3a4ef3]
- blitz@2.2.0
## 2.1.4
### Patch Changes
- blitz@2.1.4
## 2.1.3
### Patch Changes
- 0b3286468: chore: bump `next.js` and `zod` versions
- Updated dependencies [0b3286468]
- Updated dependencies [50f17d21c]
- blitz@2.1.3
## 2.1.2
### Patch Changes
- blitz@2.1.2
## 2.1.1
### Patch Changes
- 9a0ba87d1: fix: make sure blitz superjson custom error registers in rpc handler
- Updated dependencies [9a0ba87d1]
- blitz@2.1.1
## 2.1.0
### Minor Changes
- 3b10b13e6: feat: add blitz auth support for the Web `Request` API standard
Usage using the new `withBlitzAuth` adapter in the App Router:
```ts
import {withBlitzAuth} from "app/blitz-server"
export const {POST} = withBlitzAuth({
POST: async (_request, _params, ctx) => {
const session = ctx.session
await session.$revoke()
return new Response(
JSON.stringify({
userId: session.userId,
}),
{status: 200},
)
},
})
```
feat: New Blitz RPC handler meant to with the next.js app router `route.ts` files
Usage using the new `rpcAppHandler` function
```ts
// app/api/rpc/[[...blitz]]/route.ts
import {rpcAppHandler} from "@blitzjs/rpc"
import {withBlitzAuth} from "app/blitz-server"
// Usage with blitz auth
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())
// Standalone usage
export const {GET, POST, HEAD} = rpcAppHandler()
```
chore: Update the app directory starter
### Patch Changes
- Updated dependencies [d53da39cb]
- Updated dependencies [3b10b13e6]
- blitz@2.1.0
## 2.0.10 ## 2.0.10
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@blitzjs/rpc", "name": "@blitzjs/rpc",
"version": "2.0.10", "version": "2.2.0",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -37,25 +37,25 @@
}, },
"peerDependencies": { "peerDependencies": {
"@tanstack/query-core": "4.24.4", "@tanstack/query-core": "4.24.4",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "*", "next": "*",
"react": "*" "react": "*"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/auth": "2.0.10", "@blitzjs/auth": "2.2.0",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@tanstack/query-core": "4.24.4", "@tanstack/query-core": "4.24.4",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "17.0.14", "@types/react-dom": "npm:types-react-dom@19.0.0",
"blitz": "2.0.10", "blitz": "2.2.0",
"next": "canary", "next": "15.0.1",
"react": "18.2.0", "react": "19.0.0",
"react-dom": "18.2.0", "react-dom": "19.0.0",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"unbuild": "0.7.6", "unbuild": "0.7.6",
"watch": "1.0.2", "watch": "1.0.2",
"zod": "3.20.2" "zod": "3.23.8"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@@ -1,4 +1,4 @@
import {assert, Ctx, ResolverConfig} from "blitz" import {assert, Ctx, ResolverConfig, registerBlitzErrorClasses} from "blitz"
import {NextApiRequest, NextApiResponse} from "next" import {NextApiRequest, NextApiResponse} from "next"
import {resolve} from "path" import {resolve} from "path"
import {deserialize, parse, serialize as superjsonSerialize} from "superjson" import {deserialize, parse, serialize as superjsonSerialize} from "superjson"
@@ -172,6 +172,12 @@ export function installTurboConfig() {
as: "*.ts", as: "*.ts",
}, },
}, },
"**/*...blitz*/route.{jsx,tsx,js,ts}": {
default: {
loaders: [{loader: loaderServer, options: {}}],
as: "*.ts",
},
},
"**/{queries,mutations}/**": { "**/{queries,mutations}/**": {
browser: { browser: {
loaders: [ loaders: [
@@ -215,7 +221,7 @@ async function getResolverMap(): Promise<ResolverFiles | null | undefined> {
// Handles: // Handles:
// - Vite // - Vite
// { // {
// const {resolverFilesLoaded, viteProvider} = await loadTelefuncFilesWithVite(runContext) // const {resolverFilesLoaded, viteProvider} = await loadTelefuncFilesWithVite(run
// if (resolverFilesLoaded) { // if (resolverFilesLoaded) {
// assertUsage( // assertUsage(
// Object.keys(resolverFilesLoaded).length > 0, // Object.keys(resolverFilesLoaded).length > 0,
@@ -227,12 +233,13 @@ async function getResolverMap(): Promise<ResolverFiles | null | undefined> {
} }
interface RpcConfig { interface RpcConfig {
onError?: (error: Error, ctx: Ctx) => void onError?: (error: Error, ctx?: Ctx) => void
formatError?: (error: Error, ctx: Ctx) => Error formatError?: (error: Error, ctx?: Ctx) => Error
logging?: RpcLoggerOptions logging?: RpcLoggerOptions
} }
export function rpcHandler(config: RpcConfig) { export function rpcHandler(config?: RpcConfig) {
registerBlitzErrorClasses()
return async function handleRpcRequest(req: NextApiRequest, res: NextApiResponse, ctx: Ctx) { return async function handleRpcRequest(req: NextApiRequest, res: NextApiResponse, ctx: Ctx) {
const resolverMap = await getResolverMap() const resolverMap = await getResolverMap()
assert(resolverMap, "No query or mutation resolvers found") assert(resolverMap, "No query or mutation resolvers found")
@@ -244,7 +251,7 @@ export function rpcHandler(config: RpcConfig) {
const relativeRoutePath = (req.query.blitz as string[])?.join("/") const relativeRoutePath = (req.query.blitz as string[])?.join("/")
const routePath = "/" + relativeRoutePath const routePath = "/" + relativeRoutePath
const resolverName = routePath.replace(/(\/api\/rpc)?\//, "") const resolverName = routePath.replace(/(\/api\/rpc)?\//, "")
const rpcLogger = new RpcLogger(resolverName, config.logging) const rpcLogger = new RpcLogger(resolverName, config?.logging)
const loadableResolver = resolverMap?.[routePath]?.resolver const loadableResolver = resolverMap?.[routePath]?.resolver
if (!loadableResolver) { if (!loadableResolver) {
@@ -303,6 +310,7 @@ export function rpcHandler(config: RpcConfig) {
rpcLogger.timer.initNextJsSerialization() rpcLogger.timer.initNextJsSerialization()
;(res as any).blitzResult = result ;(res as any).blitzResult = result
ctx?.session?.setSession(res)
res.json({ res.json({
result: serializedResult.json, result: serializedResult.json,
error: null, error: null,
@@ -322,16 +330,18 @@ export function rpcHandler(config: RpcConfig) {
error.stack = "" error.stack = ""
} }
config.onError?.(error, ctx) config?.onError?.(error, ctx)
rpcLogger.error(error) rpcLogger.error(error)
if (!error.statusCode) { if (!error.statusCode) {
error.statusCode = 500 error.statusCode = 500
} }
const formattedError = config.formatError?.(error, ctx) ?? error const formattedError = config?.formatError?.(error, ctx) ?? error
const serializedError = superjsonSerialize(formattedError) const serializedError = superjsonSerialize(formattedError)
ctx?.session?.setSession(res)
res.json({ res.json({
result: null, result: null,
error: serializedError.json, error: serializedError.json,
@@ -349,3 +359,130 @@ export function rpcHandler(config: RpcConfig) {
} }
} }
} }
type Params = Record<string, unknown>
export function rpcAppHandler(config?: RpcConfig) {
registerBlitzErrorClasses()
async function handleRpcRequest(req: Request, segmentData: {params: Promise<Params>}, ctx?: Ctx) {
const params = await segmentData.params
const session = ctx?.session
const resolverMap = await getResolverMap()
assert(resolverMap, "No query or mutation resolvers found")
assert(
Array.isArray(params.blitz),
"It seems your Blitz RPC endpoint file is not named [[...blitz]].(jt)s. Please ensure it is",
)
const relativeRoutePath = (params.blitz as string[])?.join("/")
const routePath = "/" + relativeRoutePath
const resolverName = routePath.replace(/(\/api\/rpc)?\//, "")
const rpcLogger = new RpcLogger(resolverName, config?.logging)
const loadableResolver = resolverMap?.[routePath]?.resolver
if (!loadableResolver) {
throw new Error("No resolver for path: " + routePath)
}
const {default: resolver, config: resolverConfig} = await loadableResolver()
if (!resolver) {
throw new Error("No default export for resolver path: " + routePath)
}
const resolverConfigWithDefaults = {...defaultConfig, ...resolverConfig}
if (req.method === "HEAD") {
// We used to initiate database connection here
return new Response(null, {status: 200})
}
if (
req.method === "POST" ||
(req.method === "GET" && resolverConfigWithDefaults.httpMethod === "GET")
) {
const body = await req.json()
if (req.method === "POST" && typeof body.params === "undefined") {
const error = {message: "Request body is missing the `params` key"}
rpcLogger.error(error.message)
return new Response(JSON.stringify({result: null, error}), {status: 400})
}
try {
const data = deserialize({
json:
req.method === "POST"
? body.params
: params.params
? parse(`${params.params}`)
: undefined,
meta:
req.method === "POST"
? body.meta?.params
: params.meta
? parse(`${params.meta}`)
: undefined,
})
rpcLogger.timer.initResolver()
rpcLogger.preResolver(data)
const result = await resolver(data, {session})
rpcLogger.timer.resolverDuration()
rpcLogger.postResolver(result)
rpcLogger.timer.initSerialization()
const serializedResult = superjsonSerialize(result)
rpcLogger.timer.initNextJsSerialization()
const response = new Response(
JSON.stringify({
result: serializedResult.json,
error: null,
meta: {
result: serializedResult.meta,
},
}),
)
session?.setSession(response)
return response
} catch (error: any) {
if (error._clearStack) {
error.stack = ""
}
config?.onError?.(error, {session} as Ctx)
rpcLogger.error(error)
if (!error.statusCode) {
error.statusCode = 500
}
const formattedError = config?.formatError?.(error, {session} as Ctx) ?? error
const serializedError = superjsonSerialize(formattedError)
const response = new Response(
JSON.stringify({
result: null,
error: serializedError.json,
meta: {
error: serializedError.meta,
},
}),
)
session?.setSession(response)
return response
}
} else {
// Everything else is error
rpcLogger.warn(`${req.method} method not supported`)
return new Response(null, {status: 404})
}
}
return {
GET: handleRpcRequest,
POST: handleRpcRequest,
HEAD: handleRpcRequest,
}
}

View File

@@ -1,7 +1,7 @@
import {QueryClientProvider} from "@tanstack/react-query" import {QueryClientProvider} from "@tanstack/react-query"
import React from "react" import React from "react"
export type BlitzProviderType = ({children}: {children: React.ReactNode}) => JSX.Element export type BlitzProviderType = ({children}: {children: React.ReactNode}) => React.JSX.Element
const BlitzProvider: BlitzProviderType = ({children}) => { const BlitzProvider: BlitzProviderType = ({children}) => {
const [queryClient] = React.useState(globalThis.queryClient) const [queryClient] = React.useState(globalThis.queryClient)

View File

@@ -1,5 +1,97 @@
# blitz # blitz
## 2.2.0
### Minor Changes
- 3fa3a4ef3: chore: support next.js 15
### Patch Changes
- 565db3c5a: Fixed incorrect terminal message after creating a new Blitz project to use the correct command (`npm run dev` or `yarn dev`).
- @blitzjs/generator@2.2.0
## 2.1.4
### Patch Changes
- Updated dependencies [38d320fd2]
- @blitzjs/generator@2.1.4
## 2.1.3
### Patch Changes
- 0b3286468: chore: bump `next.js` and `zod` versions
- 50f17d21c: Update prisma-ast dependency to prevent Blitz generator from failing when Prisma keywords are used as model names
- Updated dependencies [0b3286468]
- @blitzjs/generator@2.1.3
## 2.1.2
### Patch Changes
- Updated dependencies [2711291e9]
- Updated dependencies [56bd32b55]
- @blitzjs/generator@2.1.2
## 2.1.1
### Patch Changes
- 9a0ba87d1: fix: make sure blitz superjson custom error registers in rpc handler
- @blitzjs/generator@2.1.1
## 2.1.0
### Minor Changes
- 3b10b13e6: feat: add blitz auth support for the Web `Request` API standard
Usage using the new `withBlitzAuth` adapter in the App Router:
```ts
import {withBlitzAuth} from "app/blitz-server"
export const {POST} = withBlitzAuth({
POST: async (_request, _params, ctx) => {
const session = ctx.session
await session.$revoke()
return new Response(
JSON.stringify({
userId: session.userId,
}),
{status: 200},
)
},
})
```
feat: New Blitz RPC handler meant to with the next.js app router `route.ts` files
Usage using the new `rpcAppHandler` function
```ts
// app/api/rpc/[[...blitz]]/route.ts
import {rpcAppHandler} from "@blitzjs/rpc"
import {withBlitzAuth} from "app/blitz-server"
// Usage with blitz auth
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())
// Standalone usage
export const {GET, POST, HEAD} = rpcAppHandler()
```
chore: Update the app directory starter
### Patch Changes
- d53da39cb: Improved parsing of default export names to handle higher-order components (HOCs) in the `parseDefaultExportName` function.
- Updated dependencies [3b10b13e6]
- @blitzjs/generator@2.1.0
## 2.0.10 ## 2.0.10
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "blitz", "name": "blitz",
"version": "2.0.10", "version": "2.2.0",
"homepage": "https://blitzjs.com/", "homepage": "https://blitzjs.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -30,8 +30,8 @@
"blitz": "bin/blitz" "blitz": "bin/blitz"
}, },
"dependencies": { "dependencies": {
"@blitzjs/generator": "2.0.10", "@blitzjs/generator": "2.2.0",
"@mrleebo/prisma-ast": "0.2.6", "@mrleebo/prisma-ast": "0.4.1",
"@types/global-agent": "2.1.1", "@types/global-agent": "2.1.1",
"arg": "5.0.1", "arg": "5.0.1",
"ast-types": "0.14.2", "ast-types": "0.14.2",
@@ -80,7 +80,7 @@
"watchpack": "2.1.1" "watchpack": "2.1.1"
}, },
"devDependencies": { "devDependencies": {
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@types/cookie": "0.4.1", "@types/cookie": "0.4.1",
"@types/cross-spawn": "6.0.2", "@types/cross-spawn": "6.0.2",
"@types/debug": "4.1.7", "@types/debug": "4.1.7",
@@ -95,17 +95,17 @@
"@types/npm-which": "3.0.1", "@types/npm-which": "3.0.1",
"@types/progress": "2.0.5", "@types/progress": "2.0.5",
"@types/prompts": "2.0.14", "@types/prompts": "2.0.14",
"@types/react": "18.0.25", "@types/react": "npm:types-react@19.0.0",
"@types/react-dom": "17.0.14", "@types/react-dom": "npm:types-react-dom@19.0.0",
"@types/test-listen": "1.1.0", "@types/test-listen": "1.1.0",
"@types/watchpack": "1.1.1", "@types/watchpack": "1.1.1",
"express": "4.17.3", "express": "4.17.3",
"react": "18.2.0", "react": "19.0.0",
"test-listen": "1.1.0", "test-listen": "1.1.0",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"unbuild": "0.7.6", "unbuild": "0.7.6",
"watch": "1.0.2", "watch": "1.0.2",
"zod": "3.20.2" "zod": "3.23.8"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@@ -3,7 +3,8 @@ import {CliCommand} from "../index"
import prompts from "prompts" import prompts from "prompts"
import {bootstrap} from "global-agent" import {bootstrap} from "global-agent"
import {baseLogger, log} from "../../logging" import {baseLogger, log} from "../../logging"
const debug = require("debug")("blitz:cli") import Debug from "debug"
const debug = Debug("blitz:cli")
import {join, resolve, dirname} from "path" import {join, resolve, dirname} from "path"
import {Stream} from "stream" import {Stream} from "stream"
import {promisify} from "util" import {promisify} from "util"

View File

@@ -274,7 +274,7 @@ const newApp: CliCommand = async () => {
) )
} }
postInstallSteps.push(`${projectPkgManger} blitz dev`) postInstallSteps.push(`${projectPkgManger} ${projectPkgManger === "npm" ? "run " : ""}dev`);
console.log("\n Your new Blitz app is ready! Next steps:") console.log("\n Your new Blitz app is ready! Next steps:")
postInstallSteps.forEach((step, index) => { postInstallSteps.forEach((step, index) => {

View File

@@ -4,7 +4,8 @@ import {readJSON} from "fs-extra"
import path from "path" import path from "path"
import pkgDir from "pkg-dir" import pkgDir from "pkg-dir"
import resolveCwd from "resolve-cwd" import resolveCwd from "resolve-cwd"
const debug = require("debug")("blitz:utils") import Debug from "debug"
const debug = Debug("blitz:utils")
export async function resolveBinAsync(pkg: string, executable = pkg) { export async function resolveBinAsync(pkg: string, executable = pkg) {
const packageDir = await pkgDir(resolveCwd(pkg)) const packageDir = await pkgDir(resolveCwd(pkg))

View File

@@ -5,7 +5,8 @@ import path from "path"
import * as REPL from "repl" import * as REPL from "repl"
import {REPLCommand, REPLServer} from "repl" import {REPLCommand, REPLServer} from "repl"
// eslint-disable-next-line @next/next/no-assign-module-variable // eslint-disable-next-line @next/next/no-assign-module-variable
const debug = require("debug")("blitz:repl") import Debug from "debug"
const debug = Debug("blitz:repl")
import ProgressBar from "progress" import ProgressBar from "progress"
import {log} from "../../logging" import {log} from "../../logging"

View File

@@ -7,7 +7,8 @@ import * as esbuild from "esbuild"
import pkgDir from "pkg-dir" import pkgDir from "pkg-dir"
import type {ServerConfig} from "./config" import type {ServerConfig} from "./config"
const debug = require("debug")("blitz:utils") import Debug from "debug"
const debug = Debug("blitz:utils")
export function getProjectRootSync() { export function getProjectRootSync() {
return process.cwd() return process.cwd()

View File

@@ -5,7 +5,8 @@ import {outputFile, readdir, readFile} from "fs-extra"
import Watchpack from "watchpack" import Watchpack from "watchpack"
import {findNodeModulesRoot} from "./find-node-modules" import {findNodeModulesRoot} from "./find-node-modules"
const debug = require("debug")("blitz") import Debug from "debug"
const debug = Debug("blitz")
export const CONFIG_FILE = ".blitz.config.compiled.js" export const CONFIG_FILE = ".blitz.config.compiled.js"
export const NEXT_CONFIG_FILE = "next.config.js" export const NEXT_CONFIG_FILE = "next.config.js"
export const PHASE_PRODUCTION_SERVER = "phase-production-server" export const PHASE_PRODUCTION_SERVER = "phase-production-server"
@@ -503,12 +504,13 @@ const pascalCase = (value: string): string => {
return val.substr(0, 1).toUpperCase() + val.substr(1) return val.substr(0, 1).toUpperCase() + val.substr(1)
} }
export function parseDefaultExportName(contents: string): string | null { export function parseDefaultExportName(contents: string): string | null {
const result = contents.match(/export\s+default(?:\s+(?:const|let|class|var|function))?\s+(\w+)/) const result = contents.match(/export\s+default(?:\s+(const|let|class|var|function))?\s+(\w+)(?:\(([a-zA-Z_$][a-zA-Z0-9_$]*\b).*\))?/)
if (!result) { if (!result) {
return null return null
} }
const [,declaration,compOrHOCName,comp] = result
return result[1] ?? null if(declaration||!comp) return compOrHOCName ?? null;
return comp ?? null
} }
export async function generateManifest() { export async function generateManifest() {
const config = await loadConfig(process.cwd()) const config = await loadConfig(process.cwd())

View File

@@ -89,7 +89,10 @@ export class PaginationArgumentError extends Error {
} }
} }
if (isNotInUserTestEnvironment() && !globalThis._BLITZ_ERROR_CLASS_REGISTERED) { let _blitzErrorClassRegistered = false
export function registerBlitzErrorClasses() {
if (_blitzErrorClassRegistered || !isNotInUserTestEnvironment()) return
SuperJson.registerClass(AuthenticationError, { SuperJson.registerClass(AuthenticationError, {
identifier: "BlitzAuthenticationError", identifier: "BlitzAuthenticationError",
allowProps: errorProps, allowProps: errorProps,
@@ -125,5 +128,7 @@ if (isNotInUserTestEnvironment() && !globalThis._BLITZ_ERROR_CLASS_REGISTERED) {
allowProps: errorProps, allowProps: errorProps,
}) })
globalThis._BLITZ_ERROR_CLASS_REGISTERED = true _blitzErrorClassRegistered = true
} }
registerBlitzErrorClasses()

View File

@@ -8,8 +8,10 @@ import {
PaginationArgumentError, PaginationArgumentError,
RedirectError, RedirectError,
OAuthError, OAuthError,
registerBlitzErrorClasses,
} from "./errors" } from "./errors"
import type {EventHooks, MiddlewareHooks} from "./types" import type {EventHooks, MiddlewareHooks} from "./types"
import React from "react"
export { export {
AuthenticationError, AuthenticationError,
AuthorizationError, AuthorizationError,
@@ -18,13 +20,14 @@ export {
PaginationArgumentError, PaginationArgumentError,
RedirectError, RedirectError,
OAuthError, OAuthError,
registerBlitzErrorClasses,
} }
export * from "./utils/enhance-prisma" export * from "./utils/enhance-prisma"
export type BlitzProviderComponentType = <TProps = any>( export type BlitzProviderComponentType = <TProps = any>(
component: ComponentType<TProps>, component: ComponentType<TProps>,
) => { ) => {
(props: TProps): JSX.Element (props: TProps): React.JSX.Element
displayName: string displayName: string
} }

View File

@@ -9,7 +9,7 @@ export * from "./utils/enhance-prisma"
export * from "./middleware" export * from "./middleware"
export * from "./paginate" export * from "./paginate"
export * from "./logging" export * from "./logging"
export {reduceBlitzServerPlugins} from "./plugin" export {reduceBlitzServerPlugins, merge, pipe} from "./plugin"
export {findNodeModulesRoot, findNodeModulesRootSync} from "./cli/utils/find-node-modules" export {findNodeModulesRoot, findNodeModulesRootSync} from "./cli/utils/find-node-modules"
export {startWatcher, stopWatcher} from "./cli/utils/routes-manifest" export {startWatcher, stopWatcher} from "./cli/utils/routes-manifest"

View File

@@ -99,8 +99,8 @@ const branded = (msg: string) => {
* @param {string} msg * @param {string} msg
*/ */
const clearLine = (msg?: string) => { const clearLine = (msg?: string) => {
readline.clearLine(process.stdout, 0) readline.clearLine(process.stdout as any, 0)
readline.cursorTo(process.stdout, 0) readline.cursorTo(process.stdout as any, 0)
msg && process.stdout.write(msg) msg && process.stdout.write(msg)
} }
@@ -173,7 +173,8 @@ const box = async (mes: string, title: string) => {
* If the DEBUG env var is set this will write to the console * If the DEBUG env var is set this will write to the console
* @param str msg * @param str msg
*/ */
const debug = require("debug")("blitz") import Debug from "debug"
const debug = Debug("blitz")
export const log = { export const log = {
withBrand, withBrand,

View File

@@ -1,5 +1,57 @@
# @blitzjs/codemod # @blitzjs/codemod
## 2.2.0
### Patch Changes
- Updated dependencies [565db3c5a]
- Updated dependencies [3fa3a4ef3]
- blitz@2.2.0
- @blitzjs/generator@2.2.0
## 2.1.4
### Patch Changes
- Updated dependencies [38d320fd2]
- @blitzjs/generator@2.1.4
- blitz@2.1.4
## 2.1.3
### Patch Changes
- Updated dependencies [0b3286468]
- Updated dependencies [50f17d21c]
- blitz@2.1.3
- @blitzjs/generator@2.1.3
## 2.1.2
### Patch Changes
- Updated dependencies [2711291e9]
- Updated dependencies [56bd32b55]
- @blitzjs/generator@2.1.2
- blitz@2.1.2
## 2.1.1
### Patch Changes
- Updated dependencies [9a0ba87d1]
- blitz@2.1.1
- @blitzjs/generator@2.1.1
## 2.1.0
### Patch Changes
- Updated dependencies [d53da39cb]
- Updated dependencies [3b10b13e6]
- blitz@2.1.0
- @blitzjs/generator@2.1.0
## 2.0.10 ## 2.0.10
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@blitzjs/codemod", "name": "@blitzjs/codemod",
"version": "2.0.10", "version": "2.2.0",
"scripts": { "scripts": {
"build": "unbuild", "build": "unbuild",
"dev": "watch unbuild src --wait=0.2", "dev": "watch unbuild src --wait=0.2",
@@ -25,9 +25,9 @@
"@babel/plugin-proposal-class-properties": "7.17.12", "@babel/plugin-proposal-class-properties": "7.17.12",
"@babel/plugin-syntax-jsx": "7.17.12", "@babel/plugin-syntax-jsx": "7.17.12",
"@babel/plugin-syntax-typescript": "7.17.12", "@babel/plugin-syntax-typescript": "7.17.12",
"@blitzjs/generator": "2.0.10", "@blitzjs/generator": "2.2.0",
"arg": "5.0.1", "arg": "5.0.1",
"blitz": "2.0.10", "blitz": "2.2.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"cross-spawn": "7.0.3", "cross-spawn": "7.0.3",
"debug": "4.3.3", "debug": "4.3.3",
@@ -38,7 +38,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-env": "7.12.10", "@babel/preset-env": "7.12.10",
"@blitzjs/config": "2.0.10", "@blitzjs/config": "2.2.0",
"@types/jscodeshift": "0.11.2", "@types/jscodeshift": "0.11.2",
"@types/node": "18.11.9", "@types/node": "18.11.9",
"ast-types": "0.14.2", "ast-types": "0.14.2",

View File

@@ -10,7 +10,7 @@ describe("replaceBlitzPkgsVersions", () => {
return { return {
fetchDistTags: vi.fn((pkg: string) => { fetchDistTags: vi.fn((pkg: string) => {
if (pkg === "blitz") { if (pkg === "blitz") {
return {alpha: "1.0.0", beta: "2.0.0", danger: "3.0.0"} return {alpha: "1.0.0", beta: "2.0.0", danger: "2.1.0"}
} }
if (pkg === "zod") { if (pkg === "zod") {
return {latest: "1.2.3"} return {latest: "1.2.3"}
@@ -52,10 +52,10 @@ describe("replaceBlitzPkgsVersions", () => {
it("correctly updates versions with the danger tag", async () => { it("correctly updates versions with the danger tag", async () => {
expect(await replaceBlitzPkgsVersions(pkgJson, "danger")).toEqual({ expect(await replaceBlitzPkgsVersions(pkgJson, "danger")).toEqual({
dependencies: { dependencies: {
blitz: "3.0.0", blitz: "2.1.0",
"@blitzjs/rpc": "3.0.0", "@blitzjs/rpc": "2.1.0",
"@blitzjs/auth": "3.0.0", "@blitzjs/auth": "2.1.0",
"@blitzjs/next": "3.0.0", "@blitzjs/next": "2.1.0",
next: "12.2.0", next: "12.2.0",
zod: "1.2.3", zod: "1.2.3",
}, },

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