Socket
Socket
Sign inDemoInstall

@auth/core

Package Overview
Dependencies
Maintainers
2
Versions
92
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@auth/core - npm Package Compare versions

Comparing version 0.2.5 to 0.3.0

lib/oauth/checks.d.ts

47

errors.d.ts

@@ -17,3 +17,41 @@ /**

}
/** @todo */
/**
* There was an error while trying to finish up authenticating the user.
* Depending on the type of provider, this could be for multiple reasons.
*
* :::tip
* Check out `[auth][details]` in the error message to know which provider failed.
* @example
* ```sh
* [auth][details]: { "provider": "github" }
* ```
* :::
*
* For an **OAuth provider**, possible causes are:
* - The user denied access to the application
* - There was an error parsing the OAuth Profile:
* Check out the provider's `profile` or `userinfo.request` method to make sure
* it correctly fetches the user's profile.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* For an **Email provider**, possible causes are:
* - The provided email/token combination was invalid/missing:
* Check if the provider's `sendVerificationRequest` method correctly sends the email.
* - The provided email/token combination has expired:
* Ask the user to log in again.
* - There was an error with the database:
* Check the database logs.
*
* For a **Credentials provider**, possible causes are:
* - The `authorize` method threw an uncaught error:
* Check the provider's `authorize` method.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* :::tip
* Check out `[auth][cause]` in the error message for more details.
* It will show the original stack trace.
* :::
*/
export declare class CallbackRouteError extends AuthError {

@@ -84,2 +122,9 @@ }

}
/**
* The user's email/token combination was invalid.
* This could be because the email/token combination was not found in the database,
* or because it token has expired. Ask the user to log in again.
*/
export declare class Verification extends AuthError {
}
//# sourceMappingURL=errors.d.ts.map
/** @internal */
export class AuthError extends Error {
constructor(message, metadata) {
constructor(message, cause) {
if (message instanceof Error) {
super(message.message);
this.stack = message.stack;
super(undefined, {
cause: { err: message, ...message.cause, ...cause },
});
}
else
super(message);
this.name = this.constructor.name;
this.metadata = metadata;
else if (typeof message === "string") {
if (cause instanceof Error) {
cause = { err: cause, ...cause.cause };
}
super(message, cause);
}
else {
super(undefined, message);
}
Error.captureStackTrace?.(this, this.constructor);
this.name =
message instanceof AuthError ? message.name : this.constructor.name;
}

@@ -31,3 +39,41 @@ }

}
/** @todo */
/**
* There was an error while trying to finish up authenticating the user.
* Depending on the type of provider, this could be for multiple reasons.
*
* :::tip
* Check out `[auth][details]` in the error message to know which provider failed.
* @example
* ```sh
* [auth][details]: { "provider": "github" }
* ```
* :::
*
* For an **OAuth provider**, possible causes are:
* - The user denied access to the application
* - There was an error parsing the OAuth Profile:
* Check out the provider's `profile` or `userinfo.request` method to make sure
* it correctly fetches the user's profile.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* For an **Email provider**, possible causes are:
* - The provided email/token combination was invalid/missing:
* Check if the provider's `sendVerificationRequest` method correctly sends the email.
* - The provided email/token combination has expired:
* Ask the user to log in again.
* - There was an error with the database:
* Check the database logs.
*
* For a **Credentials provider**, possible causes are:
* - The `authorize` method threw an uncaught error:
* Check the provider's `authorize` method.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* :::tip
* Check out `[auth][cause]` in the error message for more details.
* It will show the original stack trace.
* :::
*/
export class CallbackRouteError extends AuthError {

@@ -98,1 +144,8 @@ }

}
/**
* The user's email/token combination was invalid.
* This could be because the email/token combination was not found in the database,
* or because it token has expired. Ask the user to log in again.
*/
export class Verification extends AuthError {
}

@@ -49,3 +49,3 @@ /**

* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, {
* const response = await AuthHandler(request, {
* providers: [...],

@@ -69,3 +69,3 @@ * secret: "...",

* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, authConfig)
* const response = await AuthHandler(request, authConfig)
* ```

@@ -209,3 +209,14 @@ *

trustHost?: boolean;
skipCSRFCheck?: typeof skipCSRFCheck;
}
/**
* :::danger
* This option is inteded for framework authors.
* :::
*
* Auth.js comes with built-in {@link https://authjs.dev/concepts/security#csrf CSRF} protection, but
* if you are implementing a framework that is already protected against CSRF attacks, you can skip this check by
* passing this value to {@link AuthConfig.skipCSRFCheck}.
*/
export declare const skipCSRFCheck: unique symbol;
//# sourceMappingURL=index.d.ts.map

@@ -50,3 +50,3 @@ /**

* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, {
* const response = await AuthHandler(request, {
* providers: [...],

@@ -110,1 +110,11 @@ * secret: "...",

}
/**
* :::danger
* This option is inteded for framework authors.
* :::
*
* Auth.js comes with built-in {@link https://authjs.dev/concepts/security#csrf CSRF} protection, but
* if you are implementing a framework that is already protected against CSRF attacks, you can skip this check by
* passing this value to {@link AuthConfig.skipCSRFCheck}.
*/
export const skipCSRFCheck = Symbol("skip-csrf-check");

2

lib/assert.js

@@ -20,3 +20,3 @@ import { defaultCookies } from "./cookie.js";

if (!warned && options.debug)
warnings.push("debug_enabled");
warnings.push("debug-enabled");
if (!options.trustHost) {

@@ -23,0 +23,0 @@ return new UntrustedHost(`Host must be trusted. URL was: ${request.url}`);

@@ -101,3 +101,3 @@ import { AccountNotLinked } from "../errors.js";

// and need to return an error.
throw new AccountNotLinked("The account is already associated with another user");
throw new AccountNotLinked("The account is already associated with another user", { provider: account.provider });
}

@@ -159,3 +159,3 @@ // If there is no active session, but the account being signed in with is already

// to sign in via email to verify their identity and then link the accounts.
throw new AccountNotLinked("Another account already exists with the same e-mail address");
throw new AccountNotLinked("Another account already exists with the same e-mail address", { provider: account.provider });
}

@@ -162,0 +162,0 @@ }

@@ -1,3 +0,3 @@

import type { RequestInternal, ResponseInternal, AuthConfig } from "../types.js";
import type { AuthConfig, RequestInternal, ResponseInternal } from "../types.js";
export declare function AuthInternal<Body extends string | Record<string, any> | any[]>(request: RequestInternal, authOptions: AuthConfig): Promise<ResponseInternal<Body>>;
//# sourceMappingURL=index.d.ts.map

@@ -0,3 +1,4 @@

import { UnknownAction } from "../errors.js";
import { skipCSRFCheck } from "../index.js";
import { SessionStore } from "./cookie.js";
import { UnknownAction } from "../errors.js";
import { init } from "./init.js";

@@ -8,2 +9,3 @@ import renderPage from "./pages/index.js";

const { action, providerId, error, method } = request;
const csrfDisabled = authOptions.skipCSRFCheck === skipCSRFCheck;
const { options, cookies } = await init({

@@ -18,2 +20,3 @@ authOptions,

isPost: method === "POST",
csrfDisabled,
});

@@ -34,3 +37,12 @@ const sessionStore = new SessionStore(options.cookies.sessionToken, request, options.logger);

}
case "csrf":
case "csrf": {
if (csrfDisabled) {
options.logger.warn("csrf-disabled");
cookies.push({
name: options.cookies.csrfToken.name,
value: "",
options: { ...options.cookies.csrfToken.options, maxAge: 0 },
});
return { status: 404, cookies };
}
return {

@@ -41,2 +53,3 @@ headers: { "Content-Type": "application/json" },

};
}
case "signin":

@@ -106,4 +119,3 @@ if (pages.signIn) {

case "signin":
// Verified CSRF Token required for all sign in routes
if (options.csrfTokenVerified && options.provider) {
if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
const signin = await routes.signin(request.query, request.body, options);

@@ -116,4 +128,3 @@ if (signin.cookies)

case "signout":
// Verified CSRF Token required for signout
if (options.csrfTokenVerified) {
if (csrfDisabled || options.csrfTokenVerified) {
const signout = await routes.signout(sessionStore, options);

@@ -129,2 +140,3 @@ if (signout.cookies)

if (options.provider.type === "credentials" &&
!csrfDisabled &&
!options.csrfTokenVerified) {

@@ -131,0 +143,0 @@ return { redirect: `${options.url}/signin?csrf=true`, cookies };

@@ -13,2 +13,3 @@ import * as cookie from "./cookie.js";

/** Is the incoming request a POST request? */
csrfDisabled: boolean;
isPost: boolean;

@@ -18,3 +19,3 @@ cookies: RequestInternal["cookies"];

/** Initialize all internal options and cookies. */
export declare function init({ authOptions, providerId, action, url: reqUrl, cookies: reqCookies, callbackUrl: reqCallbackUrl, csrfToken: reqCsrfToken, isPost, }: InitParams): Promise<{
export declare function init({ authOptions, providerId, action, url: reqUrl, cookies: reqCookies, callbackUrl: reqCallbackUrl, csrfToken: reqCsrfToken, csrfDisabled, isPost, }: InitParams): Promise<{
options: InternalOptions;

@@ -21,0 +22,0 @@ cookies: cookie.Cookie[];

@@ -11,3 +11,3 @@ import * as jwt from "../jwt.js";

/** Initialize all internal options and cookies. */
export async function init({ authOptions, providerId, action, url: reqUrl, cookies: reqCookies, callbackUrl: reqCallbackUrl, csrfToken: reqCsrfToken, isPost, }) {
export async function init({ authOptions, providerId, action, url: reqUrl, cookies: reqCookies, callbackUrl: reqCallbackUrl, csrfToken: reqCsrfToken, csrfDisabled, isPost, }) {
// TODO: move this to web.ts

@@ -54,3 +54,3 @@ const parsed = parseUrl(reqUrl.origin +

updateAge: 24 * 60 * 60,
generateSessionToken: crypto.randomUUID,
generateSessionToken: () => crypto.randomUUID(),
...authOptions.session,

@@ -78,16 +78,18 @@ },

const cookies = [];
const { csrfToken, cookie: csrfCookie, csrfTokenVerified, } = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
});
options.csrfToken = csrfToken;
options.csrfTokenVerified = csrfTokenVerified;
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
if (!csrfDisabled) {
const { csrfToken, cookie: csrfCookie, csrfTokenVerified, } = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
});
options.csrfToken = csrfToken;
options.csrfTokenVerified = csrfTokenVerified;
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
});
}
}

@@ -94,0 +96,0 @@ const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({

@@ -1,3 +0,2 @@

import type { CookiesOptions, InternalOptions, RequestInternal, ResponseInternal } from "../../types.js";
import type { Cookie } from "../cookie.js";
import type { InternalOptions, RequestInternal, ResponseInternal } from "../../types.js";
/**

@@ -9,4 +8,2 @@ * Generates an authorization/request token URL.

export declare function getAuthorizationUrl(query: RequestInternal["query"], options: InternalOptions<"oauth">): Promise<ResponseInternal>;
/** Returns a signed cookie. */
export declare function signCookie(type: keyof CookiesOptions, value: string, maxAge: number, options: InternalOptions<"oauth">): Promise<Cookie>;
//# sourceMappingURL=authorization-url.d.ts.map

@@ -0,1 +1,2 @@

import * as checks from "./checks.js";
import * as o from "oauth4webapi";

@@ -36,6 +37,6 @@ /**

const cookies = [];
if (provider.checks?.includes("state")) {
const { value, raw } = await createState(options);
authParams.set("state", raw);
cookies.push(value);
const state = await checks.state.create(options);
if (state) {
authParams.set("state", state.value);
cookies.push(state.cookie);
}

@@ -49,12 +50,12 @@ if (provider.checks?.includes("pkce")) {

else {
const { code_challenge, pkce } = await createPKCE(options);
authParams.set("code_challenge", code_challenge);
const { value, cookie } = await checks.pkce.create(options);
authParams.set("code_challenge", value);
authParams.set("code_challenge_method", "S256");
cookies.push(pkce);
cookies.push(cookie);
}
}
if (provider.checks?.includes("nonce")) {
const nonce = await createNonce(options);
const nonce = await checks.nonce.create(options);
if (nonce) {
authParams.set("nonce", nonce.value);
cookies.push(nonce);
cookies.push(nonce.cookie);
}

@@ -69,34 +70,1 @@ // TODO: This does not work in normalizeOAuth because authorization endpoint can come from discovery

}
/** Returns a signed cookie. */
export async function signCookie(type, value, maxAge, options) {
const { cookies, jwt, logger } = options;
logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge });
const expires = new Date();
expires.setTime(expires.getTime() + maxAge * 1000);
return {
name: cookies[type].name,
value: await jwt.encode({ ...jwt, maxAge, token: { value } }),
options: { ...cookies[type].options, expires },
};
}
const STATE_MAX_AGE = 60 * 15; // 15 minutes in seconds
async function createState(options) {
const raw = o.generateRandomState();
const maxAge = STATE_MAX_AGE;
const value = await signCookie("state", raw, maxAge, options);
return { value, raw };
}
const PKCE_MAX_AGE = 60 * 15; // 15 minutes in seconds
async function createPKCE(options) {
const code_verifier = o.generateRandomCodeVerifier();
const code_challenge = await o.calculatePKCECodeChallenge(code_verifier);
const maxAge = PKCE_MAX_AGE;
const pkce = await signCookie("pkceCodeVerifier", code_verifier, maxAge, options);
return { code_challenge, pkce };
}
const NONCE_MAX_AGE = 60 * 15; // 15 minutes in seconds
async function createNonce(options) {
const raw = o.generateRandomNonce();
const maxAge = NONCE_MAX_AGE;
return await signCookie("nonce", raw, maxAge, options);
}

@@ -0,6 +1,4 @@

import * as checks from "./checks.js";
import * as o from "oauth4webapi";
import { OAuthCallbackError, OAuthProfileParseError } from "../../errors.js";
import { useNonce } from "./nonce-handler.js";
import { usePKCECodeVerifier } from "./pkce-handler.js";
import { useState } from "./state-handler.js";
/**

@@ -46,3 +44,3 @@ * Handles the following OAuth steps.

const resCookies = [];
const state = await useState(cookies, resCookies, options);
const state = await checks.state.use(cookies, resCookies, options);
const parameters = o.validateAuthResponse(as, client, new URLSearchParams(query), provider.checks.includes("state") ? state : o.skipStateCheck);

@@ -57,12 +55,17 @@ /** https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1 */

}
const codeVerifier = await usePKCECodeVerifier(cookies?.[options.cookies.pkceCodeVerifier.name], options);
const codeVerifier = await checks.pkce.use(cookies?.[options.cookies.pkceCodeVerifier.name], options);
if (codeVerifier)
resCookies.push(codeVerifier.cookie);
// TODO:
const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options);
const nonce = await checks.nonce.use(cookies?.[options.cookies.nonce.name], options);
if (nonce && provider.type === "oidc") {
resCookies.push(nonce.cookie);
}
const codeGrantResponse = await o.authorizationCodeGrantRequest(as, client, parameters, provider.callbackUrl, codeVerifier?.codeVerifier ?? "auth" // TODO: review fallback code verifier
let codeGrantResponse = await o.authorizationCodeGrantRequest(as, client, parameters, provider.callbackUrl, codeVerifier?.codeVerifier ?? "auth" // TODO: review fallback code verifier
);
if (provider.token?.conform) {
codeGrantResponse =
(await provider.token.conform(codeGrantResponse.clone())) ??
codeGrantResponse;
}
let challenges;

@@ -69,0 +72,0 @@ if ((challenges = o.parseWwwAuthenticateChallenges(codeGrantResponse))) {

@@ -38,4 +38,4 @@ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";

`,
} })), theme?.logo && _jsx("img", { src: theme.logo, alt: "Logo", className: "logo" }), _jsxs("div", { className: "card", children: [_jsx("h1", { children: heading }), _jsx("div", { className: "message", children: message }), signin] })] })),
} })), _jsxs("div", { className: "card", children: [theme?.logo && _jsx("img", { src: theme?.logo, alt: "Logo", className: "logo" }), _jsx("h1", { children: heading }), _jsx("div", { className: "message", children: message }), signin] })] })),
};
}

@@ -20,10 +20,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";

}
if (typeof document !== "undefined" && theme.buttonText) {
document.documentElement.style.setProperty("--button-text-color", theme.buttonText);
}
const error = errorType &&
(signinErrors[errorType.toLowerCase()] ??
signinErrors.default);
// TODO: move logos
const logos = "https://raw.githubusercontent.com/nextauthjs/next-auth/main/packages/next-auth/provider-logos";
const logos = "https://authjs.dev/img/providers";
return (_jsxs("div", { className: "signin", children: [theme.brandColor && (_jsx("style", { dangerouslySetInnerHTML: {
__html: `:root {--brand-color: ${theme.brandColor}}`,
} })), theme.logo && _jsx("img", { src: theme.logo, alt: "Logo", className: "logo" }), _jsxs("div", { className: "card", children: [error && (_jsx("div", { className: "error", children: _jsx("p", { children: error }) })), providers.map((provider, i) => (_jsxs("div", { className: "provider", children: [provider.type === "oauth" || provider.type === "oidc" ? (_jsxs("form", { action: provider.signinUrl, method: "POST", children: [_jsx("input", { type: "hidden", name: "csrfToken", value: csrfToken }), callbackUrl && (_jsx("input", { type: "hidden", name: "callbackUrl", value: callbackUrl })), _jsxs("button", { type: "submit", className: "button", style: {
} })), theme.buttonText && (_jsx("style", { dangerouslySetInnerHTML: {
__html: `
:root {
--button-text-color: ${theme.buttonText}
}
`,
} })), _jsxs("div", { className: "card", children: [error && (_jsx("div", { className: "error", children: _jsx("p", { children: error }) })), providers.map((provider, i) => (_jsxs("div", { className: "provider", children: [provider.type === "oauth" || provider.type === "oidc" ? (_jsxs("form", { action: provider.signinUrl, method: "POST", children: [_jsx("input", { type: "hidden", name: "csrfToken", value: csrfToken }), callbackUrl && (_jsx("input", { type: "hidden", name: "callbackUrl", value: callbackUrl })), _jsxs("button", { type: "submit", className: "button", style: {
"--provider-bg": provider.style?.bg ?? "",

@@ -33,3 +41,4 @@ "--provider-dark-bg": provider.style?.bgDark ?? "",

"--provider-dark-color": provider.style?.textDark ?? "",
}, children: [provider.style?.logo && (_jsx("img", { id: "provider-logo", src: `${provider.style.logo.startsWith("/") ? logos : ""}${provider.style.logo}` })), provider.style?.logoDark && (_jsx("img", { id: "provider-logo-dark", src: `${provider.style.logo.startsWith("/") ? logos : ""}${provider.style.logoDark}` })), _jsxs("span", { children: ["Sign in with ", provider.name] })] })] })) : null, (provider.type === "email" || provider.type === "credentials") &&
gap: 8,
}, children: [provider.style?.logo && (_jsx("img", { loading: "lazy", height: 24, width: 24, id: "provider-logo", src: `${provider.style.logo.startsWith("/") ? logos : ""}${provider.style.logo}` })), provider.style?.logoDark && (_jsx("img", { loading: "lazy", height: 24, width: 24, id: "provider-logo-dark", src: `${provider.style.logo.startsWith("/") ? logos : ""}${provider.style.logoDark}` })), _jsxs("span", { children: ["Sign in with ", provider.name] })] })] })) : null, (provider.type === "email" || provider.type === "credentials") &&
i > 0 &&

@@ -39,4 +48,4 @@ providers[i - 1].type !== "email" &&

return (_jsxs("div", { children: [_jsx("label", { className: "section-header", htmlFor: `input-${credential}-for-${provider.id}-provider`, children: provider.credentials[credential].label ?? credential }), _jsx("input", { name: credential, id: `input-${credential}-for-${provider.id}-provider`, type: provider.credentials[credential].type ?? "text", placeholder: provider.credentials[credential].placeholder ?? "", ...provider.credentials[credential] })] }, `input-group-${provider.id}`));
}), _jsxs("button", { type: "submit", children: ["Sign in with ", provider.name] })] })), (provider.type === "email" || provider.type === "credentials") &&
}), _jsxs("button", { id: "submitButton", type: "submit", children: ["Sign in with ", provider.name] })] })), (provider.type === "email" || provider.type === "credentials") &&
i + 1 < providers.length && _jsx("hr", {})] }, provider.id)))] })] }));
}

@@ -10,3 +10,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";

`,
} })), theme.logo && _jsx("img", { src: theme.logo, alt: "Logo", className: "logo" }), _jsxs("div", { className: "card", children: [_jsx("h1", { children: "Signout" }), _jsx("p", { children: "Are you sure you want to sign out?" }), _jsxs("form", { action: `${url}/signout`, method: "POST", children: [_jsx("input", { type: "hidden", name: "csrfToken", value: csrfToken }), _jsx("button", { type: "submit", children: "Sign out" })] })] })] }));
} })), theme.buttonText && (_jsx("style", { dangerouslySetInnerHTML: {
__html: `
:root {
--button-text-color: ${theme.buttonText}
}
`,
} })), _jsxs("div", { className: "card", children: [theme.logo && _jsx("img", { src: theme.logo, alt: "Logo", className: "logo" }), _jsx("h1", { children: "Signout" }), _jsx("p", { children: "Are you sure you want to sign out?" }), _jsxs("form", { action: `${url}/signout`, method: "POST", children: [_jsx("input", { type: "hidden", name: "csrfToken", value: csrfToken }), _jsx("button", { id: "submitButton", type: "submit", children: "Sign out" })] })] })] }));
}

@@ -1,3 +0,3 @@

declare const _default: ":root {\n --border-width: 1px;\n --border-radius: 0.5rem;\n --color-error: #c94b4b;\n --color-info: #157efb;\n --color-info-text: #fff;\n}\n\n.__next-auth-theme-auto,\n.__next-auth-theme-light {\n --color-background: #fff;\n --color-text: #000;\n --color-primary: #444;\n --color-control-border: #bbb;\n --color-button-active-background: #f9f9f9;\n --color-button-active-border: #aaa;\n --color-seperator: #ccc;\n}\n\n.__next-auth-theme-dark {\n --color-background: #000;\n --color-text: #fff;\n --color-primary: #ccc;\n --color-control-border: #555;\n --color-button-active-background: #060606;\n --color-button-active-border: #666;\n --color-seperator: #444;\n}\n\n@media (prefers-color-scheme: dark) {\n .__next-auth-theme-auto {\n --color-background: #000;\n --color-text: #fff;\n --color-primary: #ccc;\n --color-control-border: #555;\n --color-button-active-background: #060606;\n --color-button-active-border: #666;\n --color-seperator: #444;\n }\n}\n\nbody {\n background-color: var(--color-background);\n margin: 0;\n padding: 0;\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif,\n \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n}\n\nh1 {\n font-weight: 400;\n margin-bottom: 1.5rem;\n padding: 0 1rem;\n color: var(--color-text);\n}\n\np {\n color: var(--color-text);\n}\n\nform {\n margin: 0;\n padding: 0;\n}\n\nlabel {\n font-weight: 500;\n text-align: left;\n margin-bottom: 0.25rem;\n display: block;\n color: var(--color-text);\n}\n\ninput[type] {\n box-sizing: border-box;\n display: block;\n width: 100%;\n padding: 0.5rem 1rem;\n border: var(--border-width) solid var(--color-control-border);\n background: var(--color-background);\n font-size: 1rem;\n border-radius: var(--border-radius);\n box-shadow: inset 0 0.1rem 0.2rem rgba(0, 0, 0, 0.2);\n color: var(--color-text);\n}\n\ninput[type]:focus {\n box-shadow: none;\n }\n\np {\n margin: 0 0 1.5rem 0;\n padding: 0 1rem;\n font-size: 1.1rem;\n line-height: 2rem;\n}\n\na.button {\n text-decoration: none;\n line-height: 1rem;\n}\n\na.button:link,\n a.button:visited {\n background-color: var(--color-background);\n color: var(--color-primary);\n }\n\nbutton,\na.button {\n margin: 0 0 0.75rem 0;\n padding: 0.75rem 1rem;\n color: var(--provider-color, var(--color-primary));\n background-color: var(--provider-bg, var(--color-background));\n font-size: 1.1rem;\n min-height: 62px;\n border-color: rgba(0, 0, 0, 0.1);\n border-radius: var(--border-radius);\n transition: all 0.1s ease-in-out;\n box-shadow: #000 0px 0px 0px 0px, #000 0px 0px 0px 0px,\n rgba(0, 0, 0, 0.2) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px;\n font-weight: 500;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\nbutton:has(img), a.button:has(img) {\n justify-content: unset;\n }\n\nbutton:has(img) span, a.button:has(img) span {\n flex-grow: 1;\n }\n\nbutton:hover, a.button:hover {\n cursor: pointer;\n }\n\nbutton:active, a.button:active {\n box-shadow: 0 0.15rem 0.3rem rgba(0, 0, 0, 0.15),\n inset 0 0.1rem 0.2rem var(--color-background),\n inset 0 -0.1rem 0.1rem rgba(0, 0, 0, 0.1);\n cursor: pointer;\n }\n\nbutton #provider-logo, a.button #provider-logo {\n display: block;\n }\n\nbutton #provider-logo-dark, a.button #provider-logo-dark {\n display: none;\n }\n\n@media (prefers-color-scheme: dark) {\n button,\n a.button {\n color: var(--provider-dark-color, var(--color-primary));\n background-color: var(--provider-dark-bg, var(--color-background));\n border: 1px solid #0d0d0d;\n box-shadow: #000 0px 0px 0px 0px, #ccc 0px 0px 0px 0px,\n rgba(255, 255, 255, 0.01) 0px 5px 5px -3px,\n rgba(255, 255, 255, 0.05) 0px 4px 6px -4px;\n }\n #provider-logo {\n display: none !important;\n }\n #provider-logo-dark {\n display: block !important;\n }\n}\n\na.site {\n color: var(--color-primary);\n text-decoration: none;\n font-size: 1rem;\n line-height: 2rem;\n}\n\na.site:hover {\n text-decoration: underline;\n }\n\n.page {\n position: absolute;\n width: 100%;\n height: 100%;\n display: grid;\n place-items: center;\n margin: 0;\n padding: 0;\n}\n\n.page > div {\n text-align: center;\n padding: 0.5rem;\n }\n\n.error a.button {\n display: inline-block;\n padding-left: 2rem;\n padding-right: 2rem;\n margin-top: 0.5rem;\n }\n\n.error .message {\n margin-bottom: 1.5rem;\n }\n\n.signin input[type=\"text\"] {\n margin-left: auto;\n margin-right: auto;\n display: block;\n }\n\n.signin hr {\n display: block;\n border: 0;\n border-top: 1px solid var(--color-seperator);\n margin: 1.5em auto 0 auto;\n overflow: visible;\n }\n\n.signin hr::before {\n content: \"or\";\n background: var(--color-background);\n color: #888;\n padding: 0 0.4rem;\n position: relative;\n top: -0.6rem;\n }\n\n.signin .error {\n background: #f5f5f5;\n font-weight: 500;\n border-radius: 0.3rem;\n background: var(--color-info);\n }\n\n.signin .error p {\n text-align: left;\n padding: 0.5rem 1rem;\n font-size: 0.9rem;\n line-height: 1.2rem;\n color: var(--color-info-text);\n }\n\n.signin > div,\n .signin form {\n display: block;\n }\n\n.signin > div input[type], .signin form input[type] {\n margin-bottom: 0.5rem;\n }\n\n.signin > div button, .signin form button {\n width: 100%;\n }\n\n.signin > div,\n .signin form {\n\n max-width: 300px;\n}\n.signout .message {\n margin-bottom: 1.5rem;\n }\n\n.logo {\n display: inline-block;\n margin-top: 100px;\n max-width: 300px;\n max-height: 150px;\n}\n\n.card {\n max-width: -moz-max-content;\n max-width: max-content;\n border: 1px solid var(--color-control-border);\n border-radius: 5px;\n padding: 20px 50px;\n margin: 50px auto;\n}\n\n.card .header {\n color: var(--color-primary);\n }\n\n.section-header {\n color: var(--brand-color, var(--color-text));\n}\n";
declare const _default: ":root {\n --border-width: 1px;\n --border-radius: 0.5rem;\n --color-error: #c94b4b;\n --color-info: #157efb;\n --color-info-text: #fff;\n}\n\n.__next-auth-theme-auto,\n.__next-auth-theme-light {\n --color-background: #ececec;\n --color-background-card: #fff;\n --color-text: #000;\n --color-primary: #444;\n --color-control-border: #bbb;\n --color-button-active-background: #f9f9f9;\n --color-button-active-border: #aaa;\n --color-seperator: #ccc;\n}\n\n.__next-auth-theme-dark {\n --color-background: #161b22;\n --color-background-card: #0d1117;\n --color-text: #fff;\n --color-primary: #ccc;\n --color-control-border: #555;\n --color-button-active-background: #060606;\n --color-button-active-border: #666;\n --color-seperator: #444;\n}\n\n@media (prefers-color-scheme: dark) {\n .__next-auth-theme-auto {\n --color-background: #161b22;\n --color-background-card: #0d1117;\n --color-text: #fff;\n --color-primary: #ccc;\n --color-control-border: #555;\n --color-button-active-background: #060606;\n --color-button-active-border: #666;\n --color-seperator: #444;\n }\n}\n\nbody {\n background-color: var(--color-background);\n margin: 0;\n padding: 0;\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif,\n \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n}\n\nh1 {\n font-weight: 400;\n margin-bottom: 1.5rem;\n padding: 0 1rem;\n color: var(--color-text);\n}\n\np {\n color: var(--color-text);\n}\n\nform {\n margin: 0;\n padding: 0;\n}\n\nlabel {\n font-weight: 500;\n text-align: left;\n margin-bottom: 0.25rem;\n display: block;\n color: var(--color-text);\n}\n\ninput[type] {\n box-sizing: border-box;\n display: block;\n width: 100%;\n padding: 0.5rem 1rem;\n border: var(--border-width) solid var(--color-control-border);\n background: var(--color-background-card);\n font-size: 1rem;\n border-radius: var(--border-radius);\n color: var(--color-text);\n}\n\ninput[type]:focus {\n box-shadow: none;\n }\n\np {\n margin: 0 0 1.5rem 0;\n padding: 0 1rem;\n font-size: 1.1rem;\n line-height: 2rem;\n}\n\na.button {\n text-decoration: none;\n line-height: 1rem;\n}\n\na.button:link,\n a.button:visited {\n background-color: var(--color-background);\n color: var(--color-primary);\n }\n\nbutton span {\n flex-grow: 1;\n}\n\nbutton,\na.button {\n margin: 0 0 0.75rem 0;\n padding: 0.75rem 1rem;\n color: var(--provider-color, var(--color-primary));\n background-color: var(--provider-bg, var(--color-background-card));\n font-size: 1.1rem;\n min-height: 62px;\n border-color: rgba(0, 0, 0, 0.1);\n border-radius: var(--border-radius);\n transition: all 0.1s ease-in-out;\n font-weight: 500;\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n@media (max-width: 450px) {\n\nbutton,\na.button {\n font-size: 0.9rem\n}\n }\n\nbutton:hover, a.button:hover {\n cursor: pointer;\n }\n\nbutton:active, a.button:active {\n cursor: pointer;\n }\n\nbutton #provider-logo, a.button #provider-logo {\n width: 25px;\n display: block;\n }\n\nbutton #provider-logo-dark, a.button #provider-logo-dark {\n display: none;\n }\n\n#submitButton {\n color: var(--button-text-color, var(--color-info-text));\n background-color: var(--brand-color, var(--color-info));\n width: 100%;\n}\n\n@media (prefers-color-scheme: dark) {\n button,\n a.button {\n color: var(--provider-dark-color, var(--color-primary));\n background-color: var(--provider-dark-bg, var(--color-background));\n }\n #provider-logo {\n display: none !important;\n }\n #provider-logo-dark {\n width: 25px;\n display: block !important;\n }\n}\n\na.site {\n color: var(--color-primary);\n text-decoration: none;\n font-size: 1rem;\n line-height: 2rem;\n}\n\na.site:hover {\n text-decoration: underline;\n }\n\n.page {\n position: absolute;\n width: 100%;\n height: 100%;\n display: grid;\n place-items: center;\n margin: 0;\n padding: 0;\n}\n\n.page > div {\n text-align: center;\n }\n\n.error a.button {\n display: inline-block;\n padding-left: 2rem;\n padding-right: 2rem;\n margin-top: 0.5rem;\n }\n\n.error .message {\n margin-bottom: 1.5rem;\n }\n\n.signin input[type=\"text\"] {\n margin-left: auto;\n margin-right: auto;\n display: block;\n }\n\n.signin hr {\n display: block;\n border: 0;\n border-top: 1px solid var(--color-seperator);\n margin: 2rem auto 1rem auto;\n overflow: visible;\n }\n\n.signin hr::before {\n content: \"or\";\n background: var(--color-background-card);\n color: #888;\n padding: 0 0.4rem;\n position: relative;\n top: -0.7rem;\n }\n\n.signin .error {\n background: #f5f5f5;\n font-weight: 500;\n border-radius: 0.3rem;\n background: var(--color-error);\n }\n\n.signin .error p {\n text-align: left;\n padding: 0.5rem 1rem;\n font-size: 0.9rem;\n line-height: 1.2rem;\n color: var(--color-info-text);\n }\n\n.signin > div,\n .signin form {\n display: block;\n }\n\n.signin > div input[type], .signin form input[type] {\n margin-bottom: 0.5rem;\n }\n\n.signin > div button, .signin form button {\n width: 100%;\n }\n\n.signin > div,\n .signin form {\n\n max-width: 300px;\n}\n\n.logo {\n display: inline-block;\n max-width: 150px;\n margin-top: 20px;\n margin-bottom: 25px;\n max-height: 70px;\n}\n\n@media screen and (min-width: 450px) {\n\n.card {\n width: 350px\n}\n }\n\n@media screen and (max-width: 450px) {\n\n.card {\n width: 200px\n}\n }\n\n.card {\n margin: 20px 0 20px 0;\n background-color: var(--color-background-card);\n border-radius: 30px;\n padding: 20px 50px;\n}\n\n.card .header {\n color: var(--color-primary);\n }\n\n.section-header {\n color: var(--color-text);\n}\n";
export default _default;
//# sourceMappingURL=styles.d.ts.map

@@ -11,3 +11,4 @@ export default `:root {

.__next-auth-theme-light {
--color-background: #fff;
--color-background: #ececec;
--color-background-card: #fff;
--color-text: #000;

@@ -22,3 +23,4 @@ --color-primary: #444;

.__next-auth-theme-dark {
--color-background: #000;
--color-background: #161b22;
--color-background-card: #0d1117;
--color-text: #fff;

@@ -34,3 +36,4 @@ --color-primary: #ccc;

.__next-auth-theme-auto {
--color-background: #000;
--color-background: #161b22;
--color-background-card: #0d1117;
--color-text: #fff;

@@ -84,6 +87,5 @@ --color-primary: #ccc;

border: var(--border-width) solid var(--color-control-border);
background: var(--color-background);
background: var(--color-background-card);
font-size: 1rem;
border-radius: var(--border-radius);
box-shadow: inset 0 0.1rem 0.2rem rgba(0, 0, 0, 0.2);
color: var(--color-text);

@@ -114,2 +116,6 @@ }

button span {
flex-grow: 1;
}
button,

@@ -120,3 +126,3 @@ a.button {

color: var(--provider-color, var(--color-primary));
background-color: var(--provider-bg, var(--color-background));
background-color: var(--provider-bg, var(--color-background-card));
font-size: 1.1rem;

@@ -127,4 +133,2 @@ min-height: 62px;

transition: all 0.1s ease-in-out;
box-shadow: #000 0px 0px 0px 0px, #000 0px 0px 0px 0px,
rgba(0, 0, 0, 0.2) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px;
font-weight: 500;

@@ -137,10 +141,10 @@ position: relative;

button:has(img), a.button:has(img) {
justify-content: unset;
@media (max-width: 450px) {
button,
a.button {
font-size: 0.9rem
}
}
button:has(img) span, a.button:has(img) span {
flex-grow: 1;
}
button:hover, a.button:hover {

@@ -151,5 +155,2 @@ cursor: pointer;

button:active, a.button:active {
box-shadow: 0 0.15rem 0.3rem rgba(0, 0, 0, 0.15),
inset 0 0.1rem 0.2rem var(--color-background),
inset 0 -0.1rem 0.1rem rgba(0, 0, 0, 0.1);
cursor: pointer;

@@ -159,2 +160,3 @@ }

button #provider-logo, a.button #provider-logo {
width: 25px;
display: block;

@@ -167,2 +169,8 @@ }

#submitButton {
color: var(--button-text-color, var(--color-info-text));
background-color: var(--brand-color, var(--color-info));
width: 100%;
}
@media (prefers-color-scheme: dark) {

@@ -173,6 +181,2 @@ button,

background-color: var(--provider-dark-bg, var(--color-background));
border: 1px solid #0d0d0d;
box-shadow: #000 0px 0px 0px 0px, #ccc 0px 0px 0px 0px,
rgba(255, 255, 255, 0.01) 0px 5px 5px -3px,
rgba(255, 255, 255, 0.05) 0px 4px 6px -4px;
}

@@ -183,2 +187,3 @@ #provider-logo {

#provider-logo-dark {
width: 25px;
display: block !important;

@@ -211,3 +216,2 @@ }

text-align: center;
padding: 0.5rem;
}

@@ -236,3 +240,3 @@

border-top: 1px solid var(--color-seperator);
margin: 1.5em auto 0 auto;
margin: 2rem auto 1rem auto;
overflow: visible;

@@ -243,7 +247,7 @@ }

content: "or";
background: var(--color-background);
background: var(--color-background-card);
color: #888;
padding: 0 0.4rem;
position: relative;
top: -0.6rem;
top: -0.7rem;
}

@@ -255,3 +259,3 @@

border-radius: 0.3rem;
background: var(--color-info);
background: var(--color-error);
}

@@ -285,20 +289,30 @@

}
.signout .message {
margin-bottom: 1.5rem;
}
.logo {
display: inline-block;
margin-top: 100px;
max-width: 300px;
max-height: 150px;
max-width: 150px;
margin-top: 20px;
margin-bottom: 25px;
max-height: 70px;
}
@media screen and (min-width: 450px) {
.card {
max-width: -moz-max-content;
max-width: max-content;
border: 1px solid var(--color-control-border);
border-radius: 5px;
width: 350px
}
}
@media screen and (max-width: 450px) {
.card {
width: 200px
}
}
.card {
margin: 20px 0 20px 0;
background-color: var(--color-background-card);
border-radius: 30px;
padding: 20px 50px;
margin: 50px auto;
}

@@ -311,5 +325,5 @@

.section-header {
color: var(--brand-color, var(--color-text));
color: var(--color-text);
}
`;
// Generated by `pnpm css`

@@ -10,3 +10,3 @@ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";

`,
} })), theme.logo && _jsx("img", { src: theme.logo, alt: "Logo", className: "logo" }), _jsxs("div", { className: "card", children: [_jsx("h1", { children: "Check your email" }), _jsx("p", { children: "A sign in link has been sent to your email address." }), _jsx("p", { children: _jsx("a", { className: "site", href: url.origin, children: url.host }) })] })] }));
} })), _jsxs("div", { className: "card", children: [theme.logo && _jsx("img", { src: theme.logo, alt: "Logo", className: "logo" }), _jsx("h1", { children: "Check your email" }), _jsx("p", { children: "A sign in link has been sent to your email address." }), _jsx("p", { children: _jsx("a", { className: "site", href: url.origin, children: url.host }) })] })] }));
}

@@ -67,5 +67,8 @@ import { merge } from "./utils/merge.js";

const url = new URL(e?.url ?? "https://authjs.dev");
for (const k in e?.params)
for (const k in e?.params) {
if (e?.params && k === "claims")
e.params[k] = JSON.stringify(e.params[k]);
url.searchParams.set(k, e?.params[k]);
return { url, request: e?.request };
}
return { url, request: e?.request, conform: e?.conform };
}
import { handleLogin } from "../callback-handler.js";
import { CallbackRouteError } from "../../errors.js";
import { CallbackRouteError, Verification } from "../../errors.js";
import { handleOAuth } from "../oauth/callback.js";

@@ -61,11 +61,17 @@ import { createHash } from "../web.js";

});
// Encode token
const newToken = await jwt.encode({ ...jwt, token });
// Set cookie expiry date
const cookieExpires = new Date();
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000);
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
});
cookies.push(...sessionCookies);
// Clear cookies if token is null
if (token === null) {
cookies.push(...sessionStore.clean());
}
else {
// Encode token
const newToken = await jwt.encode({ ...jwt, token });
// Set cookie expiry date
const cookieExpires = new Date();
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000);
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
});
cookies.push(...sessionCookies);
}
}

@@ -99,5 +105,6 @@ else {

const identifier = query?.email;
// If these are missing, the sign-in URL was manually opened without these params or the `sendVerificationRequest` method did not send the link correctly in the email.
if (!token || !identifier) {
return { redirect: `${url}/error?error=configuration`, cookies };
const e = new TypeError("Missing token or email. The sign-in URL was manually opened without token/identifier or the link was not sent correctly in the email.", { cause: { hasToken: !!token, hasEmail: !!identifier } });
e.name = "Configuration";
throw e;
}

@@ -110,6 +117,7 @@ const secret = provider.secret ?? options.secret;

});
const invalidInvite = !invite || invite.expires.valueOf() < Date.now();
if (invalidInvite) {
return { redirect: `${url}/error?error=Verification`, cookies };
}
const hasInvite = !!invite;
const expired = invite ? invite.expires.valueOf() < Date.now() : undefined;
const invalidInvite = !hasInvite || expired;
if (invalidInvite)
throw new Verification({ hasInvite, expired });
// @ts-expect-error -- Verified in `assertConfig`.

@@ -142,11 +150,17 @@ const user = await getAdapterUserFromEmail(identifier, adapter);

});
// Encode token
const newToken = await jwt.encode({ ...jwt, token });
// Set cookie expiry date
const cookieExpires = new Date();
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000);
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
});
cookies.push(...sessionCookies);
// Clear cookies if token is null
if (token === null) {
cookies.push(...sessionStore.clean());
}
else {
// Encode token
const newToken = await jwt.encode({ ...jwt, token });
// Set cookie expiry date
const cookieExpires = new Date();
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000);
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
});
cookies.push(...sessionCookies);
}
}

@@ -179,25 +193,14 @@ else {

const credentials = body;
let user;
try {
// TODO: Forward the original request as is, instead of reconstructing it
// prettier-ignore
Object.entries(query ?? {}).forEach(([k, v]) => url.searchParams.set(k, v));
user = await provider.authorize(credentials,
// prettier-ignore
new Request(url, { headers, method, body: JSON.stringify(body) }));
if (!user) {
return {
status: 401,
redirect: `${url}/error?${new URLSearchParams({
error: "CredentialsSignin",
provider: provider.id,
})}`,
cookies,
};
}
}
catch (e) {
// TODO: Forward the original request as is, instead of reconstructing it
Object.entries(query ?? {}).forEach(([k, v]) => url.searchParams.set(k, v));
const user = await provider.authorize(credentials,
// prettier-ignore
new Request(url, { headers, method, body: JSON.stringify(body) }));
if (!user) {
return {
status: 401,
redirect: `${url}/error?error=${encodeURIComponent(e.message)}`,
redirect: `${url}/error?${new URLSearchParams({
error: "CredentialsSignin",
provider: provider.id,
})}`,
cookies,

@@ -228,11 +231,17 @@ };

});
// Encode token
const newToken = await jwt.encode({ ...jwt, token });
// Set cookie expiry date
const cookieExpires = new Date();
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000);
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
});
cookies.push(...sessionCookies);
// Clear cookies if token is null
if (token === null) {
cookies.push(...sessionStore.clean());
}
else {
// Encode token
const newToken = await jwt.encode({ ...jwt, token });
// Set cookie expiry date
const cookieExpires = new Date();
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000);
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
});
cookies.push(...sessionCookies);
}
// @ts-expect-error

@@ -239,0 +248,0 @@ await events.signIn?.({ user, account });

@@ -30,18 +30,23 @@ import { JWTSessionError, SessionTokenError } from "../../errors.js";

const token = await callbacks.jwt({ token: decodedToken });
// @ts-expect-error
const newSession = await callbacks.session({ session, token });
// Return session payload as response
response.body = newSession;
// Refresh JWT expiry by re-signing it, with an updated expiry date
const newToken = await jwt.encode({
...jwt,
token,
maxAge: options.session.maxAge,
});
// Set cookie, to also update expiry date on cookie
const sessionCookies = sessionStore.chunk(newToken, {
expires: newExpires,
});
response.cookies?.push(...sessionCookies);
await events.session?.({ session: newSession, token });
if (token !== null) {
// @ts-expect-error
const newSession = await callbacks.session({ session, token });
// Return session payload as response
response.body = newSession;
// Refresh JWT expiry by re-signing it, with an updated expiry date
const newToken = await jwt.encode({
...jwt,
token,
maxAge: options.session.maxAge,
});
// Set cookie, to also update expiry date on cookie
const sessionCookies = sessionStore.chunk(newToken, {
expires: newExpires,
});
response.cookies?.push(...sessionCookies);
await events.session?.({ session: newSession, token });
}
else {
response.cookies?.push(...sessionStore.clean());
}
}

@@ -48,0 +53,0 @@ catch (e) {

import { AuthError } from "../../errors.js";
export type WarningCode = "debug_enabled";
export type WarningCode = "debug-enabled" | "csrf-disabled";
/**

@@ -4,0 +4,0 @@ * Override any of the methods, and the rest will use the default logger.

@@ -8,8 +8,14 @@ const red = "\x1b[31m";

const url = `https://errors.authjs.dev#${error.name.toLowerCase()}`;
console.error(error.stack);
console.error(`${red}[auth][error][${error.name}]${reset}: Read more at ${url}`);
error.metadata && console.error(JSON.stringify(error.metadata, null, 2));
console.error(`${red}[auth][error][${error.name}]${reset}:${error.message ? ` ${error.message}.` : ""} Read more at ${url}`);
if (error.cause) {
const { err, ...data } = error.cause;
console.error(`${red}[auth][cause]${reset}:`, err.stack);
console.error(`${red}[auth][details]${reset}:`, JSON.stringify(data, null, 2));
}
else if (error.stack) {
console.error(error.stack.replace(/.*/, "").substring(1));
}
},
warn(code) {
const url = `https://errors.authjs.dev#${code}`;
const url = `https://warnings.authjs.dev#${code}`;
console.warn(`${yellow}[auth][warn][${code}]${reset}`, `Read more: ${url}`);

@@ -16,0 +22,0 @@ },

{
"name": "@auth/core",
"version": "0.2.5",
"version": "0.3.0",
"description": "Authentication for the Web.",

@@ -44,4 +44,4 @@ "keywords": [

"./errors": {
"import": "./errors.js",
"types": "./errors.d.ts"
"types": "./errors.d.ts",
"import": "./errors.js"
},

@@ -48,0 +48,0 @@ "./jwt": {

@@ -0,1 +1,12 @@

/**
* <div style={{backgroundColor: "#000", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Apple</b> integration.</span>
* <a href="https://apple.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/apple-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/apple
*/
import type { OAuthConfig, OAuthUserConfig } from "./index.js";

@@ -2,0 +13,0 @@ /**

@@ -0,1 +1,12 @@

/**
* <div style={{backgroundColor: "#000", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Apple</b> integration.</span>
* <a href="https://apple.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/apple-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/apple
*/
export default function Apple(options) {

@@ -2,0 +13,0 @@ return {

@@ -1,9 +0,120 @@

import type { OAuthConfig, OAuthUserConfig } from "./index.js";
export interface Auth0Profile extends Record<string, any> {
/**
* <div style={{backgroundColor: "#EB5424", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Auth0</b> integration.</span>
* <a href="https://auth0.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/auth0-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/auth0
*/
import type { OIDCConfig, OIDCUserConfig } from "./index.js";
/** @see [User Profile Structure](https://auth0.com/docs/manage-users/user-accounts/user-profiles/user-profile-structure) */
export interface Auth0Profile {
/** The user's unique identifier. */
sub: string;
/** Custom fields that store info about a user that influences the user's access, such as support plan, security roles (if not using the Authorization Core feature set), or access control groups. To learn more, read Metadata Overview. */
app_metadata: object;
/** Indicates whether the user has been blocked. Importing enables subscribers to ensure that users remain blocked when migrating to Auth0. */
blocked: boolean;
/** Timestamp indicating when the user profile was first created. */
created_at: Date;
/** (unique) The user's email address. */
email: string;
/** Indicates whether the user has verified their email address. */
email_verified: boolean;
/** The user's family name. */
family_name: string;
/** The user's given name. */
given_name: string;
/** Custom fields that store info about a user that does not impact what they can or cannot access, such as work address, home address, or user preferences. To learn more, read Metadata Overview. */
user_metadata: object;
/** (unique) The user's username. */
username: string;
/** Contains info retrieved from the identity provider with which the user originally authenticates. Users may also link their profile to multiple identity providers; those identities will then also appear in this array. The contents of an individual identity provider object varies by provider. In some cases, it will also include an API Access Token to be used with the provider. */
identities: Array<{
/** Name of the Auth0 connection used to authenticate the user. */
connection: string;
/** Indicates whether the connection is a social one. */
isSocial: boolean;
/** Name of the entity that is authenticating the user, such as Facebook, Google, SAML, or your own provider. */
provider: string;
/** User's unique identifier for this connection/provider. */
user_id: string;
/** User info associated with the connection. When profiles are linked, it is populated with the associated user info for secondary accounts. */
profileData: object;
[key: string]: any;
}>;
/** IP address associated with the user's last login. */
last_ip: string;
/** Timestamp indicating when the user last logged in. If a user is blocked and logs in, the blocked session updates last_login. If you are using this property from inside a Rule using the user< object, its value will be associated with the login that triggered the rule; this is because rules execute after login. */
last_login: Date;
/** Timestamp indicating the last time the user's password was reset/changed. At user creation, this field does not exist. This property is only available for Database connections. */
last_password_reset: Date;
/** Number of times the user has logged in. If a user is blocked and logs in, the blocked session is counted in logins_count. */
logins_count: number;
/** List of multi-factor providers with which the user is enrolled. */
multifactor: string;
/** The user's full name. */
name: string;
/** The user's nickname. */
nickname: string;
email: string;
/** The user's phone number. Only valid for users with SMS connections. */
phone_number: string;
/** Indicates whether the user has been verified their phone number. Only valid for users with SMS connections. */
phone_verified: boolean;
/** URL pointing to the user's profile picture. */
picture: string;
/** Timestamp indicating when the user's profile was last updated/modified. Changes to last_login are considered updates, so most of the time, updated_at will match last_login. */
updated_at: Date;
/** (unique) The user's identifier. Importing allows user records to be synchronized across multiple systems without using mapping tables. */
user_id: string;
}
export default function Auth0<P extends Auth0Profile>(options: OAuthUserConfig<P>): OAuthConfig<P>;
/**
* Add Auth0 login to your page.
*
* ## Example
*
* ```ts
* import { Auth } from "@auth/core"
* import Auth0 from "@auth/core/providers/auth0"
*
* const request = new Request("https://example.com")
* const resposne = await Auth(request, {
* providers: [Auth0({ clientId: "", clientSecret: "", issuer: "" })],
* })
* ```
*
* ---
*
* ## Resources
*
* - [Authenticate - Auth0 docs](https://auth0.com/docs/authenticate)
*
* ---
*
* ## Notes
*
* By default, Auth.js assumes that the Auth0 provider is
* based on the [OIDC](https://openid.net/specs/openid-connect-core-1_0.html) specification.
*
* :::tip
*
* The Auth0 provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/auth0.ts).
* To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/providers/custom-provider#override-default-options).
*
* :::
*
* :::info **Disclaimer**
*
* If you think you found a bug in the default configuration, you can [open an issue](https://authjs.dev/new/provider-issue).
*
* Auth.js strictly adheres to the specification and it cannot take responsibility for any deviation from
* the spec by the provider. You can open an issue, but if the problem is non-compliance with the spec,
* we might not pursue a resolution. You can ask for more help in [Discussions](https://authjs.dev/new/github-discussions).
*
* :::
*/
export default function Auth0(config: OIDCUserConfig<Auth0Profile>): OIDCConfig<Auth0Profile>;
//# sourceMappingURL=auth0.d.ts.map

@@ -1,2 +0,58 @@

export default function Auth0(options) {
/**
* <div style={{backgroundColor: "#EB5424", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Auth0</b> integration.</span>
* <a href="https://auth0.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/auth0-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/auth0
*/
/**
* Add Auth0 login to your page.
*
* ## Example
*
* ```ts
* import { Auth } from "@auth/core"
* import Auth0 from "@auth/core/providers/auth0"
*
* const request = new Request("https://example.com")
* const resposne = await Auth(request, {
* providers: [Auth0({ clientId: "", clientSecret: "", issuer: "" })],
* })
* ```
*
* ---
*
* ## Resources
*
* - [Authenticate - Auth0 docs](https://auth0.com/docs/authenticate)
*
* ---
*
* ## Notes
*
* By default, Auth.js assumes that the Auth0 provider is
* based on the [OIDC](https://openid.net/specs/openid-connect-core-1_0.html) specification.
*
* :::tip
*
* The Auth0 provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/auth0.ts).
* To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/providers/custom-provider#override-default-options).
*
* :::
*
* :::info **Disclaimer**
*
* If you think you found a bug in the default configuration, you can [open an issue](https://authjs.dev/new/provider-issue).
*
* Auth.js strictly adheres to the specification and it cannot take responsibility for any deviation from
* the spec by the provider. You can open an issue, but if the problem is non-compliance with the spec,
* we might not pursue a resolution. You can ask for more help in [Discussions](https://authjs.dev/new/github-discussions).
*
* :::
*/
export default function Auth0(config) {
return {

@@ -14,4 +70,4 @@ id: "auth0",

},
options,
options: config,
};
}

@@ -13,20 +13,18 @@ export default function AzureAD(options) {

// Confirm that profile photo was returned
if (response.ok) {
const pictureBuffer = await response.arrayBuffer();
const pictureBase64 = Buffer.from(pictureBuffer).toString("base64");
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: `data:image/jpeg;base64, ${pictureBase64}`,
};
let image;
// TODO: Do this without Buffer
if (response.ok && typeof Buffer !== "undefined") {
try {
const pictureBuffer = await response.arrayBuffer();
const pictureBase64 = Buffer.from(pictureBuffer).toString("base64");
image = `data:image/jpeg;base64, ${pictureBase64}`;
}
catch { }
}
else {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
};
}
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: image ?? null,
};
},

@@ -33,0 +31,0 @@ style: {

@@ -0,3 +1,14 @@

/**
* <div style={{backgroundColor: "#24292f", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>GitHub</b> integration.</span>
* <a href="https://github.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/github-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/github
*/
import type { OAuthConfig, OAuthUserConfig } from "./index.js";
export interface GithubEmail extends Record<string, any> {
export interface GitHubEmail {
email: string;

@@ -9,3 +20,3 @@ primary: boolean;

/** @see [Get the authenticated user](https://docs.github.com/en/rest/users/users#get-the-authenticated-user) */
export interface GithubProfile extends Record<string, any> {
export interface GitHubProfile {
login: string;

@@ -62,10 +73,8 @@ id: number;

*
* @example
*
* ```ts
* import Auth from "@auth/core"
* import { GitHub } from "@auth/core/providers/github"
* import { Auth } from "@auth/core"
* import GitHub from "@auth/core/providers/github"
*
* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, {
* const resposne = await Auth(request, {
* providers: [GitHub({ clientId: "", clientSecret: "" })],

@@ -105,3 +114,3 @@ * })

*/
export default function GitHub<Profile extends GithubProfile>(options: OAuthUserConfig<Profile>): OAuthConfig<Profile>;
export default function GitHub(config: OAuthUserConfig<GitHubProfile>): OAuthConfig<GitHubProfile>;
//# sourceMappingURL=github.d.ts.map
/**
* <div style={{backgroundColor: "#24292f", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>GitHub</b> integration.</span>
* <a href="https://github.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/github-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/github
*/
/**
* Add GitHub login to your page and make requests to [GitHub APIs](https://docs.github.com/en/rest).

@@ -6,10 +17,8 @@ *

*
* @example
*
* ```ts
* import Auth from "@auth/core"
* import { GitHub } from "@auth/core/providers/github"
* import { Auth } from "@auth/core"
* import GitHub from "@auth/core/providers/github"
*
* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, {
* const resposne = await Auth(request, {
* providers: [GitHub({ clientId: "", clientSecret: "" })],

@@ -49,3 +58,3 @@ * })

*/
export default function GitHub(options) {
export default function GitHub(config) {
return {

@@ -92,8 +101,8 @@ id: "github",

bg: "#fff",
bgDark: "#000",
bgDark: "#24292f",
text: "#000",
textDark: "#fff",
},
options,
options: config,
};
}

@@ -56,3 +56,3 @@ import type { Client } from "oauth4webapi";

}, Profile>;
export type ProfileCallback<P> = (profile: P, tokens: TokenSet) => Awaitable<User>;
export type ProfileCallback<Profile> = (profile: Profile, tokens: TokenSet) => Awaitable<User>;
export interface OAuthProviderButtonStyles {

@@ -67,3 +67,3 @@ logo: string;

/** TODO: */
export interface OAuth2Config<P> extends CommonProviderOptions, PartialIssuer {
export interface OAuth2Config<Profile> extends CommonProviderOptions, PartialIssuer {
/**

@@ -108,3 +108,3 @@ * Identifies the provider when you want to sign in to

*/
profile?: ProfileCallback<P>;
profile?: ProfileCallback<Profile>;
/**

@@ -129,25 +129,9 @@ * The CSRF protection performed on the callback endpoint.

/** TODO: */
export interface OIDCConfig<P> extends Omit<OAuth2Config<P>, "type"> {
export interface OIDCConfig<Profile> extends Omit<OAuth2Config<Profile>, "type"> {
type: "oidc";
}
export type OAuthConfig<P> = OIDCConfig<P> | OAuth2Config<P>;
export type OAuthConfig<Profile> = OIDCConfig<Profile> | OAuth2Config<Profile>;
export type OAuthEndpointType = "authorization" | "token" | "userinfo";
/**
* We parsesd `authorization`, `token` and `userinfo`
* to always contain a valid `URL`, with the params
*/
export type OAuthConfigInternal<P> = Omit<OAuthConfig<P>, OAuthEndpointType> & {
authorization?: {
url: URL;
};
token?: {
url: URL;
request?: TokenEndpointHandler["request"];
};
userinfo?: {
url: URL;
request?: UserinfoEndpointHandler["request"];
};
} & Pick<Required<OAuthConfig<P>>, "clientId" | "checks" | "profile">;
export type OAuthUserConfig<P> = Omit<Partial<OAuthConfig<P>>, "options" | "type"> & Required<Pick<OAuthConfig<P>, "clientId" | "clientSecret">>;
export type OAuthUserConfig<Profile> = Omit<Partial<OAuthConfig<Profile>>, "options" | "type"> & Required<Pick<OAuthConfig<Profile>, "clientId" | "clientSecret">>;
export type OIDCUserConfig<Profile> = Omit<Partial<OIDCConfig<Profile>>, "options" | "type"> & Required<Pick<OIDCConfig<Profile>, "clientId" | "clientSecret">>;
//# sourceMappingURL=oauth.d.ts.map

@@ -1,2 +0,2 @@

import type { OAuthConfig, OAuthUserConfig } from "./index.js";
import type { OIDCConfig, OIDCUserConfig } from "./index.js";
export interface TwitchProfile extends Record<string, any> {

@@ -8,3 +8,3 @@ sub: string;

}
export default function Twitch<P extends TwitchProfile>(options: OAuthUserConfig<P>): OAuthConfig<P>;
export default function Twitch(config: OIDCUserConfig<TwitchProfile>): OIDCConfig<TwitchProfile>;
//# sourceMappingURL=twitch.d.ts.map

@@ -1,2 +0,2 @@

export default function Twitch(options) {
export default function Twitch(config) {
return {

@@ -7,2 +7,3 @@ issuer: "https://id.twitch.tv/oauth2",

type: "oidc",
client: { token_endpoint_auth_method: "client_secret_post" },
authorization: {

@@ -12,10 +13,31 @@ params: {

claims: {
id_token: {
email: null,
picture: null,
preferred_username: null,
},
id_token: { email: null, picture: null, preferred_username: null },
},
},
},
token: {
async conform(response) {
const body = await response.json();
if (response.ok) {
if (typeof body.scope === "string") {
console.warn("'scope' is a string. Redundant workaround, please open an issue.");
}
else if (Array.isArray(body.scope)) {
body.scope = body.scope.join(" ");
return new Response(JSON.stringify(body), response);
}
else if ("scope" in body) {
delete body.scope;
return new Response(JSON.stringify(body), response);
}
}
else {
const { message: error_description, error } = body;
if (typeof error !== "string") {
return new Response(JSON.stringify({ error: "invalid_request", error_description }), response);
}
console.warn("Response has 'error'. Redundant workaround, please open an issue.");
}
},
},
style: {

@@ -29,4 +51,4 @@ logo: "/twitch.svg",

},
options,
options: config,
};
}
import type { OAuthConfig, OAuthUserConfig } from "./index.js";
export interface TwitterLegacyProfile {
id: number;
id_str: string;
name: string;
screen_name: string;
location: string;
description: string;
url: string;
entities: {
url: {
urls: Array<{
url: string;
expanded_url: string;
display_url: string;
indices: number[];
}>;
};
description: {
urls: any[];
};
};
protected: boolean;
followers_count: number;
friends_count: number;
listed_count: number;
created_at: string;
favourites_count: number;
utc_offset?: any;
time_zone?: any;
geo_enabled: boolean;
verified: boolean;
statuses_count: number;
lang?: any;
status: {
created_at: string;
id: number;
id_str: string;
text: string;
truncated: boolean;
entities: {
hashtags: any[];
symbols: any[];
user_mentions: Array<{
screen_name: string;
name: string;
id: number;
id_str: string;
indices: number[];
}>;
urls: any[];
};
source: string;
in_reply_to_status_id: number;
in_reply_to_status_id_str: string;
in_reply_to_user_id: number;
in_reply_to_user_id_str: string;
in_reply_to_screen_name: string;
geo?: any;
coordinates?: any;
place?: any;
contributors?: any;
is_quote_status: boolean;
retweet_count: number;
favorite_count: number;
favorited: boolean;
retweeted: boolean;
lang: string;
};
contributors_enabled: boolean;
is_translator: boolean;
is_translation_enabled: boolean;
profile_background_color: string;
profile_background_image_url: string;
profile_background_image_url_https: string;
profile_background_tile: boolean;
profile_image_url: string;
profile_image_url_https: string;
profile_banner_url: string;
profile_link_color: string;
profile_sidebar_border_color: string;
profile_sidebar_fill_color: string;
profile_text_color: string;
profile_use_background_image: boolean;
has_extended_profile: boolean;
default_profile: boolean;
default_profile_image: boolean;
following: boolean;
follow_request_sent: boolean;
notifications: boolean;
translator_type: string;
withheld_in_countries: any[];
suspended: boolean;
needs_phone_verification: boolean;
}
/**
* [Documentation](https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me)
* [Users lookup](https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me)
*/
export interface TwitterProfile {
data: {
/**
* Unique identifier of this user. This is returned as a string in order to avoid complications with languages and tools
* that cannot handle large integers.
*/
id: string;
/** The friendly name of this user, as shown on their profile. */
name: string;
/** @note Email is currently unsupported by Twitter. */
email?: string;
/** The Twitter handle (screen name) of this user. */
username: string;
/**
* The location specified in the user's profile, if the user provided one.
* As this is a freeform value, it may not indicate a valid location, but it may be fuzzily evaluated when performing searches with location queries.
*
* To return this field, add `user.fields=location` in the authorization request's query parameter.
*/
location?: string;
/**
* This object and its children fields contain details about text that has a special meaning in the user's description.
*
*To return this field, add `user.fields=entities` in the authorization request's query parameter.
*/
entities?: {
/** Contains details about the user's profile website. */
url: {
/** Contains details about the user's profile website. */
urls: Array<{
/** The start position (zero-based) of the recognized user's profile website. All start indices are inclusive. */
start: number;
/** The end position (zero-based) of the recognized user's profile website. This end index is exclusive. */
end: number;
/** The URL in the format entered by the user. */
url: string;
/** The fully resolved URL. */
expanded_url: string;
/** The URL as displayed in the user's profile. */
display_url: string;
}>;
};
/** Contains details about URLs, Hashtags, Cashtags, or mentions located within a user's description. */
description: {

@@ -123,7 +56,28 @@ hashtags: Array<{

};
/**
* Indicate if this user is a verified Twitter user.
*
* To return this field, add `user.fields=verified` in the authorization request's query parameter.
*/
verified?: boolean;
/**
* The text of this user's profile description (also known as bio), if the user provided one.
*
* To return this field, add `user.fields=description` in the authorization request's query parameter.
*/
description?: string;
/**
* The URL specified in the user's profile, if present.
*
* To return this field, add `user.fields=url` in the authorization request's query parameter.
*/
url?: string;
/** The URL to the profile image for this user, as shown on the user's profile. */
profile_image_url?: string;
protected?: boolean;
/**
* Unique identifier of this user's pinned Tweet.
*
* You can obtain the expanded object in `includes.tweets` by adding `expansions=pinned_tweet_id` in the authorization request's query parameter.
*/
pinned_tweet_id?: string;

@@ -139,5 +93,3 @@ created_at?: string;

}
export default function Twitter<P extends Record<string, any> = TwitterLegacyProfile | TwitterProfile>(options: OAuthUserConfig<P> & {
version?: "2.0";
}): OAuthConfig<P>;
export default function Twitter(config: OAuthUserConfig<TwitterProfile>): OAuthConfig<TwitterProfile>;
//# sourceMappingURL=twitter.d.ts.map

@@ -1,2 +0,2 @@

export default function Twitter(options) {
export default function Twitter(config) {
return {

@@ -7,18 +7,5 @@ id: "twitter",

checks: ["pkce", "state"],
authorization: {
url: "https://twitter.com/i/oauth2/authorize",
params: { scope: "users.read tweet.read offline.access" },
},
token: {
url: "https://api.twitter.com/2/oauth2/token",
// @ts-expect-error TODO: Remove this
async request({ client, params, checks, provider }) {
const response = await client.oauthCallback(provider.callbackUrl, params, checks, { exchangeBody: { client_id: options.clientId } });
return { tokens: response };
},
},
userinfo: {
url: "https://api.twitter.com/2/users/me",
params: { "user.fields": "profile_image_url" },
},
authorization: "https://twitter.com/i/oauth2/authorize?scope=users.read tweet.read offline.access",
token: "https://api.twitter.com/2/oauth2/token",
userinfo: "https://api.twitter.com/2/users/me?user.fields=profile_image_url",
profile({ data }) {

@@ -28,4 +15,3 @@ return {

name: data.name,
// NOTE: E-mail is currently unsupported by OAuth 2 Twitter.
email: null,
email: data.email ?? null,
image: data.profile_image_url,

@@ -42,4 +28,4 @@ };

},
options,
options: config,
};
}

@@ -0,12 +1,21 @@

interface ErrorCause extends Record<string, unknown> {}
/** @internal */
export class AuthError extends Error {
metadata?: Record<string, unknown>
constructor(message: Error | string, metadata?: Record<string, unknown>) {
constructor(message: string | Error | ErrorCause, cause?: ErrorCause) {
if (message instanceof Error) {
super(message.message)
this.stack = message.stack
} else super(message)
this.name = this.constructor.name
this.metadata = metadata
super(undefined, {
cause: { err: message, ...(message.cause as any), ...cause },
})
} else if (typeof message === "string") {
if (cause instanceof Error) {
cause = { err: cause, ...(cause.cause as any) }
}
super(message, cause)
} else {
super(undefined, message)
}
Error.captureStackTrace?.(this, this.constructor)
this.name =
message instanceof AuthError ? message.name : this.constructor.name
}

@@ -31,3 +40,41 @@ }

/** @todo */
/**
* There was an error while trying to finish up authenticating the user.
* Depending on the type of provider, this could be for multiple reasons.
*
* :::tip
* Check out `[auth][details]` in the error message to know which provider failed.
* @example
* ```sh
* [auth][details]: { "provider": "github" }
* ```
* :::
*
* For an **OAuth provider**, possible causes are:
* - The user denied access to the application
* - There was an error parsing the OAuth Profile:
* Check out the provider's `profile` or `userinfo.request` method to make sure
* it correctly fetches the user's profile.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* For an **Email provider**, possible causes are:
* - The provided email/token combination was invalid/missing:
* Check if the provider's `sendVerificationRequest` method correctly sends the email.
* - The provided email/token combination has expired:
* Ask the user to log in again.
* - There was an error with the database:
* Check the database logs.
*
* For a **Credentials provider**, possible causes are:
* - The `authorize` method threw an uncaught error:
* Check the provider's `authorize` method.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* :::tip
* Check out `[auth][cause]` in the error message for more details.
* It will show the original stack trace.
* :::
*/
export class CallbackRouteError extends AuthError {}

@@ -97,1 +144,8 @@

export class UntrustedHost extends AuthError {}
/**
* The user's email/token combination was invalid.
* This could be because the email/token combination was not found in the database,
* or because it token has expired. Ask the user to log in again.
*/
export class Verification extends AuthError {}

@@ -64,3 +64,3 @@ /**

* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, {
* const response = await AuthHandler(request, {
* providers: [...],

@@ -161,3 +161,3 @@ * secret: "...",

* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, authConfig)
* const response = await AuthHandler(request, authConfig)
* ```

@@ -301,2 +301,14 @@ *

trustHost?: boolean
skipCSRFCheck?: typeof skipCSRFCheck
}
/**
* :::danger
* This option is inteded for framework authors.
* :::
*
* Auth.js comes with built-in {@link https://authjs.dev/concepts/security#csrf CSRF} protection, but
* if you are implementing a framework that is already protected against CSRF attacks, you can skip this check by
* passing this value to {@link AuthConfig.skipCSRFCheck}.
*/
export const skipCSRFCheck = Symbol("skip-csrf-check")

@@ -48,3 +48,3 @@ import { defaultCookies } from "./cookie.js"

if (!warned && options.debug) warnings.push("debug_enabled")
if (!warned && options.debug) warnings.push("debug-enabled")

@@ -51,0 +51,0 @@ if (!options.trustHost) {

@@ -136,3 +136,4 @@ import { AccountNotLinked } from "../errors.js"

throw new AccountNotLinked(
"The account is already associated with another user"
"The account is already associated with another user",
{ provider: account.provider }
)

@@ -197,3 +198,4 @@ }

throw new AccountNotLinked(
"Another account already exists with the same e-mail address"
"Another account already exists with the same e-mail address",
{ provider: account.provider }
)

@@ -200,0 +202,0 @@ }

@@ -0,3 +1,4 @@

import { UnknownAction } from "../errors.js"
import { skipCSRFCheck } from "../index.js"
import { SessionStore } from "./cookie.js"
import { UnknownAction } from "../errors.js"
import { init } from "./init.js"

@@ -8,6 +9,6 @@ import renderPage from "./pages/index.js"

import type {
AuthConfig,
ErrorPageParam,
RequestInternal,
ResponseInternal,
AuthConfig,
ErrorPageParam,
} from "../types.js"

@@ -23,2 +24,4 @@

const csrfDisabled = authOptions.skipCSRFCheck === skipCSRFCheck
const { options, cookies } = await init({

@@ -33,2 +36,3 @@ authOptions,

isPost: method === "POST",
csrfDisabled,
})

@@ -54,3 +58,12 @@

}
case "csrf":
case "csrf": {
if (csrfDisabled) {
options.logger.warn("csrf-disabled")
cookies.push({
name: options.cookies.csrfToken.name,
value: "",
options: { ...options.cookies.csrfToken.options, maxAge: 0 },
})
return { status: 404, cookies }
}
return {

@@ -61,2 +74,3 @@ headers: { "Content-Type": "application/json" },

}
}
case "signin":

@@ -133,4 +147,3 @@ if (pages.signIn) {

case "signin":
// Verified CSRF Token required for all sign in routes
if (options.csrfTokenVerified && options.provider) {
if ((csrfDisabled || options.csrfTokenVerified) && options.provider) {
const signin = await routes.signin(

@@ -147,4 +160,3 @@ request.query,

case "signout":
// Verified CSRF Token required for signout
if (options.csrfTokenVerified) {
if (csrfDisabled || options.csrfTokenVerified) {
const signout = await routes.signout(sessionStore, options)

@@ -160,2 +172,3 @@ if (signout.cookies) cookies.push(...signout.cookies)

options.provider.type === "credentials" &&
!csrfDisabled &&
!options.csrfTokenVerified

@@ -162,0 +175,0 @@ ) {

@@ -28,2 +28,3 @@ import * as jwt from "../jwt.js"

/** Is the incoming request a POST request? */
csrfDisabled: boolean
isPost: boolean

@@ -42,2 +43,3 @@ cookies: RequestInternal["cookies"]

csrfToken: reqCsrfToken,
csrfDisabled,
isPost,

@@ -96,3 +98,3 @@ }: InitParams): Promise<{

updateAge: 24 * 60 * 60,
generateSessionToken: crypto.randomUUID,
generateSessionToken: () => crypto.randomUUID(),
...authOptions.session,

@@ -123,22 +125,24 @@ },

const {
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
})
if (!csrfDisabled) {
const {
csrfToken,
cookie: csrfCookie,
csrfTokenVerified,
} = await createCSRFToken({
options,
cookieValue: reqCookies?.[options.cookies.csrfToken.name],
isPost,
bodyValue: reqCsrfToken,
})
options.csrfToken = csrfToken
options.csrfTokenVerified = csrfTokenVerified
options.csrfToken = csrfToken
options.csrfTokenVerified = csrfTokenVerified
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
})
if (csrfCookie) {
cookies.push({
name: options.cookies.csrfToken.name,
value: csrfCookie,
options: options.cookies.csrfToken.options,
})
}
}

@@ -145,0 +149,0 @@

@@ -0,5 +1,5 @@

import * as checks from "./checks.js"
import * as o from "oauth4webapi"
import type {
CookiesOptions,
InternalOptions,

@@ -61,6 +61,6 @@ RequestInternal,

if (provider.checks?.includes("state")) {
const { value, raw } = await createState(options)
authParams.set("state", raw)
cookies.push(value)
const state = await checks.state.create(options)
if (state) {
authParams.set("state", state.value)
cookies.push(state.cookie)
}

@@ -74,13 +74,13 @@

} else {
const { code_challenge, pkce } = await createPKCE(options)
authParams.set("code_challenge", code_challenge)
const { value, cookie } = await checks.pkce.create(options)
authParams.set("code_challenge", value)
authParams.set("code_challenge_method", "S256")
cookies.push(pkce)
cookies.push(cookie)
}
}
if (provider.checks?.includes("nonce")) {
const nonce = await createNonce(options)
const nonce = await checks.nonce.create(options)
if (nonce) {
authParams.set("nonce", nonce.value)
cookies.push(nonce)
cookies.push(nonce.cookie)
}

@@ -97,50 +97,1 @@

}
/** Returns a signed cookie. */
export async function signCookie(
type: keyof CookiesOptions,
value: string,
maxAge: number,
options: InternalOptions<"oauth">
): Promise<Cookie> {
const { cookies, jwt, logger } = options
logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge })
const expires = new Date()
expires.setTime(expires.getTime() + maxAge * 1000)
return {
name: cookies[type].name,
value: await jwt.encode({ ...jwt, maxAge, token: { value } }),
options: { ...cookies[type].options, expires },
}
}
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
async function createState(options: InternalOptions<"oauth">) {
const raw = o.generateRandomState()
const maxAge = STATE_MAX_AGE
const value = await signCookie("state", raw, maxAge, options)
return { value, raw }
}
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
async function createPKCE(options: InternalOptions<"oauth">) {
const code_verifier = o.generateRandomCodeVerifier()
const code_challenge = await o.calculatePKCECodeChallenge(code_verifier)
const maxAge = PKCE_MAX_AGE
const pkce = await signCookie(
"pkceCodeVerifier",
code_verifier,
maxAge,
options
)
return { code_challenge, pkce }
}
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
async function createNonce(options: InternalOptions<"oauth">) {
const raw = o.generateRandomNonce()
const maxAge = NONCE_MAX_AGE
return await signCookie("nonce", raw, maxAge, options)
}

@@ -0,6 +1,4 @@

import * as checks from "./checks.js"
import * as o from "oauth4webapi"
import { OAuthCallbackError, OAuthProfileParseError } from "../../errors.js"
import { useNonce } from "./nonce-handler.js"
import { usePKCECodeVerifier } from "./pkce-handler.js"
import { useState } from "./state-handler.js"

@@ -76,3 +74,3 @@ import type {

const state = await useState(cookies, resCookies, options)
const state = await checks.state.use(cookies, resCookies, options)

@@ -95,3 +93,3 @@ const parameters = o.validateAuthResponse(

const codeVerifier = await usePKCECodeVerifier(
const codeVerifier = await checks.pkce.use(
cookies?.[options.cookies.pkceCodeVerifier.name],

@@ -104,3 +102,6 @@ options

// TODO:
const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options)
const nonce = await checks.nonce.use(
cookies?.[options.cookies.nonce.name],
options
)
if (nonce && provider.type === "oidc") {

@@ -110,3 +111,3 @@ resCookies.push(nonce.cookie)

const codeGrantResponse = await o.authorizationCodeGrantRequest(
let codeGrantResponse = await o.authorizationCodeGrantRequest(
as,

@@ -119,2 +120,8 @@ client,

if (provider.token?.conform) {
codeGrantResponse =
(await provider.token.conform(codeGrantResponse.clone())) ??
codeGrantResponse
}
let challenges: o.WWWAuthenticateChallenge[] | undefined

@@ -121,0 +128,0 @@ if ((challenges = o.parseWwwAuthenticateChallenges(codeGrantResponse))) {

@@ -11,3 +11,4 @@ export default `:root {

.__next-auth-theme-light {
--color-background: #fff;
--color-background: #ececec;
--color-background-card: #fff;
--color-text: #000;

@@ -22,3 +23,4 @@ --color-primary: #444;

.__next-auth-theme-dark {
--color-background: #000;
--color-background: #161b22;
--color-background-card: #0d1117;
--color-text: #fff;

@@ -34,3 +36,4 @@ --color-primary: #ccc;

.__next-auth-theme-auto {
--color-background: #000;
--color-background: #161b22;
--color-background-card: #0d1117;
--color-text: #fff;

@@ -84,6 +87,5 @@ --color-primary: #ccc;

border: var(--border-width) solid var(--color-control-border);
background: var(--color-background);
background: var(--color-background-card);
font-size: 1rem;
border-radius: var(--border-radius);
box-shadow: inset 0 0.1rem 0.2rem rgba(0, 0, 0, 0.2);
color: var(--color-text);

@@ -114,2 +116,6 @@ }

button span {
flex-grow: 1;
}
button,

@@ -120,3 +126,3 @@ a.button {

color: var(--provider-color, var(--color-primary));
background-color: var(--provider-bg, var(--color-background));
background-color: var(--provider-bg, var(--color-background-card));
font-size: 1.1rem;

@@ -127,4 +133,2 @@ min-height: 62px;

transition: all 0.1s ease-in-out;
box-shadow: #000 0px 0px 0px 0px, #000 0px 0px 0px 0px,
rgba(0, 0, 0, 0.2) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px;
font-weight: 500;

@@ -137,10 +141,10 @@ position: relative;

button:has(img), a.button:has(img) {
justify-content: unset;
@media (max-width: 450px) {
button,
a.button {
font-size: 0.9rem
}
}
button:has(img) span, a.button:has(img) span {
flex-grow: 1;
}
button:hover, a.button:hover {

@@ -151,5 +155,2 @@ cursor: pointer;

button:active, a.button:active {
box-shadow: 0 0.15rem 0.3rem rgba(0, 0, 0, 0.15),
inset 0 0.1rem 0.2rem var(--color-background),
inset 0 -0.1rem 0.1rem rgba(0, 0, 0, 0.1);
cursor: pointer;

@@ -159,2 +160,3 @@ }

button #provider-logo, a.button #provider-logo {
width: 25px;
display: block;

@@ -167,2 +169,8 @@ }

#submitButton {
color: var(--button-text-color, var(--color-info-text));
background-color: var(--brand-color, var(--color-info));
width: 100%;
}
@media (prefers-color-scheme: dark) {

@@ -173,6 +181,2 @@ button,

background-color: var(--provider-dark-bg, var(--color-background));
border: 1px solid #0d0d0d;
box-shadow: #000 0px 0px 0px 0px, #ccc 0px 0px 0px 0px,
rgba(255, 255, 255, 0.01) 0px 5px 5px -3px,
rgba(255, 255, 255, 0.05) 0px 4px 6px -4px;
}

@@ -183,2 +187,3 @@ #provider-logo {

#provider-logo-dark {
width: 25px;
display: block !important;

@@ -211,3 +216,2 @@ }

text-align: center;
padding: 0.5rem;
}

@@ -236,3 +240,3 @@

border-top: 1px solid var(--color-seperator);
margin: 1.5em auto 0 auto;
margin: 2rem auto 1rem auto;
overflow: visible;

@@ -243,7 +247,7 @@ }

content: "or";
background: var(--color-background);
background: var(--color-background-card);
color: #888;
padding: 0 0.4rem;
position: relative;
top: -0.6rem;
top: -0.7rem;
}

@@ -255,3 +259,3 @@

border-radius: 0.3rem;
background: var(--color-info);
background: var(--color-error);
}

@@ -285,20 +289,30 @@

}
.signout .message {
margin-bottom: 1.5rem;
}
.logo {
display: inline-block;
margin-top: 100px;
max-width: 300px;
max-height: 150px;
max-width: 150px;
margin-top: 20px;
margin-bottom: 25px;
max-height: 70px;
}
@media screen and (min-width: 450px) {
.card {
max-width: -moz-max-content;
max-width: max-content;
border: 1px solid var(--color-control-border);
border-radius: 5px;
width: 350px
}
}
@media screen and (max-width: 450px) {
.card {
width: 200px
}
}
.card {
margin: 20px 0 20px 0;
background-color: var(--color-background-card);
border-radius: 30px;
padding: 20px 50px;
margin: 50px auto;
}

@@ -311,5 +325,5 @@

.section-header {
color: var(--brand-color, var(--color-text));
color: var(--color-text);
}
`
// Generated by `pnpm css`

@@ -99,4 +99,7 @@ import { merge } from "./utils/merge.js"

const url = new URL(e?.url ?? "https://authjs.dev")
for (const k in e?.params) url.searchParams.set(k, e?.params[k])
return { url, request: e?.request }
for (const k in e?.params) {
if (e?.params && k === "claims") e.params[k] = JSON.stringify(e.params[k])
url.searchParams.set(k, e?.params[k])
}
return { url, request: e?.request, conform: e?.conform }
}
import { handleLogin } from "../callback-handler.js"
import { CallbackRouteError } from "../../errors.js"
import { CallbackRouteError, Verification } from "../../errors.js"
import { handleOAuth } from "../oauth/callback.js"

@@ -11,3 +11,2 @@ import { createHash } from "../web.js"

ResponseInternal,
User,
InternalOptions,

@@ -116,13 +115,18 @@ Account,

// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Clear cookies if token is null
if (token === null) {
cookies.push(...sessionStore.clean())
} else {
// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
}
} else {

@@ -160,5 +164,9 @@ // Save Session Token in cookie

// If these are missing, the sign-in URL was manually opened without these params or the `sendVerificationRequest` method did not send the link correctly in the email.
if (!token || !identifier) {
return { redirect: `${url}/error?error=configuration`, cookies }
const e = new TypeError(
"Missing token or email. The sign-in URL was manually opened without token/identifier or the link was not sent correctly in the email.",
{ cause: { hasToken: !!token, hasEmail: !!identifier } }
)
e.name = "Configuration"
throw e
}

@@ -173,6 +181,6 @@

const invalidInvite = !invite || invite.expires.valueOf() < Date.now()
if (invalidInvite) {
return { redirect: `${url}/error?error=Verification`, cookies }
}
const hasInvite = !!invite
const expired = invite ? invite.expires.valueOf() < Date.now() : undefined
const invalidInvite = !hasInvite || expired
if (invalidInvite) throw new Verification({ hasInvite, expired })

@@ -218,13 +226,18 @@ // @ts-expect-error -- Verified in `assertConfig`.

// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Clear cookies if token is null
if (token === null) {
cookies.push(...sessionStore.clean())
} else {
// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
}
} else {

@@ -261,29 +274,18 @@ // Save Session Token in cookie

let user: User | null
try {
// TODO: Forward the original request as is, instead of reconstructing it
// TODO: Forward the original request as is, instead of reconstructing it
Object.entries(query ?? {}).forEach(([k, v]) =>
url.searchParams.set(k, v)
)
const user = await provider.authorize(
credentials,
// prettier-ignore
Object.entries(query ?? {}).forEach(([k, v]) => url.searchParams.set(k, v))
user = await provider.authorize(
credentials,
// prettier-ignore
new Request(url, { headers, method, body: JSON.stringify(body) })
)
if (!user) {
return {
status: 401,
redirect: `${url}/error?${new URLSearchParams({
error: "CredentialsSignin",
provider: provider.id,
})}`,
cookies,
}
}
} catch (e) {
new Request(url, { headers, method, body: JSON.stringify(body) })
)
if (!user) {
return {
status: 401,
redirect: `${url}/error?error=${encodeURIComponent(
(e as Error).message
)}`,
redirect: `${url}/error?${new URLSearchParams({
error: "CredentialsSignin",
provider: provider.id,
})}`,
cookies,

@@ -322,14 +324,19 @@ }

// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Clear cookies if token is null
if (token === null) {
cookies.push(...sessionStore.clean())
} else {
// Encode token
const newToken = await jwt.encode({ ...jwt, token })
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
// Set cookie expiry date
const cookieExpires = new Date()
cookieExpires.setTime(cookieExpires.getTime() + sessionMaxAge * 1000)
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
const sessionCookies = sessionStore.chunk(newToken, {
expires: cookieExpires,
})
cookies.push(...sessionCookies)
cookies.push(...sessionCookies)
}

@@ -336,0 +343,0 @@ // @ts-expect-error

@@ -51,23 +51,28 @@ import { JWTSessionError, SessionTokenError } from "../../errors.js"

const token = await callbacks.jwt({ token: decodedToken })
// @ts-expect-error
const newSession = await callbacks.session({ session, token })
// Return session payload as response
response.body = newSession
if (token !== null) {
// @ts-expect-error
const newSession = await callbacks.session({ session, token })
// Refresh JWT expiry by re-signing it, with an updated expiry date
const newToken = await jwt.encode({
...jwt,
token,
maxAge: options.session.maxAge,
})
// Return session payload as response
response.body = newSession
// Set cookie, to also update expiry date on cookie
const sessionCookies = sessionStore.chunk(newToken, {
expires: newExpires,
})
// Refresh JWT expiry by re-signing it, with an updated expiry date
const newToken = await jwt.encode({
...jwt,
token,
maxAge: options.session.maxAge,
})
response.cookies?.push(...sessionCookies)
// Set cookie, to also update expiry date on cookie
const sessionCookies = sessionStore.chunk(newToken, {
expires: newExpires,
})
await events.session?.({ session: newSession, token })
response.cookies?.push(...sessionCookies)
await events.session?.({ session: newSession, token })
} else {
response.cookies?.push(...sessionStore.clean())
}
} catch (e) {

@@ -74,0 +79,0 @@ logger.error(new JWTSessionError(e as Error))

import { AuthError } from "../../errors.js"
export type WarningCode = "debug_enabled"
export type WarningCode = "debug-enabled" | "csrf-disabled"

@@ -24,10 +24,20 @@ /**

const url = `https://errors.authjs.dev#${error.name.toLowerCase()}`
console.error(error.stack)
console.error(
`${red}[auth][error][${error.name}]${reset}: Read more at ${url}`
`${red}[auth][error][${error.name}]${reset}:${
error.message ? ` ${error.message}.` : ""
} Read more at ${url}`
)
error.metadata && console.error(JSON.stringify(error.metadata, null, 2))
if (error.cause) {
const { err, ...data } = error.cause as any
console.error(`${red}[auth][cause]${reset}:`, (err as Error).stack)
console.error(
`${red}[auth][details]${reset}:`,
JSON.stringify(data, null, 2)
)
} else if (error.stack) {
console.error(error.stack.replace(/.*/, "").substring(1))
}
},
warn(code) {
const url = `https://errors.authjs.dev#${code}`
const url = `https://warnings.authjs.dev#${code}`
console.warn(`${yellow}[auth][warn][${code}]${reset}`, `Read more: ${url}`)

@@ -34,0 +44,0 @@ },

@@ -0,1 +1,13 @@

/**
* <div style={{backgroundColor: "#000", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Apple</b> integration.</span>
* <a href="https://apple.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/apple-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/apple
*/
import type { OAuthConfig, OAuthUserConfig } from "./index.js"

@@ -2,0 +14,0 @@

@@ -1,13 +0,126 @@

import type { OAuthConfig, OAuthUserConfig } from "./index.js"
/**
* <div style={{backgroundColor: "#EB5424", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Auth0</b> integration.</span>
* <a href="https://auth0.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/auth0-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/auth0
*/
export interface Auth0Profile extends Record<string, any> {
import type { OIDCConfig, OIDCUserConfig } from "./index.js"
/** @see [User Profile Structure](https://auth0.com/docs/manage-users/user-accounts/user-profiles/user-profile-structure) */
export interface Auth0Profile {
/** The user's unique identifier. */
sub: string
/** Custom fields that store info about a user that influences the user's access, such as support plan, security roles (if not using the Authorization Core feature set), or access control groups. To learn more, read Metadata Overview. */
app_metadata: object
/** Indicates whether the user has been blocked. Importing enables subscribers to ensure that users remain blocked when migrating to Auth0. */
blocked: boolean
/** Timestamp indicating when the user profile was first created. */
created_at: Date
/** (unique) The user's email address. */
email: string
/** Indicates whether the user has verified their email address. */
email_verified: boolean
/** The user's family name. */
family_name: string
/** The user's given name. */
given_name: string
/** Custom fields that store info about a user that does not impact what they can or cannot access, such as work address, home address, or user preferences. To learn more, read Metadata Overview. */
user_metadata: object
/** (unique) The user's username. */
username: string
/** Contains info retrieved from the identity provider with which the user originally authenticates. Users may also link their profile to multiple identity providers; those identities will then also appear in this array. The contents of an individual identity provider object varies by provider. In some cases, it will also include an API Access Token to be used with the provider. */
identities: Array<{
/** Name of the Auth0 connection used to authenticate the user. */
connection: string
/** Indicates whether the connection is a social one. */
isSocial: boolean
/** Name of the entity that is authenticating the user, such as Facebook, Google, SAML, or your own provider. */
provider: string
/** User's unique identifier for this connection/provider. */
user_id: string
/** User info associated with the connection. When profiles are linked, it is populated with the associated user info for secondary accounts. */
profileData: object
[key: string]: any
}>
/** IP address associated with the user's last login. */
last_ip: string
/** Timestamp indicating when the user last logged in. If a user is blocked and logs in, the blocked session updates last_login. If you are using this property from inside a Rule using the user< object, its value will be associated with the login that triggered the rule; this is because rules execute after login. */
last_login: Date
/** Timestamp indicating the last time the user's password was reset/changed. At user creation, this field does not exist. This property is only available for Database connections. */
last_password_reset: Date
/** Number of times the user has logged in. If a user is blocked and logs in, the blocked session is counted in logins_count. */
logins_count: number
/** List of multi-factor providers with which the user is enrolled. */
multifactor: string
/** The user's full name. */
name: string
/** The user's nickname. */
nickname: string
email: string
/** The user's phone number. Only valid for users with SMS connections. */
phone_number: string
/** Indicates whether the user has been verified their phone number. Only valid for users with SMS connections. */
phone_verified: boolean
/** URL pointing to the user's profile picture. */
picture: string
/** Timestamp indicating when the user's profile was last updated/modified. Changes to last_login are considered updates, so most of the time, updated_at will match last_login. */
updated_at: Date
/** (unique) The user's identifier. Importing allows user records to be synchronized across multiple systems without using mapping tables. */
user_id: string
}
export default function Auth0<P extends Auth0Profile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
/**
* Add Auth0 login to your page.
*
* ## Example
*
* ```ts
* import { Auth } from "@auth/core"
* import Auth0 from "@auth/core/providers/auth0"
*
* const request = new Request("https://example.com")
* const resposne = await Auth(request, {
* providers: [Auth0({ clientId: "", clientSecret: "", issuer: "" })],
* })
* ```
*
* ---
*
* ## Resources
*
* - [Authenticate - Auth0 docs](https://auth0.com/docs/authenticate)
*
* ---
*
* ## Notes
*
* By default, Auth.js assumes that the Auth0 provider is
* based on the [OIDC](https://openid.net/specs/openid-connect-core-1_0.html) specification.
*
* :::tip
*
* The Auth0 provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/auth0.ts).
* To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/providers/custom-provider#override-default-options).
*
* :::
*
* :::info **Disclaimer**
*
* If you think you found a bug in the default configuration, you can [open an issue](https://authjs.dev/new/provider-issue).
*
* Auth.js strictly adheres to the specification and it cannot take responsibility for any deviation from
* the spec by the provider. You can open an issue, but if the problem is non-compliance with the spec,
* we might not pursue a resolution. You can ask for more help in [Discussions](https://authjs.dev/new/github-discussions).
*
* :::
*/
export default function Auth0(
config: OIDCUserConfig<Auth0Profile>
): OIDCConfig<Auth0Profile> {
return {

@@ -25,4 +138,4 @@ id: "auth0",

},
options,
options: config,
}
}

@@ -37,19 +37,18 @@ import type { OAuthConfig, OAuthUserConfig } from "./index.js"

// Confirm that profile photo was returned
if (response.ok) {
const pictureBuffer = await response.arrayBuffer()
const pictureBase64 = Buffer.from(pictureBuffer).toString("base64")
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: `data:image/jpeg;base64, ${pictureBase64}`,
}
} else {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: null,
}
let image
// TODO: Do this without Buffer
if (response.ok && typeof Buffer !== "undefined") {
try {
const pictureBuffer = await response.arrayBuffer()
const pictureBase64 = Buffer.from(pictureBuffer).toString("base64")
image = `data:image/jpeg;base64, ${pictureBase64}`
} catch {}
}
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: image ?? null,
}
},

@@ -56,0 +55,0 @@ style: {

@@ -0,4 +1,16 @@

/**
* <div style={{backgroundColor: "#24292f", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>GitHub</b> integration.</span>
* <a href="https://github.com">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/github-dark.svg" height="48" width="48"/>
* </a>
* </div>
*
* ---
* @module providers/github
*/
import type { OAuthConfig, OAuthUserConfig } from "./index.js"
export interface GithubEmail extends Record<string, any> {
export interface GitHubEmail {
email: string

@@ -11,3 +23,3 @@ primary: boolean

/** @see [Get the authenticated user](https://docs.github.com/en/rest/users/users#get-the-authenticated-user) */
export interface GithubProfile extends Record<string, any> {
export interface GitHubProfile {
login: string

@@ -65,10 +77,8 @@ id: number

*
* @example
*
* ```ts
* import Auth from "@auth/core"
* import { GitHub } from "@auth/core/providers/github"
* import { Auth } from "@auth/core"
* import GitHub from "@auth/core/providers/github"
*
* const request = new Request("https://example.com")
* const resposne = await AuthHandler(request, {
* const resposne = await Auth(request, {
* providers: [GitHub({ clientId: "", clientSecret: "" })],

@@ -108,5 +118,5 @@ * })

*/
export default function GitHub<Profile extends GithubProfile>(
options: OAuthUserConfig<Profile>
): OAuthConfig<Profile> {
export default function GitHub(
config: OAuthUserConfig<GitHubProfile>
): OAuthConfig<GitHubProfile> {
return {

@@ -136,3 +146,3 @@ id: "github",

if (res.ok) {
const emails: GithubEmail[] = await res.json()
const emails: GitHubEmail[] = await res.json()
profile.email = (emails.find((e) => e.primary) ?? emails[0]).email

@@ -157,8 +167,8 @@ }

bg: "#fff",
bgDark: "#000",
bgDark: "#24292f",
text: "#000",
textDark: "#fff",
},
options,
options: config,
}
}

@@ -45,2 +45,4 @@ import type { Client } from "oauth4webapi"

request?: EndpointRequest<C, R, P>
/** @internal */
conform?: (response: Response) => Awaitable<Response | undefined>
}

@@ -83,4 +85,4 @@

export type ProfileCallback<P> = (
profile: P,
export type ProfileCallback<Profile> = (
profile: Profile,
tokens: TokenSet

@@ -99,3 +101,5 @@ ) => Awaitable<User>

/** TODO: */
export interface OAuth2Config<P> extends CommonProviderOptions, PartialIssuer {
export interface OAuth2Config<Profile>
extends CommonProviderOptions,
PartialIssuer {
/**

@@ -140,3 +144,3 @@ * Identifies the provider when you want to sign in to

*/
profile?: ProfileCallback<P>
profile?: ProfileCallback<Profile>
/**

@@ -166,11 +170,12 @@ * The CSRF protection performed on the callback endpoint.

*/
options?: OAuthUserConfig<P>
options?: OAuthUserConfig<Profile>
}
/** TODO: */
export interface OIDCConfig<P> extends Omit<OAuth2Config<P>, "type"> {
export interface OIDCConfig<Profile>
extends Omit<OAuth2Config<Profile>, "type"> {
type: "oidc"
}
export type OAuthConfig<P> = OIDCConfig<P> | OAuth2Config<P>
export type OAuthConfig<Profile> = OIDCConfig<Profile> | OAuth2Config<Profile>

@@ -180,15 +185,29 @@ export type OAuthEndpointType = "authorization" | "token" | "userinfo"

/**
* We parsesd `authorization`, `token` and `userinfo`
* We parsed `authorization`, `token` and `userinfo`
* to always contain a valid `URL`, with the params
* @internal
*/
export type OAuthConfigInternal<P> = Omit<OAuthConfig<P>, OAuthEndpointType> & {
export type OAuthConfigInternal<Profile> = Omit<
OAuthConfig<Profile>,
OAuthEndpointType
> & {
authorization?: { url: URL }
token?: { url: URL; request?: TokenEndpointHandler["request"] }
token?: {
url: URL
request?: TokenEndpointHandler["request"]
conform?: TokenEndpointHandler["conform"]
}
userinfo?: { url: URL; request?: UserinfoEndpointHandler["request"] }
} & Pick<Required<OAuthConfig<P>>, "clientId" | "checks" | "profile">
} & Pick<Required<OAuthConfig<Profile>>, "clientId" | "checks" | "profile">
export type OAuthUserConfig<P> = Omit<
Partial<OAuthConfig<P>>,
export type OAuthUserConfig<Profile> = Omit<
Partial<OAuthConfig<Profile>>,
"options" | "type"
> &
Required<Pick<OAuthConfig<P>, "clientId" | "clientSecret">>
Required<Pick<OAuthConfig<Profile>, "clientId" | "clientSecret">>
export type OIDCUserConfig<Profile> = Omit<
Partial<OIDCConfig<Profile>>,
"options" | "type"
> &
Required<Pick<OIDCConfig<Profile>, "clientId" | "clientSecret">>

@@ -1,2 +0,2 @@

import type { OAuthConfig, OAuthUserConfig } from "./index.js"
import type { OIDCConfig, OIDCUserConfig } from "./index.js"

@@ -10,5 +10,5 @@ export interface TwitchProfile extends Record<string, any> {

export default function Twitch<P extends TwitchProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
export default function Twitch(
config: OIDCUserConfig<TwitchProfile>
): OIDCConfig<TwitchProfile> {
return {

@@ -19,2 +19,3 @@ issuer: "https://id.twitch.tv/oauth2",

type: "oidc",
client: { token_endpoint_auth_method: "client_secret_post" },
authorization: {

@@ -24,10 +25,35 @@ params: {

claims: {
id_token: {
email: null,
picture: null,
preferred_username: null,
},
id_token: { email: null, picture: null, preferred_username: null },
},
},
},
token: {
async conform(response) {
const body = await response.json()
if (response.ok) {
if (typeof body.scope === "string") {
console.warn(
"'scope' is a string. Redundant workaround, please open an issue."
)
} else if (Array.isArray(body.scope)) {
body.scope = body.scope.join(" ")
return new Response(JSON.stringify(body), response)
} else if ("scope" in body) {
delete body.scope
return new Response(JSON.stringify(body), response)
}
} else {
const { message: error_description, error } = body
if (typeof error !== "string") {
return new Response(
JSON.stringify({ error: "invalid_request", error_description }),
response
)
}
console.warn(
"Response has 'error'. Redundant workaround, please open an issue."
)
}
},
},
style: {

@@ -41,4 +67,4 @@ logo: "/twitch.svg",

},
options,
options: config,
}
}

@@ -1,118 +0,49 @@

// TODO: move OAuth 1.0 support or remove it?
import type { OAuthConfig, OAuthUserConfig } from "./index.js"
export interface TwitterLegacyProfile {
id: number
id_str: string
name: string
screen_name: string
location: string
description: string
url: string
entities: {
url: {
urls: Array<{
url: string
expanded_url: string
display_url: string
indices: number[]
}>
}
description: {
urls: any[]
}
}
protected: boolean
followers_count: number
friends_count: number
listed_count: number
created_at: string
favourites_count: number
utc_offset?: any
time_zone?: any
geo_enabled: boolean
verified: boolean
statuses_count: number
lang?: any
status: {
created_at: string
id: number
id_str: string
text: string
truncated: boolean
entities: {
hashtags: any[]
symbols: any[]
user_mentions: Array<{
screen_name: string
name: string
id: number
id_str: string
indices: number[]
}>
urls: any[]
}
source: string
in_reply_to_status_id: number
in_reply_to_status_id_str: string
in_reply_to_user_id: number
in_reply_to_user_id_str: string
in_reply_to_screen_name: string
geo?: any
coordinates?: any
place?: any
contributors?: any
is_quote_status: boolean
retweet_count: number
favorite_count: number
favorited: boolean
retweeted: boolean
lang: string
}
contributors_enabled: boolean
is_translator: boolean
is_translation_enabled: boolean
profile_background_color: string
profile_background_image_url: string
profile_background_image_url_https: string
profile_background_tile: boolean
profile_image_url: string
profile_image_url_https: string
profile_banner_url: string
profile_link_color: string
profile_sidebar_border_color: string
profile_sidebar_fill_color: string
profile_text_color: string
profile_use_background_image: boolean
has_extended_profile: boolean
default_profile: boolean
default_profile_image: boolean
following: boolean
follow_request_sent: boolean
notifications: boolean
translator_type: string
withheld_in_countries: any[]
suspended: boolean
needs_phone_verification: boolean
}
/**
* [Documentation](https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me)
* [Users lookup](https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me)
*/
export interface TwitterProfile {
data: {
/**
* Unique identifier of this user. This is returned as a string in order to avoid complications with languages and tools
* that cannot handle large integers.
*/
id: string
/** The friendly name of this user, as shown on their profile. */
name: string
/** @note Email is currently unsupported by Twitter. */
email?: string
/** The Twitter handle (screen name) of this user. */
username: string
/**
* The location specified in the user's profile, if the user provided one.
* As this is a freeform value, it may not indicate a valid location, but it may be fuzzily evaluated when performing searches with location queries.
*
* To return this field, add `user.fields=location` in the authorization request's query parameter.
*/
location?: string
/**
* This object and its children fields contain details about text that has a special meaning in the user's description.
*
*To return this field, add `user.fields=entities` in the authorization request's query parameter.
*/
entities?: {
/** Contains details about the user's profile website. */
url: {
/** Contains details about the user's profile website. */
urls: Array<{
/** The start position (zero-based) of the recognized user's profile website. All start indices are inclusive. */
start: number
/** The end position (zero-based) of the recognized user's profile website. This end index is exclusive. */
end: number
/** The URL in the format entered by the user. */
url: string
/** The fully resolved URL. */
expanded_url: string
/** The URL as displayed in the user's profile. */
display_url: string
}>
}
/** Contains details about URLs, Hashtags, Cashtags, or mentions located within a user's description. */
description: {

@@ -126,7 +57,28 @@ hashtags: Array<{

}
/**
* Indicate if this user is a verified Twitter user.
*
* To return this field, add `user.fields=verified` in the authorization request's query parameter.
*/
verified?: boolean
/**
* The text of this user's profile description (also known as bio), if the user provided one.
*
* To return this field, add `user.fields=description` in the authorization request's query parameter.
*/
description?: string
/**
* The URL specified in the user's profile, if present.
*
* To return this field, add `user.fields=url` in the authorization request's query parameter.
*/
url?: string
/** The URL to the profile image for this user, as shown on the user's profile. */
profile_image_url?: string
protected?: boolean
/**
* Unique identifier of this user's pinned Tweet.
*
* You can obtain the expanded object in `includes.tweets` by adding `expansions=pinned_tweet_id` in the authorization request's query parameter.
*/
pinned_tweet_id?: string

@@ -143,5 +95,5 @@ created_at?: string

export default function Twitter<
P extends Record<string, any> = TwitterLegacyProfile | TwitterProfile
>(options: OAuthUserConfig<P> & { version?: "2.0" }): OAuthConfig<P> {
export default function Twitter(
config: OAuthUserConfig<TwitterProfile>
): OAuthConfig<TwitterProfile> {
return {

@@ -152,23 +104,7 @@ id: "twitter",

checks: ["pkce", "state"],
authorization: {
url: "https://twitter.com/i/oauth2/authorize",
params: { scope: "users.read tweet.read offline.access" },
},
token: {
url: "https://api.twitter.com/2/oauth2/token",
// @ts-expect-error TODO: Remove this
async request({ client, params, checks, provider }) {
const response = await client.oauthCallback(
provider.callbackUrl,
params,
checks,
{ exchangeBody: { client_id: options.clientId } }
)
return { tokens: response }
},
},
userinfo: {
url: "https://api.twitter.com/2/users/me",
params: { "user.fields": "profile_image_url" },
},
authorization:
"https://twitter.com/i/oauth2/authorize?scope=users.read tweet.read offline.access",
token: "https://api.twitter.com/2/oauth2/token",
userinfo:
"https://api.twitter.com/2/users/me?user.fields=profile_image_url",
profile({ data }) {

@@ -178,4 +114,3 @@ return {

name: data.name,
// NOTE: E-mail is currently unsupported by OAuth 2 Twitter.
email: null,
email: data.email ?? null,
image: data.profile_image_url,

@@ -192,4 +127,4 @@ }

},
options,
options: config,
}
}

@@ -206,2 +206,7 @@ /**

* Anything else will be kept inaccessible from the client.
*
* Returning `null` will invalidate the JWT session by clearing
* the user's cookies. You'll still have to monitor and invalidate
* unexpired tokens from future requests yourself to prevent
* unauthorized access.
*

@@ -219,3 +224,3 @@ * By default the JWT is encrypted.

isNewUser?: boolean
}) => Awaitable<JWT>
}) => Awaitable<JWT|null>
}

@@ -222,0 +227,0 @@

@@ -188,2 +188,7 @@ /**

*
* Returning `null` will invalidate the JWT session by clearing
* the user's cookies. You'll still have to monitor and invalidate
* unexpired tokens from future requests yourself to prevent
* unauthorized access.
*
* By default the JWT is encrypted.

@@ -200,3 +205,3 @@ *

isNewUser?: boolean;
}) => Awaitable<JWT>;
}) => Awaitable<JWT | null>;
}

@@ -203,0 +208,0 @@ /** [Documentation](https://authjs.dev/reference/configuration/auth-config#cookies) */

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc