@auth/core
Version:
Authentication for the Web.
109 lines (108 loc) • 4.04 kB
JavaScript
import { parse as parseCookie, serialize } from "cookie";
import { UnknownAction } from "../errors.js";
async function getBody(req) {
if (!("body" in req) || !req.body || req.method !== "POST")
return;
const contentType = req.headers.get("content-type");
if (contentType?.includes("application/json")) {
return await req.json();
}
else if (contentType?.includes("application/x-www-form-urlencoded")) {
const params = new URLSearchParams(await req.text());
return Object.fromEntries(params);
}
}
const actions = [
"providers",
"session",
"csrf",
"signin",
"signout",
"callback",
"verify-request",
"error",
];
export async function toInternalRequest(req) {
try {
// TODO: url.toString() should not include action and providerId
// see init.ts
const url = new URL(req.url.replace(/\/$/, ""));
// FIXME: Upstream issue in Next.js, pathname segments get included as part of the query string
url.searchParams.delete("nextauth");
const pathname = url.pathname.replace(/\/$/, "");
const action = actions.find((a) => pathname.includes(a));
if (!action) {
throw new UnknownAction(`Cannot detect action in pathname (${pathname}).`);
}
if (req.method !== "GET" && req.method !== "POST") {
throw new UnknownAction("Only GET and POST requests are supported.");
}
const providerIdOrAction = pathname.split("/").pop();
let providerId;
if (providerIdOrAction &&
!action.includes(providerIdOrAction) &&
["signin", "callback"].includes(action)) {
providerId = providerIdOrAction;
}
return {
url,
action,
providerId,
method: req.method,
headers: Object.fromEntries(req.headers),
body: req.body ? await getBody(req) : undefined,
cookies: parseCookie(req.headers.get("cookie") ?? "") ?? {},
error: url.searchParams.get("error") ?? undefined,
query: Object.fromEntries(url.searchParams),
};
}
catch (e) {
return e;
}
}
export function toRequest(request) {
return new Request(request.url, {
headers: request.headers,
method: request.method,
body: request.method === "POST"
? JSON.stringify(request.body ?? {})
: undefined,
});
}
export function toResponse(res) {
const headers = new Headers(res.headers);
res.cookies?.forEach((cookie) => {
const { name, value, options } = cookie;
const cookieHeader = serialize(name, value, options);
if (headers.has("Set-Cookie"))
headers.append("Set-Cookie", cookieHeader);
else
headers.set("Set-Cookie", cookieHeader);
});
let body = res.body;
if (headers.get("content-type") === "application/json")
body = JSON.stringify(res.body);
else if (headers.get("content-type") === "application/x-www-form-urlencoded")
body = new URLSearchParams(res.body).toString();
const status = res.redirect ? 302 : res.status ?? 200;
const response = new Response(body, { headers, status });
if (res.redirect)
response.headers.set("Location", res.redirect);
return response;
}
/** Web compatible method to create a hash, using SHA256 */
export async function createHash(message) {
const data = new TextEncoder().encode(message);
const hash = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
.toString();
}
/** Web compatible method to create a random string of a given length */
export function randomString(size) {
const i2hex = (i) => ("0" + i.toString(16)).slice(-2);
const r = (a, i) => a + i2hex(i);
const bytes = crypto.getRandomValues(new Uint8Array(size));
return Array.from(bytes).reduce(r, "");
}