Compare commits

..

9 Commits

Author SHA1 Message Date
Balázs Orbán
6fdb0da6eb chore(release): bump package version(s) [skip ci] 2022-12-09 00:34:31 +01:00
Balázs Orbán
5c4a9a697d fix(core): handle Request -> Response regressions (#5991)
* fix(next): don't override `Content-Type` by `unstable_getServerSession`

* fix(core): handle `,` while setting `set-cookie`
2022-12-08 23:29:25 +00:00
Thomas Desmond
eddd8fd7f9 chore(docs): add new tutorial (#5604)
Co-authored-by: Nico Domino <yo@ndo.dev>
2022-12-08 18:24:58 +01:00
Balázs Orbán
b74bfc68e8 chore(release): bump package version(s) [skip ci] 2022-12-08 05:16:10 +01:00
Balázs Orbán
0a140cdf87 test(core): fix test 2022-12-08 05:11:37 +01:00
Balázs Orbán
157269e0fb fix(core): throw error if no action can be determined 2022-12-08 05:10:26 +01:00
Balázs Orbán
221bc8e99c fix(core): add protocol if missing 2022-12-08 04:54:42 +01:00
Balázs Orbán
f856363ac8 chore(release): bump package version(s) [skip ci] 2022-12-08 04:38:03 +01:00
Balázs Orbán
f3291025e6 fix(core): properly construct url (#5984) 2022-12-08 04:33:20 +01:00
6 changed files with 124 additions and 26 deletions

View File

@@ -46,6 +46,10 @@ title: Tutorials and Explainers
- Learn how to use Sign-In With Ethereum to authenticate your users with their existing Ethereum wallets - identifiers they personally control.
- Example application: [spruceid/siwe-next-auth-example](https://github.com/spruceid/siwe-next-auth-example)
#### [Next.js Authentication with Okta and NextAuth.js 4.0](https://thetombomb.com/posts/nextjs-nextauth-okta) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
- Learn how to perform authentication with an OIDC Application in Okta and NextAuth.js.
## Fullstack
#### [Build a FullStack App with Next.js, NextAuth.js, Supabase & Prisma](https://themodern.dev/courses/build-a-fullstack-app-with-nextjs-supabase-and-prisma-322389284337222224) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>

View File

@@ -14,6 +14,10 @@ If you did not find a guide or tutorial covering your use case, please [open an
- How to restrict access to pages and API routes.
- [Usage with class components](/tutorials/usage-with-class-components)
- How to use `useSession()` hook with class components.
- [Next.js Authentication with Okta and NextAuth.js 4.0](https://thetombomb.com/posts/nextjs-nextauth-okta)
- Learn how to perform authentication with an OIDC Application in Okta and NextAuth.js.
### Advanced

View File

@@ -1,6 +1,6 @@
{
"name": "next-auth",
"version": "4.18.1",
"version": "4.18.4",
"description": "Authentication for Next.js",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth.git",

View File

@@ -1,5 +1,5 @@
import { AuthHandler } from "../core"
import { getURL, getBody } from "../utils/node"
import { getURL, getBody, setHeaders } from "../utils/node"
import type {
GetServerSidePropsContext,
@@ -37,10 +37,7 @@ async function NextAuthHandler(
const { status, headers } = response
res.status(status)
for (const [key, val] of headers.entries()) {
const value = key === "set-cookie" ? val.split(",") : val
res.setHeader(key, value)
}
setHeaders(headers, res)
// If the request expects a return URL, send it as JSON
// instead of doing an actual redirect.
@@ -158,10 +155,11 @@ export async function unstable_getServerSession<
const { status = 200, headers } = response
for (const [key, val] of headers.entries()) {
const value = key === "set-cookie" ? val.split(",") : val
res.setHeader(key, value)
}
setHeaders(headers, res)
// This would otherwise break rendering
// with `getServerSideProps` that needs to always return HTML
res.removeHeader?.("Content-Type")
const data = await response.json()

View File

@@ -1,4 +1,4 @@
import type { IncomingMessage } from "http"
import type { IncomingMessage, ServerResponse } from "http"
import type { GetServerSidePropsContext, NextApiRequest } from "next"
export function setCookie(res, value: string) {
@@ -43,13 +43,104 @@ export function getURL(
}
if (!host) throw new TypeError("Invalid host")
if (!url) throw new TypeError("Invalid URL, cannot determine action")
return new URL(url ?? "", new URL(host))
if (host.startsWith("http://") || host.startsWith("https://")) {
return new URL(`${host}${url}`)
}
return new URL(`https://${host}${url}`)
} catch (error) {
return error as Error
}
}
/**
* Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
* that are within a single set-cookie field-value, such as in the Expires portion.
* This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
* Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
* Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
* Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
* @source https://github.com/nfriedly/set-cookie-parser/blob/3eab8b7d5d12c8ed87832532861c1a35520cf5b3/lib/set-cookie.js#L144
*/
function getSetCookies(cookiesString: string) {
if (typeof cookiesString !== "string") {
return []
}
const cookiesStrings: string[] = []
let pos = 0
let start
let ch
let lastComma: number
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 === ",") {
// ',' is a cookie separator if we have later first '=', not ';' or ','
lastComma = pos
pos += 1
skipWhitespace()
nextStart = pos
while (pos < cookiesString.length && notSpecialChar()) {
pos += 1
}
// currently special character
if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
// we found cookies separator
cookiesSeparatorFound = true
// pos is inside the next cookie, so back up and return it.
pos = nextStart
cookiesStrings.push(cookiesString.substring(start, lastComma))
start = pos
} else {
// in param ',' or param separator ';',
// we continue from that comma
pos = lastComma + 1
}
} else {
pos += 1
}
}
if (!cookiesSeparatorFound || pos >= cookiesString.length) {
cookiesStrings.push(cookiesString.substring(start, cookiesString.length))
}
}
return cookiesStrings
}
export function setHeaders(headers: Headers, res: ServerResponse) {
for (const [key, val] of headers.entries()) {
// See: https://github.com/whatwg/fetch/issues/973
const value = key === "set-cookie" ? getSetCookies(val) : val
res.setHeader(key, value)
}
}
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {

View File

@@ -1,21 +1,22 @@
import { MissingAPIRoute } from "../src/core/errors"
// import { MissingAPIRoute } from "../src/core/errors"
import { nodeHandler } from "./utils"
it("Missing req.url throws MISSING_NEXTAUTH_API_ROUTE_ERROR", async () => {
const { res, logger } = await nodeHandler()
const { res } = await nodeHandler()
expect(res.status).toBeCalledWith(500)
expect(logger.error).toBeCalledTimes(1)
expect(logger.error).toBeCalledWith(
"MISSING_NEXTAUTH_API_ROUTE_ERROR",
expect.any(MissingAPIRoute)
)
expect(res.setHeader).toBeCalledWith("content-type", "application/json")
const body = res.send.mock.calls[0][0]
expect(JSON.parse(body)).toEqual({
message:
"There is a problem with the server configuration. Check the server logs for more information.",
})
expect(res.status).toBeCalledWith(400)
// Moved to host detection in getUrl
// expect(logger.error).toBeCalledTimes(1)
// expect(logger.error).toBeCalledWith(
// "MISSING_NEXTAUTH_API_ROUTE_ERROR",
// expect.any(MissingAPIRoute)
// )
// expect(res.setHeader).toBeCalledWith("content-type", "application/json")
// const body = res.send.mock.calls[0][0]
// expect(JSON.parse(body)).toEqual({
// message:
// "There is a problem with the server configuration. Check the server logs for more information.",
// })
})
it("Missing host throws 400 in production", async () => {