Compare commits
65 Commits
@blitzjs/a
...
v2.2.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f5ca746c6 | ||
|
|
aa033af9f1 | ||
|
|
c7bb5864f3 | ||
|
|
d174c014f3 | ||
|
|
ab455acf0b | ||
|
|
bddc1d88f5 | ||
|
|
ce1a603b26 | ||
|
|
125370a1d0 | ||
|
|
39c8c0ab80 | ||
|
|
fbf5e51a78 | ||
|
|
9cda1be11b | ||
|
|
5b20ce6282 | ||
|
|
0fc2d71a18 | ||
|
|
3fa3a4ef30 | ||
|
|
565db3c5a8 | ||
|
|
ae04524b4c | ||
|
|
38d320fd28 | ||
|
|
8723d0053c | ||
|
|
ce23d4ed09 | ||
|
|
b6c9c4ae6d | ||
|
|
0b3286468b | ||
|
|
50f17d21ce | ||
|
|
57add5f1c5 | ||
|
|
3f239e78b6 | ||
|
|
3bf90c167c | ||
|
|
56bd32b553 | ||
|
|
2711291e97 | ||
|
|
ab29c5bf3f | ||
|
|
a096f2cd80 | ||
|
|
ded16b325b | ||
|
|
4494662d6d | ||
|
|
9a0ba87d15 | ||
|
|
c80ce51d36 | ||
|
|
b505933a16 | ||
|
|
d53da39cbf | ||
|
|
e1055f7366 | ||
|
|
3b10b13e6b | ||
|
|
25601754a4 | ||
|
|
e0cfa328ec | ||
|
|
f02469aac8 | ||
|
|
2f3c552ac3 | ||
|
|
721461a7b9 | ||
|
|
3193bdea48 | ||
|
|
318e9740d6 | ||
|
|
0df368308b | ||
|
|
ad94bee56a | ||
|
|
5a14306f7b | ||
|
|
6e8eeb1514 | ||
|
|
5e61a16815 | ||
|
|
77555468f3 | ||
|
|
22e402af01 | ||
|
|
19afc57530 | ||
|
|
b7dab1d800 | ||
|
|
ee7bf87ec0 | ||
|
|
451ead089c | ||
|
|
5d9570f7f4 | ||
|
|
178c152b2c | ||
|
|
bbe1a54d7a | ||
|
|
9b075dbb47 | ||
|
|
60a4d90c86 | ||
|
|
a674897601 | ||
|
|
5a587a6c31 | ||
|
|
744242f25b | ||
|
|
0f5297f1aa | ||
|
|
76a2544f9d |
@@ -4034,6 +4034,106 @@
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "lmisea",
|
||||
"name": "Luis Isea",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/106825636?v=4",
|
||||
"profile": "https://github.com/lmisea",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "gengjiawen",
|
||||
"name": "Jiawen Geng",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/3759816?v=4",
|
||||
"profile": "https://www.gengjiawen.com",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "timneutkens",
|
||||
"name": "Tim Neutkens",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/6324199?v=4",
|
||||
"profile": "https://timn.tech",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rene-demonsters",
|
||||
"name": "René Vlugt",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20322259?v=4",
|
||||
"profile": "https://github.com/rene-demonsters",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kksandr7",
|
||||
"name": "Ksandr",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/132560756?v=4",
|
||||
"profile": "https://www.drupal.org/u/kksandr",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
5
.changeset/stale-keys-guess.md
Normal file
5
.changeset/stale-keys-guess.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@blitzjs/auth": patch
|
||||
---
|
||||
|
||||
fix: Overriden custom cookies with pages router
|
||||
24
.github/workflows/main.yml
vendored
24
.github/workflows/main.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 8.6.5
|
||||
version: 8.6.6
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
@@ -38,13 +38,13 @@ jobs:
|
||||
name: Build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 8.6.5
|
||||
version: 8.6.6
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- name: Build
|
||||
@@ -68,14 +68,14 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 8.6.5
|
||||
version: 8.6.6
|
||||
|
||||
- name: Setup node@16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -129,15 +129,15 @@ jobs:
|
||||
|
||||
- name: Setup PNPM
|
||||
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:
|
||||
version: 8.6.5
|
||||
version: 8.6.6
|
||||
|
||||
- name: Setup node@18
|
||||
if: matrix.folder != 'next-13-app-dir' || matrix.os != 'windows-latest'
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
26
.github/workflows/pr-release.yml
vendored
26
.github/workflows/pr-release.yml
vendored
@@ -22,23 +22,29 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: resolve pr refs
|
||||
id: refs
|
||||
uses: eficode/resolve-pr-refs@main
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Fetch PR information
|
||||
id: pr_info
|
||||
env:
|
||||
GH_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@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.refs.outputs.head_ref }}
|
||||
ref: ${{ steps.pr_info.outputs.head_sha }}
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2.2.1
|
||||
uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
|
||||
with:
|
||||
version: 8.9.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
cache: "pnpm"
|
||||
|
||||
|
||||
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@@ -19,12 +19,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js 16.x
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setup Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.x
|
||||
node-version: 20.x
|
||||
|
||||
- name: Creating .npmrc
|
||||
run: |
|
||||
@@ -37,8 +37,10 @@ jobs:
|
||||
- name: Pre-publish
|
||||
uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
|
||||
with:
|
||||
version: 6.32.6
|
||||
- run: pnpm install --no-frozen-lockfile
|
||||
version: 8.9.0
|
||||
- run: pnpm install
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
- run: pnpm build
|
||||
|
||||
- name: Create Release Pull Request
|
||||
@@ -49,10 +51,3 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
# - name: Github Release
|
||||
# uses: release-drafter/release-drafter@v5.19.0
|
||||
# with:
|
||||
# config-name: release-drafter.yml
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
16.13.2
|
||||
19
README.md
19
README.md
@@ -6,7 +6,7 @@
|
||||
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
|
||||
</a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-425-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-435-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/main/LICENSE">
|
||||
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
|
||||
@@ -79,6 +79,9 @@ Your financial contributions help ensure Blitz continues to be developed and mai
|
||||
</a></td>
|
||||
<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"/>
|
||||
</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>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -749,6 +752,20 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://github.com/papsavas"><img src="https://avatars.githubusercontent.com/u/50584606?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Savvas Papageorgiadis</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=papsavas" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=papsavas" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://flow-office.eu/"><img src="https://avatars.githubusercontent.com/u/77194479?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leonidas</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=LeonMueller-OneAndOnly" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=LeonMueller-OneAndOnly" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://hacknex.us"><img src="https://avatars.githubusercontent.com/u/19937034?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Doc0x1</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Doc0x1" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Doc0x1" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/lmisea"><img src="https://avatars.githubusercontent.com/u/106825636?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Luis Isea</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lmisea" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=lmisea" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.gengjiawen.com"><img src="https://avatars.githubusercontent.com/u/3759816?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jiawen Geng</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=gengjiawen" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=gengjiawen" title="Code">💻</a></td>
|
||||
</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://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>
|
||||
<td align="center"><a href="https://github.com/rene-demonsters"><img src="https://avatars.githubusercontent.com/u/20322259?v=4?s=100" width="100px;" alt=""/><br /><sub><b>René Vlugt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rene-demonsters" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rene-demonsters" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.drupal.org/u/kksandr"><img src="https://avatars.githubusercontent.com/u/132560756?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ksandr</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kksandr7" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=kksandr7" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -1,5 +1,163 @@
|
||||
# next-blitz-auth
|
||||
|
||||
## 0.1.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [aa033af9f]
|
||||
- @blitzjs/auth@2.2.3
|
||||
- @blitzjs/rpc@2.2.3
|
||||
- @blitzjs/next@2.2.3
|
||||
- @blitzjs/config@2.2.3
|
||||
- blitz@2.2.3
|
||||
|
||||
## 0.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ab455acf0]
|
||||
- @blitzjs/auth@2.2.2
|
||||
- @blitzjs/rpc@2.2.2
|
||||
- @blitzjs/next@2.2.2
|
||||
- @blitzjs/config@2.2.2
|
||||
- blitz@2.2.2
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- blitz@2.2.1
|
||||
- @blitzjs/auth@2.2.1
|
||||
- @blitzjs/next@2.2.1
|
||||
- @blitzjs/rpc@2.2.1
|
||||
- @blitzjs/config@2.2.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [318e9740d]
|
||||
- @blitzjs/auth@2.0.10
|
||||
- blitz@2.0.10
|
||||
- @blitzjs/rpc@2.0.10
|
||||
- @blitzjs/next@2.0.10
|
||||
- @blitzjs/config@2.0.10
|
||||
|
||||
## 0.1.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5a14306f7]
|
||||
- @blitzjs/next@2.0.9
|
||||
- @blitzjs/rpc@2.0.9
|
||||
- blitz@2.0.9
|
||||
- @blitzjs/auth@2.0.9
|
||||
- @blitzjs/config@2.0.9
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5e61a1681]
|
||||
- Updated dependencies [77555468f]
|
||||
- @blitzjs/next@2.0.8
|
||||
- blitz@2.0.8
|
||||
- @blitzjs/auth@2.0.8
|
||||
- @blitzjs/rpc@2.0.8
|
||||
- @blitzjs/config@2.0.8
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ee7bf87ec]
|
||||
- Updated dependencies [178c152b2]
|
||||
- blitz@2.0.7
|
||||
- @blitzjs/next@2.0.7
|
||||
- @blitzjs/rpc@2.0.7
|
||||
- @blitzjs/auth@2.0.7
|
||||
- @blitzjs/config@2.0.7
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [76a2544f9]
|
||||
- Updated dependencies [5a587a6c3]
|
||||
- blitz@2.0.6
|
||||
- @blitzjs/auth@2.0.6
|
||||
- @blitzjs/next@2.0.6
|
||||
- @blitzjs/rpc@2.0.6
|
||||
- @blitzjs/config@2.0.6
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
const {withBlitz} = require("@blitzjs/next")
|
||||
|
||||
const loaderClient = require.resolve("@blitzjs/rpc/dist/loader-client.cjs")
|
||||
const loaderServer = require.resolve("@blitzjs/rpc/dist/loader-server.cjs")
|
||||
const loaderServerResolvers = require.resolve("@blitzjs/rpc/dist/loader-server-resolvers.cjs")
|
||||
|
||||
console.log("loaderClient", loaderClient)
|
||||
console.log("loaderServer", loaderServer)
|
||||
console.log("loaderServerResolvers", loaderServerResolvers)
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-blitz-auth",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.20",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"blitz:dev": "next dev",
|
||||
@@ -12,28 +12,28 @@
|
||||
"schema": "prisma/schema.prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@hookform/error-message": "2.0.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@prisma/client": "^4.5.0",
|
||||
"@tanstack/react-query": "4.0.10",
|
||||
"blitz": "2.0.5",
|
||||
"blitz": "2.2.3",
|
||||
"flatted": "3.2.7",
|
||||
"next": "14.0.4",
|
||||
"next": "15.0.1",
|
||||
"prisma": "^4.5.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "7.39.1",
|
||||
"superjson": "1.11.0",
|
||||
"zod": "3.20.2"
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.7",
|
||||
"@types/react": "18.0.23",
|
||||
"@types/react-dom": "18.0.7",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||
"eslint": "8.26.0",
|
||||
"eslint-config-next": "13.0.0",
|
||||
"typescript": "4.8.4"
|
||||
|
||||
Binary file not shown.
4
apps/next13/src/app/api/rpc/[[...blitz]]/route.ts
Normal file
4
apps/next13/src/app/api/rpc/[[...blitz]]/route.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import {rpcAppHandler} from "@blitzjs/rpc"
|
||||
import {withBlitzAuth} from "src/blitz-server"
|
||||
|
||||
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())
|
||||
@@ -6,26 +6,27 @@ import {simpleRolesIsAuthorized} from "@blitzjs/auth"
|
||||
import {BlitzLogger} from "blitz"
|
||||
import {RpcServerPlugin} from "@blitzjs/rpc"
|
||||
|
||||
const {api, getBlitzContext, useAuthenticatedBlitzContext, invoke} = setupBlitzServer({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "web-cookie-prefix",
|
||||
storage: PrismaStorage(db),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
RpcServerPlugin({
|
||||
logging: {
|
||||
disablelevel: "debug",
|
||||
},
|
||||
onInvokeError(error) {
|
||||
console.log("onInvokeError", error)
|
||||
},
|
||||
}),
|
||||
],
|
||||
logger: BlitzLogger({}),
|
||||
})
|
||||
const {api, getBlitzContext, useAuthenticatedBlitzContext, invoke, withBlitzAuth} =
|
||||
setupBlitzServer({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "web-cookie-prefix",
|
||||
storage: PrismaStorage(db),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
RpcServerPlugin({
|
||||
logging: {
|
||||
disablelevel: "debug",
|
||||
},
|
||||
onInvokeError(error) {
|
||||
console.log("onInvokeError", error)
|
||||
},
|
||||
}),
|
||||
],
|
||||
logger: BlitzLogger({}),
|
||||
})
|
||||
|
||||
export {api, getBlitzContext, useAuthenticatedBlitzContext, invoke}
|
||||
export {api, getBlitzContext, useAuthenticatedBlitzContext, invoke, withBlitzAuth}
|
||||
|
||||
export const cliConfig: BlitzCliConfig = {
|
||||
customTemplates: "src/templates",
|
||||
|
||||
@@ -4,7 +4,7 @@ import {zodResolver} from "@hookform/resolvers/zod"
|
||||
import {z} from "zod"
|
||||
|
||||
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 */
|
||||
children?: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
|
||||
@@ -2,14 +2,15 @@ import {forwardRef, PropsWithoutRef, ComponentPropsWithoutRef} from "react"
|
||||
import {useFormContext} from "react-hook-form"
|
||||
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. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
type?: "text" | "password" | "email" | "number"
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
|
||||
labelProps?: ComponentPropsWithoutRef<"label">
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import {rpcHandler} from "@blitzjs/rpc"
|
||||
import {api} from "../../../blitz-server"
|
||||
|
||||
export default api(rpcHandler({onError: (error, ctx) => console.log(error)}))
|
||||
@@ -12,5 +12,5 @@ export default async function getCurrentUser(input: null, ctx: Ctx) {
|
||||
}
|
||||
|
||||
export const config = {
|
||||
httpMethod: "GET",
|
||||
httpMethod: "POST",
|
||||
}
|
||||
|
||||
@@ -23,31 +23,30 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@hookform/error-message": "2.0.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"openid-client": "5.2.1",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "7.39.1",
|
||||
"ts-node": "10.9.1",
|
||||
"zod": "3.20.2"
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@testing-library/react": "16.0.1",
|
||||
"@types/jest": "29.2.2",
|
||||
"@types/node": "18.11.9",
|
||||
"@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",
|
||||
"eslint": "8.27.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { z } from "zod"
|
||||
|
||||
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 */
|
||||
children?: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
|
||||
@@ -2,14 +2,15 @@ import { forwardRef, PropsWithoutRef, ComponentPropsWithoutRef } from "react"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
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. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
type?: "text" | "password" | "email" | "number"
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
|
||||
labelProps?: ComponentPropsWithoutRef<"label">
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/toolkit-app/next-env.d.ts
vendored
2
apps/toolkit-app/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -24,31 +24,30 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@hookform/error-message": "2.0.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "4.18.7",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"next-auth": "4.24.7",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "7.39.1",
|
||||
"ts-node": "10.9.1",
|
||||
"zod": "3.20.2"
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@testing-library/react": "16.0.1",
|
||||
"@types/node": "18.11.9",
|
||||
"@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",
|
||||
"@vitejs/plugin-react": "2.2.0",
|
||||
"eslint": "8.27.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { z } from "zod"
|
||||
|
||||
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 */
|
||||
children?: ReactNode
|
||||
/** Text to display in the submit button */
|
||||
|
||||
@@ -2,14 +2,15 @@ import { ComponentPropsWithoutRef, forwardRef, PropsWithoutRef } from "react"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
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. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
options: any[]
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
|
||||
labelProps?: ComponentPropsWithoutRef<"label">
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@ import { forwardRef, PropsWithoutRef, ComponentPropsWithoutRef } from "react"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
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. */
|
||||
name: string
|
||||
/** Field label. */
|
||||
label: string
|
||||
/** Field type. Doesn't include radio buttons and checkboxes */
|
||||
type?: "text" | "password" | "email" | "number"
|
||||
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
|
||||
outerProps?: PropsWithoutRef<React.JSX.IntrinsicElements["div"]>
|
||||
labelProps?: ComponentPropsWithoutRef<"label">
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,11 @@ const LoginPage: BlitzPage = () => {
|
||||
<LoginForm
|
||||
onSuccess={(_user) => {
|
||||
const next = router.query.next ? decodeURIComponent(router.query.next as string) : "/"
|
||||
// return router.push(next)
|
||||
return router.push(next)
|
||||
}}
|
||||
/>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
LoginPage.authenticate = {
|
||||
redirectTo: "/",
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,27 +16,27 @@
|
||||
"schema": "./db/schema.prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"@types/jest": "29.2.2",
|
||||
"@types/passport-twitter": "1.0.37",
|
||||
"blitz": "2.0.5",
|
||||
"blitz": "2.2.3",
|
||||
"jest": "29.3.0",
|
||||
"jest-environment-jsdom": "29.3.0",
|
||||
"next": "14.0.4",
|
||||
"next": "15.0.1",
|
||||
"passport-mock-strategy": "2.0.0",
|
||||
"passport-twitter": "1.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"ts-node": "10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"eslint": "8.27.0",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
assets/route4me.png
Normal file
BIN
assets/route4me.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
@@ -17,31 +17,30 @@
|
||||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@hookform/error-message": "2.0.0",
|
||||
"@hookform/resolvers": "2.9.10",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"delay": "5.0.0",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "7.39.1",
|
||||
"ts-node": "10.9.1",
|
||||
"zod": "3.20.2"
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@testing-library/react": "16.0.1",
|
||||
"@types/node": "18.11.9",
|
||||
"@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",
|
||||
"@vitejs/plugin-react": "2.2.0",
|
||||
"eslint": "8.27.0",
|
||||
|
||||
@@ -79,11 +79,13 @@ const runTests = () => {
|
||||
let text = await browser.elementByCss("#content").text()
|
||||
expect(text).toMatch(/logged-out/)
|
||||
await browser.elementByCss("#login").click()
|
||||
await waitFor(200)
|
||||
await waitFor(7500)
|
||||
text = await browser.elementByCss("#content").text()
|
||||
|
||||
expect(text).toMatch(/logged-in/)
|
||||
|
||||
await browser.elementByCss("#logout").click()
|
||||
await waitFor(250)
|
||||
await waitFor(5000)
|
||||
text = await browser.elementByCss("#content").text()
|
||||
expect(text).toMatch(/logged-out/)
|
||||
|
||||
@@ -93,16 +95,16 @@ const runTests = () => {
|
||||
it("should logout without infinite loop #2233", async () => {
|
||||
// Login
|
||||
let browser = await webdriver(appPort, "/login")
|
||||
await waitFor(200)
|
||||
await waitFor(5000)
|
||||
await browser.elementByCss("#login").click()
|
||||
await waitFor(200)
|
||||
await waitFor(5000)
|
||||
|
||||
await browser.eval(`window.location = "/authenticated-query"`)
|
||||
await browser.waitForElementByCss("#content")
|
||||
let text = await browser.elementByCss("#content").text()
|
||||
expect(text).toMatch(/authenticated-basic-result/)
|
||||
await browser.elementByCss("#logout").click()
|
||||
await waitFor(200)
|
||||
await waitFor(5000)
|
||||
await browser.waitForElementByCss("#error")
|
||||
text = await browser.elementByCss("#error").text()
|
||||
if (mode === "server") {
|
||||
@@ -115,9 +117,9 @@ const runTests = () => {
|
||||
|
||||
it("Page.authenticate = {role} should throw authentication error ", async () => {
|
||||
let browser = await webdriver(appPort, "/login")
|
||||
await waitFor(200)
|
||||
await waitFor(5000)
|
||||
await browser.elementByCss("#login").click()
|
||||
await waitFor(200)
|
||||
await waitFor(5000)
|
||||
await browser.eval(`window.location = "/page-dot-authenticate-role"`)
|
||||
await browser.waitForElementByCss("#error")
|
||||
let text = await browser.elementByCss("#error").text()
|
||||
@@ -169,7 +171,7 @@ const runTests = () => {
|
||||
let text = await browser.elementByCss("#content").text()
|
||||
expect(text).toMatch(/authenticated-basic-result/)
|
||||
await browser.elementByCss("#logout").click()
|
||||
await waitFor(500)
|
||||
await waitFor(5000)
|
||||
|
||||
expect(await browser.url()).toMatch(/\/login/)
|
||||
if (browser) await browser.close()
|
||||
@@ -275,7 +277,7 @@ const runTests = () => {
|
||||
}
|
||||
|
||||
describe("Auth Tests", () => {
|
||||
describe("dev mode", () => {
|
||||
describe("dev mode - webpack", () => {
|
||||
beforeAll(async () => {
|
||||
mode = "dev"
|
||||
try {
|
||||
@@ -290,6 +292,21 @@ describe("Auth Tests", () => {
|
||||
runTests()
|
||||
})
|
||||
|
||||
describe("dev mode - turbo", () => {
|
||||
beforeAll(async () => {
|
||||
mode = "dev"
|
||||
try {
|
||||
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
|
||||
appPort = await findPort()
|
||||
app = await blitzLaunchApp(appPort, {cwd: process.cwd()}, true)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}, 5000 * 60 * 2)
|
||||
afterAll(async () => await killApp(app))
|
||||
runTests()
|
||||
})
|
||||
|
||||
describe("server mode", () => {
|
||||
beforeAll(async () => {
|
||||
mode = "server"
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"secure-password": "4.0.0",
|
||||
"wait-port": "1.0.4"
|
||||
},
|
||||
@@ -36,7 +36,7 @@
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node": "18.7.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
|
||||
@@ -21,14 +21,11 @@ export const authenticateUser = async (email: string, password: string) => {
|
||||
}
|
||||
|
||||
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({
|
||||
await ctx.session.$create({
|
||||
userId: user.id,
|
||||
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})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,24 +16,24 @@
|
||||
"schema": "db/schema.prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"lowdb": "2.1.0",
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,20 +11,20 @@
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
"get-port": "6.1.2",
|
||||
|
||||
15
integration-tests/next-13-app-dir/app/api/logout/route.ts
Normal file
15
integration-tests/next-13-app-dir/app/api/logout/route.ts
Normal 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},
|
||||
)
|
||||
},
|
||||
})
|
||||
11
integration-tests/next-13-app-dir/app/api/noauth/route.ts
Normal file
11
integration-tests/next-13-app-dir/app/api/noauth/route.ts
Normal 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,
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
import {rpcAppHandler} from "@blitzjs/rpc"
|
||||
import {withBlitzAuth} from "../../../../src/blitz-server"
|
||||
|
||||
export const {GET, POST, HEAD} = withBlitzAuth(rpcAppHandler())
|
||||
38
integration-tests/next-13-app-dir/app/api/signin/route.ts
Normal file
38
integration-tests/next-13-app-dir/app/api/signin/route.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {useQuery, useMutation} from "@blitzjs/rpc"
|
||||
import logout from "../src/auth/mutations/logout"
|
||||
import getCurrentUser from "../src/users/queries/getCurrentUser"
|
||||
import {useTransition} from "react"
|
||||
import {useRouter} from "next/navigation"
|
||||
|
||||
export default function Test() {
|
||||
const router = useRouter()
|
||||
const [user] = useQuery(getCurrentUser, null)
|
||||
const [isPending, startTransition] = useTransition()
|
||||
const [logoutMutation] = useMutation(logout)
|
||||
console.log(user)
|
||||
return (
|
||||
<div>
|
||||
<h1>Test</h1>
|
||||
<p>{user?.email}</p>
|
||||
<button
|
||||
className="button small"
|
||||
onClick={async () => {
|
||||
await logoutMutation()
|
||||
startTransition(() => {
|
||||
// Refresh the current route and fetch new data from the server without
|
||||
// losing client-side browser or React state.
|
||||
router.refresh()
|
||||
})
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -17,17 +17,17 @@
|
||||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"lowdb": "2.1.0",
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"secure-password": "4.0.0",
|
||||
"wait-port": "1.0.4"
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node": "18.7.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
@@ -45,6 +45,6 @@
|
||||
"node-fetch": "3.2.3",
|
||||
"playwright": "1.28.0",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
import {api} from "../../src/blitz-server"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
res.status(200).end()
|
||||
})
|
||||
@@ -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)}))
|
||||
@@ -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})
|
||||
})
|
||||
@@ -3,16 +3,16 @@ import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
|
||||
import db from "../db"
|
||||
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
|
||||
import {BlitzLogger} from "blitz"
|
||||
import {RpcServerPlugin} from "@blitzjs/rpc"
|
||||
|
||||
const {api, getBlitzContext} = setupBlitzServer({
|
||||
export const {api, getBlitzContext, withBlitzAuth} = setupBlitzServer({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "auth-tests-cookie-prefix",
|
||||
storage: PrismaStorage(db),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
RpcServerPlugin({}),
|
||||
],
|
||||
logger: BlitzLogger({}),
|
||||
})
|
||||
|
||||
export {api, getBlitzContext}
|
||||
|
||||
@@ -57,7 +57,7 @@ const runTests = (mode?: string) => {
|
||||
"should render result for open query",
|
||||
async () => {
|
||||
const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
|
||||
method: "GET",
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
@@ -67,7 +67,7 @@ const runTests = (mode?: string) => {
|
||||
|
||||
it("sets correct cookie", async () => {
|
||||
const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
|
||||
method: "GET",
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
})
|
||||
const cookieHeader = res.headers.get("Set-Cookie")
|
||||
@@ -94,6 +94,8 @@ const runTests = (mode?: string) => {
|
||||
async () => {
|
||||
const browser = await webdriver(appPort, "/react-query")
|
||||
|
||||
await browser.refresh()
|
||||
|
||||
browser.waitForElementByCss("#button", 0)
|
||||
await browser.elementByCss("#button").click()
|
||||
|
||||
@@ -133,7 +135,7 @@ const runTests = (mode?: string) => {
|
||||
|
||||
it("does not require CSRF header on HEAD requests", async () => {
|
||||
const res = await fetch(`http://localhost:${appPort}/api/noauth`, {
|
||||
method: "GET",
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json; charset=utf-8"},
|
||||
})
|
||||
const cookieHeader = res.headers.get("Set-Cookie")
|
||||
@@ -153,7 +155,7 @@ const runTests = (mode?: string) => {
|
||||
}
|
||||
|
||||
describe("Auth Tests", () => {
|
||||
describe("dev mode", async () => {
|
||||
describe("dev mode - webpack", async () => {
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
|
||||
@@ -169,19 +171,19 @@ describe("Auth Tests", () => {
|
||||
runTests()
|
||||
})
|
||||
|
||||
// describe("server mode", () => {
|
||||
// beforeAll(async () => {
|
||||
// try {
|
||||
// await runBlitzCommand(["prisma", "generate"])
|
||||
// await runBlitzCommand(["prisma", "migrate", "deploy"])
|
||||
// await blitzBuild()
|
||||
// // appPort = await findPort()
|
||||
// app = await blitzStart(appPort, {cwd: process.cwd()})
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// }, 5000 * 60 * 2)
|
||||
// afterAll(async () => await killApp(app))
|
||||
// runTests()
|
||||
// })
|
||||
describe("dev mode - turbo", async () => {
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
await runBlitzCommand(["prisma", "migrate", "reset", "--force"])
|
||||
appPort = await findPort()
|
||||
app = await blitzLaunchApp(appPort, {cwd: process.cwd()}, true)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}, 5000 * 60 * 2)
|
||||
afterAll(async () => {
|
||||
await killApp(app)
|
||||
})
|
||||
runTests()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,24 +16,24 @@
|
||||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,21 @@
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"@tanstack/react-query": "4.0.10",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@types/react": "18.0.25",
|
||||
"@testing-library/react": "16.0.1",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"@vitejs/plugin-react": "1.3.0",
|
||||
"delay": "5.0.0",
|
||||
"eslint": "8.27.0",
|
||||
|
||||
@@ -16,23 +16,23 @@
|
||||
"schema": "db/schema.prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
|
||||
@@ -16,24 +16,24 @@
|
||||
"schema": "db/schema.prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/next": "2.0.5",
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@prisma/client": "4.6.1",
|
||||
"blitz": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/next": "2.2.3",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@prisma/client": "6.1.0",
|
||||
"blitz": "2.2.3",
|
||||
"lowdb": "3.0.0",
|
||||
"next": "14.0.4",
|
||||
"prisma": "4.6.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"next": "15.0.1",
|
||||
"prisma": "6.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"b64-lite": "1.4.0",
|
||||
"eslint": "8.27.0",
|
||||
"fs-extra": "10.0.1",
|
||||
|
||||
@@ -9,7 +9,6 @@ declare module "@blitzjs/auth" {
|
||||
PublicData: {
|
||||
userId: User["id"]
|
||||
role: Role
|
||||
views?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ export function runBlitzCommandDev(argv, stdOut, opts: RunNextCommandDevOptions
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
console.log(`Running command "blitz ${argv.join(" ")}"`)
|
||||
const instance = spawn("node", [blitzBin, ...argv], {
|
||||
cwd,
|
||||
env,
|
||||
@@ -294,8 +295,8 @@ export function runBlitzCommandDev(argv, stdOut, opts: RunNextCommandDevOptions
|
||||
}
|
||||
|
||||
// Blitz Utils
|
||||
export function blitzLaunchApp(port, opts: RunNextCommandDevOptions) {
|
||||
return runBlitzCommandDev(["dev", "-p", port], undefined, opts)
|
||||
export function blitzLaunchApp(port, opts: RunNextCommandDevOptions, turbo = false) {
|
||||
return runBlitzCommandDev(["dev", "-p", port, turbo ? "--turbo" : ""], undefined, opts)
|
||||
}
|
||||
|
||||
export function blitzBuild(args = [], opts = {}): any {
|
||||
@@ -437,7 +438,7 @@ export function runNextCommandDev(argv, stdOut, opts: RunNextCommandDevOptions =
|
||||
}
|
||||
|
||||
if (opts.stdout !== false) {
|
||||
process.stdout.write(message)
|
||||
process.stdout.write(message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -923,4 +924,5 @@ interface RunNextCommandDevOptions {
|
||||
nodeArgs?: []
|
||||
bootupMarker?: any
|
||||
nextStart?: boolean
|
||||
turbo?: boolean
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "workspace:2.0.5",
|
||||
"@blitzjs/next": "workspace:2.0.5",
|
||||
"@blitzjs/rpc": "workspace:2.0.5",
|
||||
"@blitzjs/config": "workspace:2.2.3",
|
||||
"@blitzjs/next": "workspace:2.2.3",
|
||||
"@blitzjs/rpc": "workspace:2.2.3",
|
||||
"@tanstack/react-query": "4.13.0",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/react": "16.0.1",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@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/selenium-webdriver": "4.0.18",
|
||||
"chromedriver": "100.0.0",
|
||||
@@ -23,8 +23,8 @@
|
||||
"node-fetch": "3.2.3",
|
||||
"pkg-dir": "5.0.0",
|
||||
"playwright-chromium": "1.28.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"resolve-cwd": "3.0.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
|
||||
12
package.json
12
package.json
@@ -29,9 +29,9 @@
|
||||
"husky": "8.0.2",
|
||||
"jsdom": "^19.0.0",
|
||||
"lint-staged": "13.0.3",
|
||||
"next": "14.0.4",
|
||||
"next": "15.0.1",
|
||||
"only-allow": "1.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-prisma": "4.4.0",
|
||||
"pretty-quick": "3.1.3",
|
||||
"turbo": "1.10.9",
|
||||
@@ -47,7 +47,13 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"next-auth@4.18.7": "patches/next-auth@4.18.7.patch"
|
||||
"next-auth@4.24.7": "patches/next-auth@4.24.7.patch"
|
||||
},
|
||||
"overrides": {
|
||||
"@types/mime": "3.0.4",
|
||||
"next": "15.0.1",
|
||||
"@types/react": "npm:types-react@rc",
|
||||
"@types/react-dom": "npm:types-react-dom@rc"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,155 @@
|
||||
# @blitzjs/auth
|
||||
|
||||
## 2.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- aa033af9f: fix: Overriden custom cookies with pages router
|
||||
- blitz@2.2.3
|
||||
|
||||
## 2.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ab455acf0: fix: Overriden custom cookies used inside `withBlitzAuth`
|
||||
- blitz@2.2.2
|
||||
|
||||
## 2.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- blitz@2.2.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 318e9740d: feat: support next-auth version 4.24.7
|
||||
- Updated dependencies [318e9740d]
|
||||
- blitz@2.0.10
|
||||
|
||||
## 2.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5a14306f7]
|
||||
- blitz@2.0.9
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 77555468f: fix: add missing host while initialising the next-auth adapter
|
||||
- Updated dependencies [5e61a1681]
|
||||
- Updated dependencies [77555468f]
|
||||
- blitz@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ee7bf87ec]
|
||||
- Updated dependencies [178c152b2]
|
||||
- blitz@2.0.7
|
||||
|
||||
## 2.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5a587a6c3: Fix bundling issue that occurs in vercel due to the way imports were handled internally
|
||||
- Updated dependencies [76a2544f9]
|
||||
- blitz@2.0.6
|
||||
|
||||
## 2.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@blitzjs/auth",
|
||||
"version": "2.0.5",
|
||||
"version": "2.2.3",
|
||||
"homepage": "https://blitzjs.com/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -50,7 +50,7 @@
|
||||
"url": "0.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"blitz": "2.0.5",
|
||||
"blitz": "2.2.3",
|
||||
"next": "*",
|
||||
"next-auth": "*",
|
||||
"secure-password": "4.0.0"
|
||||
@@ -67,19 +67,18 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@testing-library/react": "16.0.1",
|
||||
"@types/cookie": "0.4.1",
|
||||
"@types/debug": "4.1.7",
|
||||
"@types/jsonwebtoken": "8.5.8",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "4.18.7",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"next-auth": "4.24.7",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"secure-password": "4.0.0",
|
||||
"typescript": "^4.8.4",
|
||||
"unbuild": "0.7.6",
|
||||
|
||||
@@ -7,7 +7,7 @@ import {parsePublicDataToken, getPublicDataStore, useSession} from "./index"
|
||||
import {COOKIE_PUBLIC_DATA_TOKEN} from "../shared"
|
||||
import {toBase64} from "b64-lite"
|
||||
import {act} from "@testing-library/react"
|
||||
import {renderHook} from "@testing-library/react-hooks"
|
||||
import {renderHook} from "@testing-library/react"
|
||||
|
||||
vi.mock("blitz", async () => {
|
||||
const blitz = await vi.importActual("blitz")
|
||||
|
||||
@@ -161,6 +161,8 @@ export const useSession = (options: UseSessionOptions = {}): ClientSession => {
|
||||
if (isServer) {
|
||||
const e = new Error()
|
||||
e.name = "Rendering Suspense fallback..."
|
||||
;(e as any).digest = "DYNAMIC_SERVER_USAGE"
|
||||
e.message = "DYNAMIC_SERVER_USAGE"
|
||||
delete e.stack
|
||||
throw e
|
||||
} else {
|
||||
@@ -258,7 +260,7 @@ export type RedirectAuthenticatedToFn = (
|
||||
) => RedirectAuthenticatedTo
|
||||
|
||||
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>}
|
||||
suppressFirstRenderFlicker?: boolean
|
||||
redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn
|
||||
|
||||
@@ -107,13 +107,8 @@ export function NextAuthAdapter<P extends Provider[]>(
|
||||
providerId = providerId.split("?")[0]
|
||||
}
|
||||
const {options, cookies} = await init({
|
||||
// @ts-ignore
|
||||
url: new URL(
|
||||
// @ts-ignore
|
||||
internalRequest.url!,
|
||||
process.env.APP_ORIGIN || process.env.BLITZ_DEV_SERVER_ORIGIN,
|
||||
),
|
||||
authOptions: config as unknown as AuthOptions,
|
||||
origin: internalRequest.origin,
|
||||
action,
|
||||
providerId,
|
||||
callbackUrl: req.body?.callbackUrl ?? (req.query?.callbackUrl as string),
|
||||
|
||||
@@ -47,7 +47,12 @@ async function readJSONBody(
|
||||
// prettier-ignore
|
||||
const actions = [ "providers", "session", "csrf", "login", "signout", "callback", "verify-request", "error", "_log"]
|
||||
|
||||
export async function toInternalRequest(req: Request): Promise<RequestInternal | Error> {
|
||||
export async function toInternalRequest(req: Request): Promise<
|
||||
| (RequestInternal & {
|
||||
url: URL
|
||||
})
|
||||
| Error
|
||||
> {
|
||||
try {
|
||||
// TODO: url.toString() should not include action and providerId
|
||||
// see init.ts
|
||||
@@ -69,17 +74,28 @@ export async function toInternalRequest(req: Request): Promise<RequestInternal |
|
||||
providerId = providerIdOrAction
|
||||
}
|
||||
|
||||
const headers = Object.fromEntries(req.headers)
|
||||
const detectOrigin = (host?: string, proto?: string) => {
|
||||
if (process.env.VERCEL ?? process.env.FLIGHTCONTROL ?? process.env.AUTH_TRUST_HOST)
|
||||
return `${proto === "http" ? "http" : "https"}://${host}`
|
||||
|
||||
return process.env.NEXTAUTH_URL
|
||||
}
|
||||
|
||||
return {
|
||||
//@ts-ignore
|
||||
url,
|
||||
action,
|
||||
providerId,
|
||||
method: req.method ?? "GET",
|
||||
headers: Object.fromEntries(req.headers),
|
||||
headers,
|
||||
body: req.body ? await readJSONBody(req.body) : undefined,
|
||||
cookies: parseCookie(req.headers.get("cookie") ?? "") ?? {},
|
||||
error: url.searchParams.get("error") ?? undefined,
|
||||
query: Object.fromEntries(url.searchParams),
|
||||
origin: detectOrigin(
|
||||
headers["x-forwarded-host"] ?? headers.host,
|
||||
headers["x-forwarded-proto"],
|
||||
),
|
||||
}
|
||||
} catch (error) {
|
||||
return error as Error
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {RequestMiddleware, Ctx, createServerPlugin} 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 {getBlitzContext, getSession, useAuthenticatedBlitzContext} from "./auth-sessions"
|
||||
|
||||
@@ -130,11 +130,28 @@ export const AuthServerPlugin = createServerPlugin((options: AuthPluginOptions)
|
||||
if (!globalThis.__BLITZ_GET_RSC_CONTEXT) {
|
||||
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 {
|
||||
requestMiddlewares: [authPluginSessionMiddleware()],
|
||||
exports: () => ({
|
||||
getBlitzContext,
|
||||
useAuthenticatedBlitzContext,
|
||||
withBlitzAuth,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,41 +1,226 @@
|
||||
import {expect, describe, it} from "vitest"
|
||||
import {setCookie} from "./auth-sessions"
|
||||
import cookie from "cookie"
|
||||
import {expect, describe, it, beforeEach} from "vitest"
|
||||
import {ServerResponse} from "http"
|
||||
import {Writable} from "stream"
|
||||
import {append} from "./auth-sessions"
|
||||
|
||||
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")
|
||||
class MockServerResponse extends Writable {
|
||||
private headers: Map<string, string | string[]> = new Map()
|
||||
|
||||
getHeader(name: string) {
|
||||
return this.headers.get(name)
|
||||
}
|
||||
|
||||
setHeader(name: string, value: string | string[]) {
|
||||
this.headers.set(name, value)
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
return Object.fromEntries(this.headers)
|
||||
}
|
||||
|
||||
_write(_chunk: unknown, _encoding: string, callback: (error?: Error | null) => void): void {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
describe("append", () => {
|
||||
let res: ServerResponse
|
||||
const COOKIE_PREFIX = "auth-tests-cookie-prefix_s"
|
||||
|
||||
beforeEach(() => {
|
||||
res = new MockServerResponse() as unknown as ServerResponse
|
||||
})
|
||||
|
||||
describe("Blitz Auth Flows", () => {
|
||||
const anonymousSessionCookie = `${COOKIE_PREFIX}AnonymousSessionToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJibGl0empzIjp7ImlzQW5vbnltb3VzIjp0cnVlLCJoYW5kbGUiOiJEVjk4OVZadFpra0lpWHFSOFRPX3Fvem44MHBwWFBnaDphand0IiwicHVibGljRGF0YSI6eyJ1c2VySWQiOm51bGx9LCJhbnRpQ1NSRlRva2VuIjoiM25BdDBZWVI0b0xDNnAtTm1fQW1CeFQxRmJmVmpiaXMifSwiaWF0IjoxNzQwODA0NTE4LCJhdWQiOiJibGl0empzIiwiaXNzIjoiYmxpdHpqcyIsInN1YiI6ImFub255bW91cyJ9.ZpMxWh3Yq2Qe4BXzZ61d4V0YGV2luswF7ovE90DxURM; Path=/; Expires=Thu, 28 Feb 2030 04:48:38 GMT; HttpOnly; SameSite=Lax`
|
||||
const antiCsrfCookie = `${COOKIE_PREFIX}AntiCsrfToken=3nAt0YYR4oLC6p-Nm_AmBxT1FbfVjbis; Path=/; Expires=Thu, 28 Feb 2030 04:48:38 GMT; SameSite=Lax`
|
||||
const publicDataCookie = `${COOKIE_PREFIX}PublicDataToken=eyJ1c2VySWQiOm51bGx9; Path=/; Expires=Thu, 28 Feb 2030 04:48:38 GMT; SameSite=Lax`
|
||||
|
||||
const expiredSessionCookie = `${COOKIE_PREFIX}SessionToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Lax`
|
||||
const expiredAnonymousCookie = `${COOKIE_PREFIX}AnonymousSessionToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Lax`
|
||||
|
||||
// Login cookies
|
||||
const loginAntiCsrfCookie = `${COOKIE_PREFIX}AntiCsrfToken=1s3yaYs0yThO-DwOuiepJLzycvN090tO; Path=/; Expires=Mon, 31 Mar 2025 04:48:38 GMT; SameSite=Lax`
|
||||
const loginPublicDataCookie = `${COOKIE_PREFIX}PublicDataToken=eyJ1c2VySWQiOjEsInJvbGUiOiJ1c2VyIn0%3D; Path=/; Expires=Mon, 31 Mar 2025 04:48:38 GMT; SameSite=Lax`
|
||||
const loginSessionCookie = `${COOKIE_PREFIX}SessionToken=aGNjc0o5anJ5eTF4bDdqRE5VN09LeEx5QUJoR2toUjc6b3RzO1NaWC1la3YydGR4UGNjWVp6QkM0SlBQbUdWWmZEMlpFOzhhYWU1MDI2M2Q0YmUyNDIxZWYwNDBmMmFhZGI2MDk4YTNiNjhjMTAyZjlmNmNjYTQ4NzUzMGZiYjc0ZTdhYmI7djA%3D; Path=/; Expires=Mon, 31 Mar 2025 04:48:38 GMT; HttpOnly; SameSite=Lax`
|
||||
|
||||
it("should handle anonymous session cookies", () => {
|
||||
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(cookies).toHaveLength(3)
|
||||
expect(cookies[0]).toBe(anonymousSessionCookie)
|
||||
expect(cookies[1]).toBe(antiCsrfCookie)
|
||||
expect(cookies[2]).toBe(publicDataCookie)
|
||||
})
|
||||
|
||||
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("should deduplicate cookies when the same one is set twice", () => {
|
||||
append(res, "Set-Cookie", anonymousSessionCookie)
|
||||
append(res, "Set-Cookie", anonymousSessionCookie)
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(cookies).toHaveLength(1)
|
||||
expect(cookies[0]).toBe(anonymousSessionCookie)
|
||||
})
|
||||
|
||||
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", {}),
|
||||
it("should replace cookies with same name when values change", () => {
|
||||
append(res, "Set-Cookie", anonymousSessionCookie)
|
||||
|
||||
const updatedAnonymousCookie = `${COOKIE_PREFIX}AnonymousSessionToken=NEW_TOKEN_VALUE; Path=/; SameSite=Lax`
|
||||
append(res, "Set-Cookie", updatedAnonymousCookie)
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(cookies).toHaveLength(1)
|
||||
expect(cookies[0]).toBe(updatedAnonymousCookie)
|
||||
})
|
||||
|
||||
it("should handle session expiration", () => {
|
||||
// First add anonymous session
|
||||
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||
|
||||
append(res, "Set-Cookie", [expiredSessionCookie, expiredAnonymousCookie])
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(cookies).toHaveLength(4)
|
||||
|
||||
expect(cookies.find((c) => c === expiredSessionCookie)).toBeDefined()
|
||||
expect(cookies.find((c) => c === expiredAnonymousCookie)).toBeDefined()
|
||||
})
|
||||
|
||||
it("should handle login flow cookies", () => {
|
||||
// First anonymous session
|
||||
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||
|
||||
// Then login, which expires anonymous and sets new session
|
||||
append(res, "Set-Cookie", [
|
||||
expiredAnonymousCookie,
|
||||
loginSessionCookie,
|
||||
loginAntiCsrfCookie,
|
||||
loginPublicDataCookie,
|
||||
])
|
||||
setCookie(res, cookie.serialize("C", "c-value", {}))
|
||||
expect(res.getHeader("Set-Cookie")).toEqual(["A=a-value", "B=b-value", "C=c-value"])
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
|
||||
// Should have 4 cookies:
|
||||
// - Original antiCsrf cookie (should be replaced by login one)
|
||||
// - Expired anonymous cookie
|
||||
// - Login session cookie
|
||||
// - Login publicData cookie
|
||||
expect(cookies).toHaveLength(4)
|
||||
|
||||
// Check proper replacement by extracting cookie names
|
||||
const cookieNames = cookies.map((c) => {
|
||||
const namePart = c.substring(0, c.indexOf("="))
|
||||
return namePart
|
||||
})
|
||||
|
||||
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AntiCsrfToken`)).toHaveLength(1)
|
||||
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}PublicDataToken`)).toHaveLength(1)
|
||||
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}SessionToken`)).toHaveLength(1)
|
||||
// the expired cookie
|
||||
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AnonymousSessionToken`)).toHaveLength(
|
||||
1,
|
||||
)
|
||||
})
|
||||
|
||||
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", {}),
|
||||
it("should properly combine multiple append calls with different cookie groups", () => {
|
||||
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie])
|
||||
|
||||
append(res, "Set-Cookie", [publicDataCookie, loginAntiCsrfCookie])
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
|
||||
expect(cookies).toHaveLength(3)
|
||||
|
||||
const antiCsrfCookies = cookies.filter((c) => c.includes(`${COOKIE_PREFIX}AntiCsrfToken`))
|
||||
expect(antiCsrfCookies).toHaveLength(1)
|
||||
expect(antiCsrfCookies[0]).toBe(loginAntiCsrfCookie)
|
||||
})
|
||||
|
||||
it("should handle the full session flow", () => {
|
||||
append(res, "Set-Cookie", [anonymousSessionCookie, antiCsrfCookie, publicDataCookie])
|
||||
|
||||
const initialCookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(initialCookies).toHaveLength(3)
|
||||
|
||||
append(res, "Set-Cookie", [
|
||||
expiredAnonymousCookie,
|
||||
loginSessionCookie,
|
||||
loginAntiCsrfCookie,
|
||||
loginPublicDataCookie,
|
||||
])
|
||||
setCookie(res, cookie.serialize("A", "new-a-value", {}))
|
||||
expect(res.getHeader("Set-Cookie")).toEqual(["A=new-a-value", "B=b-value"])
|
||||
const loginCookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(loginCookies).toHaveLength(4)
|
||||
|
||||
append(res, "Set-Cookie", [
|
||||
expiredSessionCookie,
|
||||
anonymousSessionCookie,
|
||||
antiCsrfCookie,
|
||||
publicDataCookie,
|
||||
])
|
||||
const logoutCookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(logoutCookies).toHaveLength(4)
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
|
||||
const cookieNames = cookies.map((c) => c.substring(0, c.indexOf("=")))
|
||||
|
||||
const counts = cookieNames.reduce((acc, name) => {
|
||||
acc[name] = (acc[name] || 0) + 1
|
||||
return acc
|
||||
}, {} as Record<string, number>)
|
||||
|
||||
expect(Object.keys(counts).length).toBe(4)
|
||||
|
||||
Object.values(counts).forEach((count) => {
|
||||
expect(count).toBeLessThanOrEqual(3)
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle cookies with quoted values and special characters", () => {
|
||||
const specialCookie = `${COOKIE_PREFIX}PublicDataToken="eyJ1c2VySWQiOjEsIm5hbWUiOiJKb2huIERvZSwgSnIuIn0%3D"; Path=/; SameSite=Lax`
|
||||
append(res, "Set-Cookie", specialCookie)
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
expect(cookies).toHaveLength(1)
|
||||
expect(cookies[0]).toBe(specialCookie)
|
||||
})
|
||||
|
||||
it("should properly merge with existing custom cookies already in the response", () => {
|
||||
const customCookie1 = "custom1=value1; Path=/; HttpOnly"
|
||||
const customCookie2 = "custom2=value2; Path=/; HttpOnly"
|
||||
const existingAuthCookie = `${COOKIE_PREFIX}AntiCsrfToken=old-token; Path=/; SameSite=Lax`
|
||||
|
||||
res.setHeader("Set-Cookie", [customCookie1, customCookie2, existingAuthCookie])
|
||||
|
||||
// login
|
||||
append(res, "Set-Cookie", [anonymousSessionCookie, loginAntiCsrfCookie, publicDataCookie])
|
||||
|
||||
const cookies = res.getHeader("Set-Cookie") as string[]
|
||||
|
||||
expect(cookies).toHaveLength(5)
|
||||
|
||||
// Custom cookies should be preserved
|
||||
expect(cookies).toContain(customCookie1)
|
||||
expect(cookies).toContain(customCookie2)
|
||||
|
||||
// Auth cookies should be correctly applied, with antiCsrf being updated
|
||||
expect(cookies).toContain(anonymousSessionCookie)
|
||||
expect(cookies).toContain(loginAntiCsrfCookie)
|
||||
expect(cookies).toContain(publicDataCookie)
|
||||
|
||||
// The old auth cookie should be replaced
|
||||
expect(cookies).not.toContain(existingAuthCookie)
|
||||
|
||||
// Verify we have the right counts of each cookie type
|
||||
const cookieNames = cookies.map((c) => c.substring(0, c.indexOf("=")))
|
||||
expect(cookieNames.filter((n) => n === "custom1")).toHaveLength(1)
|
||||
expect(cookieNames.filter((n) => n === "custom2")).toHaveLength(1)
|
||||
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AnonymousSessionToken`)).toHaveLength(
|
||||
1,
|
||||
)
|
||||
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}AntiCsrfToken`)).toHaveLength(1)
|
||||
expect(cookieNames.filter((n) => n === `${COOKIE_PREFIX}PublicDataToken`)).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {fromBase64, toBase64} from "b64-lite"
|
||||
import cookie, {parse} from "cookie"
|
||||
import {IncomingMessage, ServerResponse} from "http"
|
||||
import {sign as jwtSign, verify as jwtVerify} from "jsonwebtoken"
|
||||
import jsonwebtoken from "jsonwebtoken"
|
||||
import {
|
||||
assert,
|
||||
isPast,
|
||||
@@ -13,9 +12,9 @@ import {
|
||||
AuthorizationError,
|
||||
CSRFTokenMismatchError,
|
||||
log,
|
||||
AuthenticatedCtx,
|
||||
baseLogger,
|
||||
chalk,
|
||||
AuthenticatedCtx,
|
||||
} from "blitz"
|
||||
import {
|
||||
EmptyPublicData,
|
||||
@@ -39,12 +38,69 @@ import {
|
||||
AuthenticatedSessionContext,
|
||||
} from "../shared"
|
||||
import {generateToken, hash256} from "./auth-utils"
|
||||
import {Socket} from "net"
|
||||
import {UrlObject} from "url"
|
||||
import {formatWithValidation} from "../shared/url-utils"
|
||||
|
||||
export function isLocalhost(req: IncomingMessage): boolean {
|
||||
let {host} = req.headers
|
||||
import type {UrlObject} from "url"
|
||||
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
|
||||
if (host) {
|
||||
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> {
|
||||
({ctx, args}: {ctx: any; args: [roleOrRoles?: RoleType | RoleType[]]}): boolean
|
||||
@@ -131,14 +188,6 @@ type 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(
|
||||
res: ServerResponse & {[key: string]: any},
|
||||
): asserts res is ServerResponse & {blitzCtx: Ctx} {
|
||||
@@ -147,59 +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(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
appDir = false,
|
||||
isRsc: boolean,
|
||||
): Promise<SessionContext>
|
||||
export async function getSession(
|
||||
req: IncomingMessage | Request,
|
||||
res?: ServerResponse,
|
||||
isRsc?: boolean,
|
||||
): Promise<SessionContext> {
|
||||
ensureApiRequest(req)
|
||||
ensureMiddlewareResponse(res)
|
||||
|
||||
debug("cookiePrefix", globalThis.__BLITZ_SESSION_COOKIE_PREFIX)
|
||||
|
||||
if (res.blitzCtx.session) {
|
||||
debug("Returning existing session")
|
||||
return res.blitzCtx.session
|
||||
const headers = convertRequestToHeader(req)
|
||||
if (res) {
|
||||
ensureMiddlewareResponse(res)
|
||||
debug("cookiePrefix", globalThis.__BLITZ_SESSION_COOKIE_PREFIX)
|
||||
if (res.blitzCtx.session) {
|
||||
debug("Returning existing session")
|
||||
return res.blitzCtx.session
|
||||
}
|
||||
}
|
||||
|
||||
let sessionKernel = await getSessionKernel(req, res)
|
||||
const method = req.method
|
||||
let sessionKernel = await getSessionKernel({headers, method})
|
||||
|
||||
if (sessionKernel) {
|
||||
debug("Got existing session", sessionKernel)
|
||||
}
|
||||
|
||||
if (!sessionKernel) {
|
||||
debug("No session found, creating anonymous session")
|
||||
sessionKernel = await createAnonymousSession(req, res)
|
||||
sessionKernel = await createAnonymousSession({headers})
|
||||
}
|
||||
|
||||
const sessionContext = makeProxyToPublicData(
|
||||
new SessionContextClass(req, res, sessionKernel, appDir),
|
||||
new SessionContextClass(headers, sessionKernel, !!isRsc, res),
|
||||
)
|
||||
debug("New session context")
|
||||
res.blitzCtx.session = sessionContext
|
||||
if (res) {
|
||||
;(res as any).blitzCtx = {
|
||||
session: sessionContext,
|
||||
}
|
||||
sessionContext.setSession(res)
|
||||
}
|
||||
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> {
|
||||
try {
|
||||
//using eval to avoid bundling next/headers
|
||||
const {headers, cookies} = eval("require('next/headers')")
|
||||
const req = new IncomingMessage(new Socket()) as IncomingMessage & {
|
||||
cookies: {[key: string]: string}
|
||||
}
|
||||
req.headers = Object.fromEntries(headers())
|
||||
const csrfToken = cookies().get(COOKIE_CSRF_TOKEN())
|
||||
const {headers, cookies} = require("next/headers")
|
||||
const cookieStore = await cookies()
|
||||
const headersStore = await headers()
|
||||
const reqHeader = Object.fromEntries(headersStore)
|
||||
const csrfToken = cookieStore.get(COOKIE_CSRF_TOKEN())
|
||||
if (csrfToken) {
|
||||
req.headers[HEADER_CSRF] = csrfToken.value
|
||||
reqHeader[HEADER_CSRF] = csrfToken.value
|
||||
}
|
||||
req.cookies = Object.fromEntries(
|
||||
cookies()
|
||||
.getAll()
|
||||
.map((c: {name: string; value: string}) => [c.name, c.value]),
|
||||
const session = await getSession(
|
||||
{
|
||||
headers: new Headers(reqHeader),
|
||||
method: "POST",
|
||||
} as Request,
|
||||
null as never,
|
||||
true,
|
||||
)
|
||||
const res = new ServerResponse(req)
|
||||
const session = await getSession(req, res, true)
|
||||
const ctx: Ctx = {
|
||||
session,
|
||||
}
|
||||
@@ -207,17 +311,13 @@ export async function getBlitzContext(): Promise<Ctx> {
|
||||
} catch (e) {
|
||||
if ((e as NodeJS.ErrnoException).code === "MODULE_NOT_FOUND") {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
interface RouteUrlObject extends Pick<UrlObject, "pathname" | "query" | "href"> {
|
||||
pathname: string
|
||||
}
|
||||
|
||||
export async function useAuthenticatedBlitzContext({
|
||||
redirectTo,
|
||||
redirectAuthenticatedTo,
|
||||
@@ -234,8 +334,7 @@ export async function useAuthenticatedBlitzContext({
|
||||
const ctx: Ctx = await getBlitzContext()
|
||||
const userId = ctx.session.userId
|
||||
try {
|
||||
//using eval to avoid bundling next/navigation
|
||||
const {redirect} = eval("require('next/navigation')")
|
||||
const {redirect} = require("next/navigation")
|
||||
if (userId) {
|
||||
debug("[useAuthenticatedBlitzContext] User is authenticated")
|
||||
if (redirectAuthenticatedTo) {
|
||||
@@ -293,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 message = `Method ${method} is not yet supported in React Server Components`
|
||||
const _box = await log.box(message, log.chalk.hex("8a3df0").bold("Blitz Auth"))
|
||||
@@ -312,21 +399,27 @@ const NotSupportedMessage = async (method: string) => {
|
||||
}
|
||||
|
||||
export class SessionContextClass implements SessionContext {
|
||||
private _req: IncomingMessage & {cookies: {[key: string]: string}}
|
||||
private _res: ServerResponse & {blitzCtx: Ctx}
|
||||
private _headers: Headers
|
||||
private _kernel: SessionKernel
|
||||
private _appDir: boolean
|
||||
private _isRsc: boolean
|
||||
private _response?: ServerResponse
|
||||
|
||||
constructor(
|
||||
req: IncomingMessage & {cookies: {[key: string]: string}},
|
||||
res: ServerResponse & {blitzCtx: Ctx},
|
||||
kernel: SessionKernel,
|
||||
appDir: boolean,
|
||||
) {
|
||||
this._req = req
|
||||
this._res = res
|
||||
private static headersToIncludeInResponse = [
|
||||
HEADER_CSRF,
|
||||
HEADER_CSRF_ERROR,
|
||||
HEADER_PUBLIC_DATA_TOKEN,
|
||||
HEADER_SESSION_CREATED,
|
||||
]
|
||||
|
||||
constructor(headers: Headers, kernel: SessionKernel, isRsc: boolean, response?: ServerResponse) {
|
||||
this._headers = headers
|
||||
this._kernel = kernel
|
||||
this._appDir = appDir
|
||||
this._isRsc = isRsc
|
||||
this._response = response
|
||||
}
|
||||
|
||||
$antiCSRFToken() {
|
||||
return this._kernel.antiCSRFToken
|
||||
}
|
||||
|
||||
get $handle() {
|
||||
@@ -354,38 +447,69 @@ export class SessionContextClass implements SessionContext {
|
||||
$isAuthorized(...args: IsAuthorizedArgs) {
|
||||
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 {
|
||||
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.append("Set-Cookie", cookieHeaders!)
|
||||
} else {
|
||||
append(response, "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>) {
|
||||
if (this._appDir) {
|
||||
if (this._isRsc) {
|
||||
void NotSupportedMessage("$create")
|
||||
return
|
||||
}
|
||||
this._kernel = await createNewSession({
|
||||
req: this._req,
|
||||
res: this._res,
|
||||
headers: this._headers,
|
||||
publicData,
|
||||
privateData,
|
||||
jwtPayload: this._kernel.jwtPayload,
|
||||
anonymous: false,
|
||||
})
|
||||
|
||||
if (this._response) this.setSession(this._response)
|
||||
}
|
||||
|
||||
async $revoke() {
|
||||
if (this._appDir) {
|
||||
if (this._isRsc) {
|
||||
void NotSupportedMessage("$revoke")
|
||||
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() {
|
||||
if (this._appDir) {
|
||||
if (this._isRsc) {
|
||||
void NotSupportedMessage("$revokeAll")
|
||||
return
|
||||
}
|
||||
@@ -397,25 +521,28 @@ export class SessionContextClass implements SessionContext {
|
||||
}
|
||||
|
||||
async $setPublicData(data: Record<any, any>) {
|
||||
if (this._appDir) {
|
||||
if (this._isRsc) {
|
||||
void NotSupportedMessage("$setPublicData")
|
||||
return
|
||||
}
|
||||
if (this.userId) {
|
||||
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() {
|
||||
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")
|
||||
return Promise.resolve()
|
||||
}
|
||||
return setPrivateData(this._kernel, data)
|
||||
await setPrivateData(this._kernel, data)
|
||||
if (this._response) this.setSession(this._response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +634,7 @@ const JWT_ANONYMOUS_SUBJECT = "anonymous"
|
||||
const JWT_ALGORITHM = "HS256"
|
||||
|
||||
const createAnonymousSessionToken = (payload: AnonymousSessionPayload) => {
|
||||
return jwtSign({[JWT_NAMESPACE]: payload}, getSessionSecretKey() || "", {
|
||||
return jsonwebtoken.sign({[JWT_NAMESPACE]: payload}, getSessionSecretKey() || "", {
|
||||
algorithm: JWT_ALGORITHM,
|
||||
issuer: JWT_ISSUER,
|
||||
audience: JWT_AUDIENCE,
|
||||
@@ -521,7 +648,7 @@ const parseAnonymousSessionToken = (token: string) => {
|
||||
const secret = getSessionSecretKey()
|
||||
|
||||
try {
|
||||
const fullPayload = jwtVerify(token, secret!, {
|
||||
const fullPayload = jsonwebtoken.verify(token, secret!, {
|
||||
algorithms: [JWT_ALGORITHM],
|
||||
issuer: JWT_ISSUER,
|
||||
audience: JWT_AUDIENCE,
|
||||
@@ -538,126 +665,127 @@ const parseAnonymousSessionToken = (token: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const setCookie = (res: ServerResponse, cookieStr: string) => {
|
||||
const getCookieName = (c: string) => c.split("=", 2)[0]
|
||||
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
|
||||
const cookieOptions = (headers: Headers, expires: Date, httpOnly: boolean) => {
|
||||
return {
|
||||
path: "/",
|
||||
secure:
|
||||
global.sessionConfig.secureCookies &&
|
||||
!isLocalhost({
|
||||
headers,
|
||||
} as Request),
|
||||
sameSite: global.sessionConfig.sameSite,
|
||||
domain: global.sessionConfig.domain,
|
||||
expires: new Date(expires),
|
||||
httpOnly,
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof cookiesHeader === "string") {
|
||||
if (cookieName === getCookieName(cookiesHeader)) {
|
||||
res.setHeader("Set-Cookie", cookieStr)
|
||||
function replaceOrAppendValueInSetCookieHeader(
|
||||
headers: Headers,
|
||||
cookieName: string,
|
||||
newValue: string,
|
||||
): string {
|
||||
const cookies = headers.get("set-cookie")
|
||||
if (!cookies) return newValue
|
||||
const cookiesAsArray = splitCookiesString(cookies!)
|
||||
for (let i = 0; i < cookiesAsArray.length; i++) {
|
||||
const cookie = cookiesAsArray[i]
|
||||
if (cookie?.startsWith(cookieName)) {
|
||||
cookiesAsArray[i] = newValue
|
||||
return cookiesAsArray.join(", ")
|
||||
} 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
|
||||
if (i === cookiesAsArray.length - 1) {
|
||||
cookiesAsArray.push(newValue)
|
||||
return cookiesAsArray.join(", ")
|
||||
}
|
||||
}
|
||||
appendCookie()
|
||||
}
|
||||
return cookiesAsArray.filter(Boolean).join(", ")
|
||||
}
|
||||
|
||||
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: "/",
|
||||
httpOnly: true,
|
||||
secure: global.sessionConfig.secureCookies,
|
||||
sameSite: global.sessionConfig.sameSite,
|
||||
domain: global.sessionConfig.domain,
|
||||
expires: expiresAt,
|
||||
}),
|
||||
const setSessionCookie = (headers: Headers, sessionToken: string, expiresAt: Date) => {
|
||||
const sessionCookie = cookie.serialize(
|
||||
COOKIE_SESSION_TOKEN(),
|
||||
sessionToken,
|
||||
cookieOptions(headers, expiresAt, true),
|
||||
)
|
||||
}
|
||||
|
||||
const setAnonymousSessionCookie = (res: ServerResponse, token: string, expiresAt: Date) => {
|
||||
setCookie(
|
||||
res,
|
||||
cookie.serialize(COOKIE_ANONYMOUS_SESSION_TOKEN(), token, {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: global.sessionConfig.secureCookies,
|
||||
sameSite: global.sessionConfig.sameSite,
|
||||
domain: global.sessionConfig.domain,
|
||||
expires: expiresAt,
|
||||
}),
|
||||
const newCookies = replaceOrAppendValueInSetCookieHeader(
|
||||
headers,
|
||||
COOKIE_SESSION_TOKEN(),
|
||||
sessionCookie,
|
||||
)
|
||||
headers.set("Set-Cookie", newCookies)
|
||||
}
|
||||
|
||||
const setCSRFCookie = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
antiCSRFToken: string,
|
||||
expiresAt: Date,
|
||||
) => {
|
||||
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)
|
||||
assert(antiCSRFToken !== undefined, "Internal error: antiCSRFToken is being set to undefined")
|
||||
setCookie(
|
||||
res,
|
||||
cookie.serialize(COOKIE_CSRF_TOKEN(), antiCSRFToken, {
|
||||
path: "/",
|
||||
secure: global.sessionConfig.secureCookies && !isLocalhost(req),
|
||||
sameSite: global.sessionConfig.sameSite,
|
||||
domain: global.sessionConfig.domain,
|
||||
expires: expiresAt,
|
||||
}),
|
||||
const csrfCookie = cookie.serialize(
|
||||
COOKIE_CSRF_TOKEN(),
|
||||
antiCSRFToken,
|
||||
cookieOptions(headers, expiresAt, false),
|
||||
)
|
||||
|
||||
const newCookies = replaceOrAppendValueInSetCookieHeader(headers, COOKIE_CSRF_TOKEN(), csrfCookie)
|
||||
headers.set("Set-Cookie", newCookies)
|
||||
}
|
||||
|
||||
const setPublicDataCookie = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
publicDataToken: string,
|
||||
expiresAt: Date,
|
||||
) => {
|
||||
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 setPublicDataCookie = (headers: Headers, publicDataToken: string, expiresAt: Date) => {
|
||||
headers.set(HEADER_PUBLIC_DATA_TOKEN, "updated")
|
||||
const publicDataCookie = cookie.serialize(
|
||||
COOKIE_PUBLIC_DATA_TOKEN(),
|
||||
publicDataToken,
|
||||
cookieOptions(headers, expiresAt, false),
|
||||
)
|
||||
const newCookies = replaceOrAppendValueInSetCookieHeader(
|
||||
headers,
|
||||
COOKIE_PUBLIC_DATA_TOKEN(),
|
||||
publicDataCookie,
|
||||
)
|
||||
headers.set("Set-Cookie", newCookies)
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
// Get Session
|
||||
// --------------------------------
|
||||
async function getSessionKernel(
|
||||
req: IncomingMessage & {cookies: {[key: string]: string}},
|
||||
res: ServerResponse,
|
||||
): Promise<SessionKernel | null> {
|
||||
const anonymousSessionToken = req.cookies[COOKIE_ANONYMOUS_SESSION_TOKEN()]
|
||||
const sessionToken = req.cookies[COOKIE_SESSION_TOKEN()] // for essential method
|
||||
const idRefreshToken = req.cookies[COOKIE_REFRESH_TOKEN()] // for advanced method
|
||||
async function getSessionKernel({
|
||||
headers,
|
||||
method,
|
||||
}: {
|
||||
headers: Headers
|
||||
method: string | undefined
|
||||
}): 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 =
|
||||
req.method !== "GET" &&
|
||||
req.method !== "OPTIONS" &&
|
||||
req.method !== "HEAD" &&
|
||||
method !== "GET" &&
|
||||
method !== "OPTIONS" &&
|
||||
method !== "HEAD" &&
|
||||
!process.env.DANGEROUSLY_DISABLE_CSRF_PROTECTION
|
||||
const antiCSRFToken = req.headers[HEADER_CSRF] as string | undefined
|
||||
|
||||
if (sessionToken) {
|
||||
debug("[getSessionKernel] Request has sessionToken")
|
||||
@@ -700,7 +828,7 @@ async function getSessionKernel(
|
||||
)
|
||||
}
|
||||
|
||||
setHeader(res, HEADER_CSRF_ERROR, "true")
|
||||
headers.set(HEADER_CSRF_ERROR, "true")
|
||||
throw new CSRFTokenMismatchError()
|
||||
}
|
||||
|
||||
@@ -712,7 +840,7 @@ async function getSessionKernel(
|
||||
* But only renew with non-GET requests because a GET request could be from a
|
||||
* browser level navigation
|
||||
*/
|
||||
if (req.method !== "GET") {
|
||||
if (method !== "GET") {
|
||||
// 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
|
||||
const hasPublicDataChanged =
|
||||
@@ -735,8 +863,7 @@ async function getSessionKernel(
|
||||
|
||||
if (hasPublicDataChanged || hasQuarterExpiryTimePassed) {
|
||||
await refreshSession(
|
||||
req,
|
||||
res,
|
||||
headers,
|
||||
{
|
||||
handle,
|
||||
publicData: JSON.parse(persistedSession.publicData || ""),
|
||||
@@ -776,7 +903,7 @@ async function getSessionKernel(
|
||||
)
|
||||
}
|
||||
|
||||
setHeader(res, HEADER_CSRF_ERROR, "true")
|
||||
headers.set(HEADER_CSRF_ERROR, "true")
|
||||
throw new CSRFTokenMismatchError()
|
||||
}
|
||||
|
||||
@@ -797,16 +924,14 @@ async function getSessionKernel(
|
||||
// Create Session
|
||||
// --------------------------------
|
||||
interface CreateNewAnonSession {
|
||||
req: IncomingMessage
|
||||
res: ServerResponse
|
||||
headers: Headers
|
||||
publicData: EmptyPublicData
|
||||
privateData?: Record<any, any>
|
||||
anonymous: true
|
||||
jwtPayload?: JwtPayload
|
||||
}
|
||||
interface CreateNewAuthedSession {
|
||||
req: IncomingMessage
|
||||
res: ServerResponse
|
||||
headers: Headers
|
||||
publicData: PublicData
|
||||
privateData?: Record<any, any>
|
||||
anonymous: false
|
||||
@@ -816,7 +941,6 @@ interface CreateNewAuthedSession {
|
||||
async function createNewSession(
|
||||
args: CreateNewAnonSession | CreateNewAuthedSession,
|
||||
): Promise<SessionKernel> {
|
||||
const {req, res} = args
|
||||
assert(args.publicData.userId !== undefined, "You must provide publicData.userId")
|
||||
|
||||
const antiCSRFToken = createAntiCSRFToken()
|
||||
@@ -837,12 +961,12 @@ async function createNewSession(
|
||||
new Date(),
|
||||
global.sessionConfig.anonSessionExpiryMinutes as number,
|
||||
)
|
||||
setAnonymousSessionCookie(res, anonymousSessionToken, expiresAt)
|
||||
setCSRFCookie(req, res, antiCSRFToken, expiresAt)
|
||||
setPublicDataCookie(req, res, publicDataToken, expiresAt)
|
||||
setAnonymousSessionCookie(args.headers, anonymousSessionToken, expiresAt)
|
||||
setCSRFCookie(args.headers, antiCSRFToken, expiresAt)
|
||||
setPublicDataCookie(args.headers, publicDataToken, expiresAt)
|
||||
// Clear the essential session cookie in case it was previously set
|
||||
setSessionCookie(res, "", new Date(0))
|
||||
setHeader(res, HEADER_SESSION_CREATED, "true")
|
||||
setSessionCookie(args.headers, "", new Date(0))
|
||||
args.headers.set(HEADER_SESSION_CREATED, "true")
|
||||
|
||||
return {
|
||||
handle,
|
||||
@@ -893,12 +1017,13 @@ async function createNewSession(
|
||||
privateData: JSON.stringify(newPrivateData),
|
||||
})
|
||||
|
||||
setSessionCookie(res, sessionToken, expiresAt)
|
||||
setCSRFCookie(req, res, antiCSRFToken, expiresAt)
|
||||
setPublicDataCookie(req, res, publicDataToken, expiresAt)
|
||||
setSessionCookie(args.headers, sessionToken, expiresAt)
|
||||
debug("Session created", {handle, publicData: newPublicData, expiresAt})
|
||||
setCSRFCookie(args.headers, antiCSRFToken, expiresAt)
|
||||
setPublicDataCookie(args.headers, publicDataToken, expiresAt)
|
||||
// Clear the anonymous session cookie in case it was previously set
|
||||
setAnonymousSessionCookie(res, "", new Date(0))
|
||||
setHeader(res, HEADER_SESSION_CREATED, "true")
|
||||
setAnonymousSessionCookie(args.headers, "", new Date(0))
|
||||
args.headers.set(HEADER_SESSION_CREATED, "true")
|
||||
|
||||
return {
|
||||
handle,
|
||||
@@ -916,10 +1041,9 @@ async function createNewSession(
|
||||
}
|
||||
}
|
||||
|
||||
async function createAnonymousSession(req: IncomingMessage, res: ServerResponse) {
|
||||
async function createAnonymousSession({headers}: {headers: Headers}) {
|
||||
return await createNewSession({
|
||||
req,
|
||||
res,
|
||||
headers,
|
||||
publicData: {userId: null},
|
||||
anonymous: true,
|
||||
})
|
||||
@@ -930,8 +1054,7 @@ async function createAnonymousSession(req: IncomingMessage, res: ServerResponse)
|
||||
// --------------------------------
|
||||
|
||||
async function refreshSession(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
headers: Headers,
|
||||
sessionKernel: SessionKernel,
|
||||
{publicDataChanged}: {publicDataChanged: boolean},
|
||||
) {
|
||||
@@ -945,8 +1068,8 @@ async function refreshSession(
|
||||
const publicDataToken = createPublicDataToken(sessionKernel.publicData)
|
||||
|
||||
const expiresAt = addYears(new Date(), 30)
|
||||
setAnonymousSessionCookie(res, anonymousSessionToken, expiresAt)
|
||||
setPublicDataCookie(req, res, publicDataToken, expiresAt)
|
||||
setAnonymousSessionCookie(headers, anonymousSessionToken, expiresAt)
|
||||
setPublicDataCookie(headers, publicDataToken, expiresAt)
|
||||
} else if (global.sessionConfig.method === "essential" && "sessionToken" in sessionKernel) {
|
||||
const expiresAt = addMinutes(new Date(), global.sessionConfig.sessionExpiryMinutes as number)
|
||||
|
||||
@@ -954,7 +1077,7 @@ async function refreshSession(
|
||||
if (publicDataChanged) {
|
||||
debug("Public data has changed")
|
||||
const publicDataToken = createPublicDataToken(sessionKernel.publicData)
|
||||
setPublicDataCookie(req, res, publicDataToken, expiresAt)
|
||||
setPublicDataCookie(headers, publicDataToken, expiresAt)
|
||||
await global.sessionConfig.updateSession(sessionKernel.handle, {
|
||||
expiresAt,
|
||||
publicData: JSON.stringify(sessionKernel.publicData),
|
||||
@@ -996,12 +1119,7 @@ async function syncPubicDataFieldsForUserIfNeeded(
|
||||
}
|
||||
}
|
||||
|
||||
async function revokeSession(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
handle: string,
|
||||
anonymous: boolean = false,
|
||||
) {
|
||||
async function revokeSession(headers: Headers, handle: string, anonymous: boolean = false) {
|
||||
debug("Revoking session", handle)
|
||||
if (!anonymous) {
|
||||
try {
|
||||
@@ -1014,7 +1132,9 @@ async function revokeSession(
|
||||
// This fixes race condition where all client side queries get refreshed
|
||||
// in parallel and each creates a new anon session
|
||||
// https://github.com/blitz-js/blitz/issues/2746
|
||||
return createAnonymousSession(req, res)
|
||||
return createAnonymousSession({
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
async function revokeAllSessionsForUser(userId: PublicData["userId"]) {
|
||||
@@ -1080,8 +1200,7 @@ async function setPrivateData(sessionKernel: SessionKernel, data: Record<any, an
|
||||
}
|
||||
|
||||
async function setPublicData(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
headers: Headers,
|
||||
sessionKernel: SessionKernel,
|
||||
data: Record<any, any>,
|
||||
) {
|
||||
@@ -1093,7 +1212,7 @@ async function setPublicData(
|
||||
...data,
|
||||
} as PublicData
|
||||
|
||||
await refreshSession(req, res, {...sessionKernel, publicData}, {publicDataChanged: true})
|
||||
await refreshSession(headers, {...sessionKernel, publicData}, {publicDataChanged: true})
|
||||
return publicData
|
||||
}
|
||||
|
||||
@@ -1130,12 +1249,31 @@ export async function setPublicDataForUser(userId: PublicData["userId"], data: R
|
||||
* @param {string} field
|
||||
* @param {string| string[]} val
|
||||
*/
|
||||
function append(res: ServerResponse, field: string, val: string | string[]) {
|
||||
export function append(res: ServerResponse, field: string, val: string | string[]) {
|
||||
let prev: string | string[] | undefined = res.getHeader(field) as string | string[] | undefined
|
||||
let value = val
|
||||
|
||||
if (prev !== undefined) {
|
||||
// concat the new and prev vals
|
||||
if (field.toLowerCase() === "set-cookie") {
|
||||
const prevCookies = prev ? (Array.isArray(prev) ? prev : [prev]) : []
|
||||
const newCookies = Array.isArray(val) ? val : [val]
|
||||
|
||||
const allCookies = [...prevCookies, ...newCookies].reduce((acc: string[], cookieHeader) => {
|
||||
return acc.concat(splitCookiesString(cookieHeader))
|
||||
}, [])
|
||||
|
||||
const cookieMap = new Map()
|
||||
allCookies.forEach((cookieStr) => {
|
||||
const firstSemicolon = cookieStr.indexOf(";")
|
||||
const cookieNameValue = firstSemicolon > -1 ? cookieStr.slice(0, firstSemicolon) : cookieStr
|
||||
const parsed = cookie.parse(cookieNameValue)
|
||||
const name = Object.keys(parsed)[0]
|
||||
if (name) {
|
||||
cookieMap.set(name, cookieStr)
|
||||
}
|
||||
})
|
||||
|
||||
value = Array.from(cookieMap.values())
|
||||
} else if (prev !== undefined) {
|
||||
value = Array.isArray(prev)
|
||||
? prev.concat(val)
|
||||
: Array.isArray(val)
|
||||
@@ -1144,7 +1282,6 @@ function append(res: ServerResponse, field: string, val: string | string[]) {
|
||||
}
|
||||
|
||||
value = Array.isArray(value) ? value.map(String) : String(value)
|
||||
|
||||
res.setHeader(field, value)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ export {
|
||||
getSession,
|
||||
isLocalhost,
|
||||
setPublicDataForUser,
|
||||
setCookie,
|
||||
simpleRolesIsAuthorized,
|
||||
getBlitzContext,
|
||||
} from "./auth-sessions"
|
||||
export type {AnonymousSessionPayload, SimpleRolesIsAuthorized} from "./auth-sessions"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//@ts-nocheck
|
||||
import {Ctx} from "blitz"
|
||||
import type {Ctx} from "blitz"
|
||||
import type {ServerResponse} from "http"
|
||||
|
||||
export interface Session {
|
||||
// isAuthorize can be injected here
|
||||
@@ -66,6 +67,12 @@ export interface SessionContextBase {
|
||||
$getPrivateData: () => Promise<Record<any, any>>
|
||||
$setPrivateData: (data: Record<any, any>) => 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
|
||||
|
||||
@@ -1,5 +1,177 @@
|
||||
# @blitzjs/next
|
||||
|
||||
## 2.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @blitzjs/rpc@2.2.3
|
||||
- blitz@2.2.3
|
||||
|
||||
## 2.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @blitzjs/rpc@2.2.2
|
||||
- blitz@2.2.2
|
||||
|
||||
## 2.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- blitz@2.2.1
|
||||
- @blitzjs/rpc@2.2.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [318e9740d]
|
||||
- blitz@2.0.10
|
||||
- @blitzjs/rpc@2.0.10
|
||||
|
||||
## 2.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5a14306f7: fix export `enhancePrisma` in client
|
||||
- Updated dependencies [5a14306f7]
|
||||
- @blitzjs/rpc@2.0.9
|
||||
- blitz@2.0.9
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5e61a1681: bug: merge existing and custom blitz turbo configs
|
||||
- Updated dependencies [5e61a1681]
|
||||
- Updated dependencies [77555468f]
|
||||
- blitz@2.0.8
|
||||
- @blitzjs/rpc@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ee7bf87ec: Turbopack support for Blitz
|
||||
|
||||
This PR includes the changes required to make the Blitz loaders work with Turbopack.
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
pnpm blitz dev --turbo
|
||||
```
|
||||
|
||||
- Updated dependencies [ee7bf87ec]
|
||||
- Updated dependencies [178c152b2]
|
||||
- blitz@2.0.7
|
||||
- @blitzjs/rpc@2.0.7
|
||||
|
||||
## 2.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [76a2544f9]
|
||||
- blitz@2.0.6
|
||||
- @blitzjs/rpc@2.0.6
|
||||
|
||||
## 2.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@blitzjs/next",
|
||||
"version": "2.0.5",
|
||||
"version": "2.2.3",
|
||||
"homepage": "https://blitzjs.com/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -29,7 +29,7 @@
|
||||
"eslint.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@blitzjs/rpc": "2.0.5",
|
||||
"@blitzjs/rpc": "2.2.3",
|
||||
"@types/hoist-non-react-statics": "3.3.1",
|
||||
"copy-webpack-plugin": "11.0.0",
|
||||
"debug": "4.3.3",
|
||||
@@ -39,30 +39,29 @@
|
||||
"supports-color": "8.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"blitz": "2.0.5",
|
||||
"blitz": "2.2.3",
|
||||
"next": "*",
|
||||
"react": "*",
|
||||
"tslog": "4.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@testing-library/dom": "8.13.0",
|
||||
"@testing-library/jest-dom": "5.16.3",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@testing-library/react": "16.0.1",
|
||||
"@testing-library/user-event": "13.5.0",
|
||||
"@types/debug": "4.1.7",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||
"@types/testing-library__react-hooks": "4.0.0",
|
||||
"blitz": "2.0.5",
|
||||
"blitz": "2.2.3",
|
||||
"cross-spawn": "7.0.3",
|
||||
"find-up": "4.1.0",
|
||||
"next": "14.0.4",
|
||||
"next": "15.0.1",
|
||||
"next-router-mock": "0.9.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"ts-jest": "27.1.4",
|
||||
"tslog": "4.9.0",
|
||||
|
||||
@@ -170,6 +170,7 @@ function codegen() {
|
||||
const defaultIndexJsPath = path.join(dotBlitzDir, "index.js")
|
||||
const defaultIndexBrowserJSPath = path.join(dotBlitzDir, "index-browser.js")
|
||||
const defaultIndexDTSPath = path.join(dotBlitzDir, "index.d.ts")
|
||||
const emptyJSPath = path.join(dotBlitzDir, "turbopack-empty.js")
|
||||
|
||||
if (!fs.existsSync(defaultIndexJsPath)) {
|
||||
await copyFile(path.join(__dirname, "default-index.js"), defaultIndexJsPath)
|
||||
@@ -181,6 +182,10 @@ function codegen() {
|
||||
if (!fs.existsSync(defaultIndexDTSPath)) {
|
||||
await copyFile(path.join(__dirname, "default-index.d.ts"), defaultIndexDTSPath)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(emptyJSPath)) {
|
||||
await copyFile(path.join(__dirname, "turbopack-empty.js"), emptyJSPath)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
8
packages/blitz-next/scripts/turbopack-empty.js
Normal file
8
packages/blitz-next/scripts/turbopack-empty.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const exports = {
|
||||
"npm-which": {},
|
||||
"cross-spawn": {},
|
||||
fs: {},
|
||||
child_process: {},
|
||||
}
|
||||
|
||||
module.exports = exports
|
||||
@@ -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."
|
||||
// `)
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// 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."
|
||||
// `)
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// can recover
|
||||
|
||||
@@ -77,25 +77,10 @@ test("standard use-case", () => {
|
||||
const {unmount} = render(<App />)
|
||||
|
||||
userEvent.type(screen.getByRole("textbox", {name: /username/i}), "fail")
|
||||
|
||||
const [[actualError], [componentStack]] = consoleError.mock.calls
|
||||
expect(firstLine(actualError as string)).toMatchInlineSnapshot(
|
||||
`"Error: Uncaught [Error: 💥 CABOOM 💥]"`,
|
||||
)
|
||||
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)
|
||||
const calls = consoleError.mock.calls[0]
|
||||
//@ts-expect-error - it's a mock
|
||||
expect(calls[1]).toMatchInlineSnapshot("[Error: 💥 CABOOM 💥]")
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
expect(screen.getByRole("alert")).toMatchInlineSnapshot(`
|
||||
@@ -149,7 +134,7 @@ test("fallbackRender prop", () => {
|
||||
}
|
||||
|
||||
const {unmount} = render(<App />)
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// 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>
|
||||
</ErrorBoundary>,
|
||||
)
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
expect(screen.getByText(/oh no/i)).to.exist
|
||||
expect(screen.queryByText(/child/i)).to.not.exist
|
||||
@@ -183,27 +168,16 @@ test("withErrorBoundary HOC", () => {
|
||||
() => {
|
||||
throw new Error("💥 CABOOM 💥")
|
||||
},
|
||||
{FallbackComponent: ErrorFallback, onError: onErrorHandler},
|
||||
{
|
||||
FallbackComponent: ErrorFallback,
|
||||
onError: onErrorHandler,
|
||||
},
|
||||
)
|
||||
const {unmount} = render(<Boundary />)
|
||||
|
||||
const [[actualError], [componentStack]] = consoleError.mock.calls
|
||||
const firstLineOfError = firstLine(actualError as string)
|
||||
expect(firstLineOfError).toMatchInlineSnapshot(`"Error: Uncaught [Error: 💥 CABOOM 💥]"`)
|
||||
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)
|
||||
const calls = consoleError.mock.calls[0]
|
||||
//@ts-expect-error - it's a mock
|
||||
expect(calls[1]).toMatchInlineSnapshot("[Error: 💥 CABOOM 💥]")
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
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)
|
||||
expect(() => {
|
||||
const result = render(
|
||||
// @ts-expect-error we're testing the runtime check of missing props here
|
||||
<ErrorBoundary>
|
||||
<Bomb />
|
||||
</ErrorBoundary>,
|
||||
@@ -318,7 +291,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
|
||||
// blow it up
|
||||
userEvent.click(screen.getByText("toggle explode"))
|
||||
expect(screen.getByRole("alert")).to.exist
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// recover via try again button
|
||||
@@ -333,7 +306,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
|
||||
// blow it up again
|
||||
userEvent.click(screen.getByText("toggle explode"))
|
||||
expect(screen.getByRole("alert")).to.exist
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// recover via resetKeys change
|
||||
@@ -348,7 +321,7 @@ test("supports automatic reset of error boundary when resetKeys change", () => {
|
||||
// blow it up again
|
||||
userEvent.click(screen.getByText("toggle explode"))
|
||||
expect(screen.getByRole("alert")).to.exist
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// 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])
|
||||
handleResetKeysChange.mockClear()
|
||||
expect(screen.getByRole("alert")).to.exist
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// 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(screen.getByRole("alert")).to.exist
|
||||
handleResetKeysChange.mockClear()
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// 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
|
||||
expect(screen.queryByRole("alert", {})).to.exist
|
||||
expect(consoleError).toHaveBeenCalledTimes(3)
|
||||
expect(consoleError).toHaveBeenCalledTimes(1)
|
||||
consoleError.mockClear()
|
||||
|
||||
// recover via "toggle explode" button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {RedirectError} from "blitz"
|
||||
import {useRouter} from "next/compat/router"
|
||||
import type {NextRouter} from "next/router"
|
||||
import * as React from "react"
|
||||
import React from "react"
|
||||
import {RouterContext} from "./router-context"
|
||||
import _debug from "debug"
|
||||
import type {ExcludeRouterProps, WithRouterProps} from "next/dist/client/with-router"
|
||||
@@ -77,7 +77,7 @@ const initialState: ErrorBoundaryState = {error: null}
|
||||
function withRouter<P extends WithRouterProps>(
|
||||
ComposedComponent: React.ComponentType<P>,
|
||||
): React.ComponentType<ExcludeRouterProps<P>> {
|
||||
function WithRouterWrapper(props: any): JSX.Element {
|
||||
function WithRouterWrapper(props: any): React.JSX.Element {
|
||||
return <ComposedComponent router={useRouter()} {...props} />
|
||||
}
|
||||
|
||||
@@ -114,7 +114,13 @@ export const ErrorBoundary = withRouter(
|
||||
await this.props.router.push(error.url)
|
||||
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() {
|
||||
@@ -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>,
|
||||
errorBoundaryProps: ErrorBoundaryProps,
|
||||
): React.ComponentType<P> {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import React from "react"
|
||||
import {NextPageContext} from "next"
|
||||
import dynamic from "next/dynamic"
|
||||
const Head = dynamic(() => import("next/head").then((mod) => mod.default), {
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
})
|
||||
const Head = dynamic(
|
||||
() =>
|
||||
import("next/head").then((mod) => ({
|
||||
default: mod.default,
|
||||
})),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
},
|
||||
)
|
||||
|
||||
const statusCodes: {[code: number]: string} = {
|
||||
400: "Bad Request",
|
||||
|
||||
@@ -9,10 +9,16 @@ import type {Router} from "next/router"
|
||||
import {BlitzProvider} from "./provider"
|
||||
import dynamic from "next/dynamic"
|
||||
export {Routes} from ".blitz"
|
||||
const Head = dynamic(() => import("next/head").then((mod) => mod.default), {
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
})
|
||||
const Head = dynamic(
|
||||
() =>
|
||||
import("next/head").then((mod) => ({
|
||||
default: mod.default,
|
||||
})),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => null,
|
||||
},
|
||||
)
|
||||
|
||||
export {BlitzProvider} from "./provider"
|
||||
|
||||
@@ -55,7 +61,7 @@ type RedirectAuthenticatedToFnCtx = {
|
||||
}
|
||||
type RedirectAuthenticatedToFn = (args: RedirectAuthenticatedToFnCtx) => RedirectAuthenticatedTo
|
||||
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>}
|
||||
suppressFirstRenderFlicker?: boolean
|
||||
redirectAuthenticatedTo?: RedirectAuthenticatedTo | RedirectAuthenticatedToFn
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
getInfiniteQueryKey,
|
||||
getQueryKey,
|
||||
installWebpackConfig,
|
||||
installTurboConfig,
|
||||
InstallWebpackConfigOptions,
|
||||
ResolverPathOptions,
|
||||
DefaultOptions,
|
||||
@@ -242,6 +243,7 @@ export interface BlitzConfig extends NextConfig {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function withBlitz(nextConfig: BlitzConfig = {}): NextConfig {
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" &&
|
||||
@@ -272,26 +274,30 @@ export function withBlitz(nextConfig: BlitzConfig = {}): NextConfig {
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const sodiumNativePath = fs.realpathSync(path.join(require.resolve("sodium-native"), ".."))
|
||||
const dotNextDirectory = `${process.cwd()}/.next`
|
||||
config.plugins.push(
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
//dev
|
||||
from: `${sodiumNativePath}/prebuilds/`,
|
||||
to: `${dotNextDirectory}/server/vendor-chunks/prebuilds/`,
|
||||
},
|
||||
{
|
||||
//prod
|
||||
from: `${sodiumNativePath}/prebuilds/`,
|
||||
to: `${dotNextDirectory}/server/chunks/prebuilds/`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
} catch {}
|
||||
if (!process.env.TURBOPACK) {
|
||||
try {
|
||||
const sodiumNativePath = fs.realpathSync(
|
||||
path.join(require.resolve("sodium-native"), ".."),
|
||||
)
|
||||
const dotNextDirectory = `${process.cwd()}/.next`
|
||||
config.plugins.push(
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
//dev
|
||||
from: `${sodiumNativePath}/prebuilds/`,
|
||||
to: `${dotNextDirectory}/server/vendor-chunks/prebuilds/`,
|
||||
},
|
||||
{
|
||||
//prod
|
||||
from: `${sodiumNativePath}/prebuilds/`,
|
||||
to: `${dotNextDirectory}/server/chunks/prebuilds/`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (typeof nextConfig.webpack === "function") {
|
||||
return nextConfig.webpack(config, options)
|
||||
@@ -300,6 +306,26 @@ export function withBlitz(nextConfig: BlitzConfig = {}): NextConfig {
|
||||
},
|
||||
})
|
||||
|
||||
if (process.env.TURBOPACK) {
|
||||
const blitzTurboConfig = installTurboConfig()
|
||||
config.experimental = {
|
||||
...config.experimental,
|
||||
turbo: {
|
||||
loaders: {
|
||||
...config.experimental?.turbo?.loaders,
|
||||
},
|
||||
resolveAlias: {
|
||||
...config.experimental?.turbo?.resolveAlias,
|
||||
...blitzTurboConfig.resolveAlias,
|
||||
},
|
||||
rules: {
|
||||
...config.experimental?.turbo?.rules,
|
||||
...blitzTurboConfig.rules,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const {blitz, ...rest} = config
|
||||
return rest
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {QueryClient, HydrateOptions} from "@blitzjs/rpc"
|
||||
import React from "react"
|
||||
|
||||
export type BlitzProviderProps = {
|
||||
children: JSX.Element
|
||||
children: React.JSX.Element
|
||||
client?: QueryClient
|
||||
contextSharing?: boolean
|
||||
dehydratedState?: unknown
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React from "react"
|
||||
import {describe, it, expect, vi, afterEach} from "vitest"
|
||||
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 {RouterContext} from "./router-context"
|
||||
|
||||
|
||||
@@ -1,5 +1,162 @@
|
||||
# @blitzjs/rpc
|
||||
|
||||
## 2.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- blitz@2.2.3
|
||||
|
||||
## 2.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- blitz@2.2.2
|
||||
|
||||
## 2.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- blitz@2.2.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [318e9740d]
|
||||
- blitz@2.0.10
|
||||
|
||||
## 2.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5a14306f7: fix export `enhancePrisma` in client
|
||||
- Updated dependencies [5a14306f7]
|
||||
- blitz@2.0.9
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5e61a1681]
|
||||
- Updated dependencies [77555468f]
|
||||
- blitz@2.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ee7bf87ec: Turbopack support for Blitz
|
||||
|
||||
This PR includes the changes required to make the Blitz loaders work with Turbopack.
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
pnpm blitz dev --turbo
|
||||
```
|
||||
|
||||
- Updated dependencies [ee7bf87ec]
|
||||
- Updated dependencies [178c152b2]
|
||||
- blitz@2.0.7
|
||||
|
||||
## 2.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [76a2544f9]
|
||||
- blitz@2.0.6
|
||||
|
||||
## 2.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@blitzjs/rpc",
|
||||
"version": "2.0.5",
|
||||
"version": "2.2.3",
|
||||
"homepage": "https://blitzjs.com/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -37,25 +37,25 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/query-core": "4.24.4",
|
||||
"blitz": "2.0.5",
|
||||
"blitz": "2.2.3",
|
||||
"next": "*",
|
||||
"react": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitzjs/auth": "2.0.5",
|
||||
"@blitzjs/config": "2.0.5",
|
||||
"@blitzjs/auth": "2.2.3",
|
||||
"@blitzjs/config": "2.2.3",
|
||||
"@tanstack/query-core": "4.24.4",
|
||||
"@types/debug": "4.1.7",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"blitz": "2.0.5",
|
||||
"next": "14.0.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@types/react": "npm:types-react@19.0.0",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0",
|
||||
"blitz": "2.2.3",
|
||||
"next": "15.0.1",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"typescript": "^4.8.4",
|
||||
"unbuild": "0.7.6",
|
||||
"watch": "1.0.2",
|
||||
"zod": "3.20.2"
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {assert, Ctx, ResolverConfig} from "blitz"
|
||||
import {assert, Ctx, ResolverConfig, registerBlitzErrorClasses} from "blitz"
|
||||
import {NextApiRequest, NextApiResponse} from "next"
|
||||
import {resolve} from "path"
|
||||
import {deserialize, parse, serialize as superjsonSerialize} from "superjson"
|
||||
@@ -157,6 +157,51 @@ export function installWebpackConfig({
|
||||
})
|
||||
}
|
||||
|
||||
export function installTurboConfig() {
|
||||
return {
|
||||
resolveAlias: {
|
||||
"cross-spawn": {browser: ".blitz/turbopack-empty.js"},
|
||||
"npm-which": {browser: ".blitz/turbopack-empty.js"},
|
||||
fs: {browser: ".blitz/turbopack-empty.js"},
|
||||
child_process: {browser: ".blitz/turbopack-empty.js"},
|
||||
},
|
||||
rules: {
|
||||
"**/*...blitz*.{jsx,tsx,js,ts}": {
|
||||
default: {
|
||||
loaders: [{loader: loaderServer, options: {}}],
|
||||
as: "*.ts",
|
||||
},
|
||||
},
|
||||
"**/*...blitz*/route.{jsx,tsx,js,ts}": {
|
||||
default: {
|
||||
loaders: [{loader: loaderServer, options: {}}],
|
||||
as: "*.ts",
|
||||
},
|
||||
},
|
||||
"**/{queries,mutations}/**": {
|
||||
browser: {
|
||||
loaders: [
|
||||
{
|
||||
loader: loaderClient,
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
as: "*.ts",
|
||||
},
|
||||
default: {
|
||||
loaders: [
|
||||
{
|
||||
loader: loaderServerResolvers,
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
as: "*.ts",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ----------
|
||||
// END LOADER
|
||||
// ----------
|
||||
@@ -176,7 +221,7 @@ async function getResolverMap(): Promise<ResolverFiles | null | undefined> {
|
||||
// Handles:
|
||||
// - Vite
|
||||
// {
|
||||
// const {resolverFilesLoaded, viteProvider} = await loadTelefuncFilesWithVite(runContext)
|
||||
// const {resolverFilesLoaded, viteProvider} = await loadTelefuncFilesWithVite(run
|
||||
// if (resolverFilesLoaded) {
|
||||
// assertUsage(
|
||||
// Object.keys(resolverFilesLoaded).length > 0,
|
||||
@@ -188,12 +233,13 @@ async function getResolverMap(): Promise<ResolverFiles | null | undefined> {
|
||||
}
|
||||
|
||||
interface RpcConfig {
|
||||
onError?: (error: Error, ctx: Ctx) => void
|
||||
formatError?: (error: Error, ctx: Ctx) => Error
|
||||
onError?: (error: Error, ctx?: Ctx) => void
|
||||
formatError?: (error: Error, ctx?: Ctx) => Error
|
||||
logging?: RpcLoggerOptions
|
||||
}
|
||||
|
||||
export function rpcHandler(config: RpcConfig) {
|
||||
export function rpcHandler(config?: RpcConfig) {
|
||||
registerBlitzErrorClasses()
|
||||
return async function handleRpcRequest(req: NextApiRequest, res: NextApiResponse, ctx: Ctx) {
|
||||
const resolverMap = await getResolverMap()
|
||||
assert(resolverMap, "No query or mutation resolvers found")
|
||||
@@ -205,7 +251,7 @@ export function rpcHandler(config: RpcConfig) {
|
||||
const relativeRoutePath = (req.query.blitz as string[])?.join("/")
|
||||
const routePath = "/" + relativeRoutePath
|
||||
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
|
||||
if (!loadableResolver) {
|
||||
@@ -264,6 +310,7 @@ export function rpcHandler(config: RpcConfig) {
|
||||
|
||||
rpcLogger.timer.initNextJsSerialization()
|
||||
;(res as any).blitzResult = result
|
||||
ctx?.session?.setSession(res)
|
||||
res.json({
|
||||
result: serializedResult.json,
|
||||
error: null,
|
||||
@@ -283,16 +330,18 @@ export function rpcHandler(config: RpcConfig) {
|
||||
error.stack = ""
|
||||
}
|
||||
|
||||
config.onError?.(error, ctx)
|
||||
config?.onError?.(error, ctx)
|
||||
rpcLogger.error(error)
|
||||
|
||||
if (!error.statusCode) {
|
||||
error.statusCode = 500
|
||||
}
|
||||
|
||||
const formattedError = config.formatError?.(error, ctx) ?? error
|
||||
const formattedError = config?.formatError?.(error, ctx) ?? error
|
||||
const serializedError = superjsonSerialize(formattedError)
|
||||
|
||||
ctx?.session?.setSession(res)
|
||||
|
||||
res.json({
|
||||
result: null,
|
||||
error: serializedError.json,
|
||||
@@ -310,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {QueryClientProvider} from "@tanstack/react-query"
|
||||
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 [queryClient] = React.useState(globalThis.queryClient)
|
||||
|
||||
@@ -14,12 +14,24 @@ import {getResolverConfig} from "../../parsers/parse-rpc-config"
|
||||
// Subset of `import type { LoaderDefinitionFunction } from 'webpack'`
|
||||
|
||||
export async function loader(this: Loader, input: string): Promise<string> {
|
||||
const compiler = this._compiler!
|
||||
const id = this.resource
|
||||
const root = this._compiler!.context
|
||||
const root = this.rootContext
|
||||
|
||||
const isSSR = compiler.name === "server"
|
||||
if (!isSSR) {
|
||||
// Webpack has `_compiler` property. Turbopack does not.
|
||||
const webpackCompilerName = this._compiler?.name
|
||||
if (webpackCompilerName) {
|
||||
const isSSR = webpackCompilerName === "server"
|
||||
if (!isSSR) {
|
||||
return await transformBlitzRpcResolverClient(
|
||||
input,
|
||||
toPosixPath(id),
|
||||
toPosixPath(root),
|
||||
this.query,
|
||||
)
|
||||
}
|
||||
// Handle Turbopack / other bundlers case.
|
||||
// The decision of which environment to run the loader in is decided by the loader configuration instead.
|
||||
} else {
|
||||
return await transformBlitzRpcResolverClient(
|
||||
input,
|
||||
toPosixPath(id),
|
||||
|
||||
@@ -13,12 +13,24 @@ import {posix} from "path"
|
||||
// Subset of `import type { LoaderDefinitionFunction } from 'webpack'`
|
||||
|
||||
export async function loader(this: Loader, input: string): Promise<string> {
|
||||
const compiler = this._compiler!
|
||||
const id = this.resource
|
||||
const root = this._compiler!.context
|
||||
const root = this.rootContext
|
||||
|
||||
const isSSR = compiler.name === "server"
|
||||
if (isSSR) {
|
||||
// Webpack has `_compiler` property. Turbopack does not.
|
||||
const webpackCompilerName = this._compiler?.name
|
||||
if (webpackCompilerName) {
|
||||
const isSSR = webpackCompilerName === "server"
|
||||
if (isSSR) {
|
||||
return await transformBlitzRpcResolverServer(
|
||||
input,
|
||||
toPosixPath(id),
|
||||
toPosixPath(root),
|
||||
this.query,
|
||||
)
|
||||
}
|
||||
// Handle Turbopack / other bundlers case.
|
||||
// The decision of which environment to run the loader in is decided by the loader configuration instead.
|
||||
} else {
|
||||
return await transformBlitzRpcResolverServer(
|
||||
input,
|
||||
toPosixPath(id),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {join} from "path"
|
||||
import {join, relative} from "path"
|
||||
import {promises} from "fs"
|
||||
import {
|
||||
assertPosixPath,
|
||||
@@ -14,17 +14,37 @@ import {
|
||||
// Subset of `import type { LoaderDefinitionFunction } from 'webpack'`
|
||||
|
||||
export async function loader(this: Loader, input: string): Promise<string> {
|
||||
const compiler = this._compiler!
|
||||
const id = this.resource
|
||||
const root = this._compiler!.context
|
||||
const root = this.rootContext
|
||||
const rpcFolders = this.query.includeRPCFolders ? this.query.includeRPCFolders : []
|
||||
|
||||
const isSSR = compiler.name === "server"
|
||||
if (isSSR) {
|
||||
// Webpack has `_compiler` property. Turbopack does not.
|
||||
const webpackCompilerName = this._compiler?.name
|
||||
if (webpackCompilerName) {
|
||||
const isSSR = webpackCompilerName === "server"
|
||||
if (isSSR) {
|
||||
this.cacheable(false)
|
||||
|
||||
const resolvers = await collectResolvers(root, rpcFolders, ["ts", "js", "tsx", "jsx"])
|
||||
|
||||
return await transformBlitzRpcServer(
|
||||
this.context,
|
||||
input,
|
||||
toPosixPath(id),
|
||||
toPosixPath(root),
|
||||
resolvers,
|
||||
this.query,
|
||||
)
|
||||
}
|
||||
// Handle Turbopack / other bundlers case.
|
||||
// The decision of which environment to run the loader in is decided by the loader configuration instead.
|
||||
} else {
|
||||
this.cacheable(false)
|
||||
|
||||
const resolvers = await collectResolvers(root, rpcFolders, ["ts", "js", "tsx", "jsx"])
|
||||
|
||||
return await transformBlitzRpcServer(
|
||||
this.context,
|
||||
input,
|
||||
toPosixPath(id),
|
||||
toPosixPath(root),
|
||||
@@ -43,6 +63,7 @@ function slash(str: string) {
|
||||
}
|
||||
|
||||
export async function transformBlitzRpcServer(
|
||||
context: string,
|
||||
src: string,
|
||||
id: string,
|
||||
root: string,
|
||||
@@ -68,7 +89,9 @@ export async function transformBlitzRpcServer(
|
||||
|
||||
code += `__internal_addBlitzRpcResolver('${routePath}','${slash(
|
||||
resolverFilePath,
|
||||
)}',() => ${importStrategy}('${slash(resolverFilePath)}'));`
|
||||
)}',() => ${importStrategy}('${slash(
|
||||
relative(context, resolverFilePath).replace(/\\/g, "/"),
|
||||
)}'))`
|
||||
code += "\n"
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@ export interface LoaderOptions {
|
||||
}
|
||||
|
||||
export interface Loader {
|
||||
_compiler?: {
|
||||
_compiler: {
|
||||
name: string
|
||||
context: string
|
||||
}
|
||||
rootContext: string
|
||||
context: string
|
||||
resource: string
|
||||
cacheable: (enabled: boolean) => void
|
||||
query: LoaderOptions
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user