172 lines
5.8 KiB
JavaScript
172 lines
5.8 KiB
JavaScript
import * as jwt from "../jwt.js";
|
|
import { createCallbackUrl } from "./utils/callback-url.js";
|
|
import * as cookie from "./utils/cookie.js";
|
|
import { createCSRFToken } from "./actions/callback/oauth/csrf-token.js";
|
|
import { AdapterError, EventError } from "../errors.js";
|
|
import parseProviders from "./utils/providers.js";
|
|
import { setLogger } from "./utils/logger.js";
|
|
import { merge } from "./utils/merge.js";
|
|
export const defaultCallbacks = {
|
|
signIn() {
|
|
return true;
|
|
},
|
|
redirect({ url, baseUrl }) {
|
|
if (url.startsWith("/"))
|
|
return `${baseUrl}${url}`;
|
|
else if (new URL(url).origin === baseUrl)
|
|
return url;
|
|
return baseUrl;
|
|
},
|
|
session({ session }) {
|
|
return {
|
|
user: {
|
|
name: session.user?.name,
|
|
email: session.user?.email,
|
|
image: session.user?.image,
|
|
},
|
|
expires: session.expires?.toISOString?.() ?? session.expires,
|
|
};
|
|
},
|
|
jwt({ token }) {
|
|
return token;
|
|
},
|
|
};
|
|
/** Initialize all internal options and cookies. */
|
|
export async function init({ authOptions: config, providerId, action, url, cookies: reqCookies, callbackUrl: reqCallbackUrl, csrfToken: reqCsrfToken, csrfDisabled, isPost, }) {
|
|
const logger = setLogger(config);
|
|
const { providers, provider } = parseProviders({ url, providerId, config });
|
|
const maxAge = 30 * 24 * 60 * 60; // Sessions expire after 30 days of being idle by default
|
|
let isOnRedirectProxy = false;
|
|
if ((provider?.type === "oauth" || provider?.type === "oidc") &&
|
|
provider.redirectProxyUrl) {
|
|
try {
|
|
isOnRedirectProxy =
|
|
new URL(provider.redirectProxyUrl).origin === url.origin;
|
|
}
|
|
catch {
|
|
throw new TypeError(`redirectProxyUrl must be a valid URL. Received: ${provider.redirectProxyUrl}`);
|
|
}
|
|
}
|
|
// User provided options are overridden by other options,
|
|
// except for the options with special handling above
|
|
const options = {
|
|
debug: false,
|
|
pages: {},
|
|
theme: {
|
|
colorScheme: "auto",
|
|
logo: "",
|
|
brandColor: "",
|
|
buttonText: "",
|
|
},
|
|
// Custom options override defaults
|
|
...config,
|
|
// These computed settings can have values in userOptions but we override them
|
|
// and are request-specific.
|
|
url,
|
|
action,
|
|
// @ts-expect-errors
|
|
provider,
|
|
cookies: merge(cookie.defaultCookies(config.useSecureCookies ?? url.protocol === "https:"), config.cookies),
|
|
providers,
|
|
// Session options
|
|
session: {
|
|
// If no adapter specified, force use of JSON Web Tokens (stateless)
|
|
strategy: config.adapter ? "database" : "jwt",
|
|
maxAge,
|
|
updateAge: 24 * 60 * 60,
|
|
generateSessionToken: () => crypto.randomUUID(),
|
|
...config.session,
|
|
},
|
|
// JWT options
|
|
jwt: {
|
|
secret: config.secret, // Asserted in assert.ts
|
|
maxAge: config.session?.maxAge ?? maxAge, // default to same as `session.maxAge`
|
|
encode: jwt.encode,
|
|
decode: jwt.decode,
|
|
...config.jwt,
|
|
},
|
|
// Event messages
|
|
events: eventsErrorHandler(config.events ?? {}, logger),
|
|
adapter: adapterErrorHandler(config.adapter, logger),
|
|
// Callback functions
|
|
callbacks: { ...defaultCallbacks, ...config.callbacks },
|
|
logger,
|
|
callbackUrl: url.origin,
|
|
isOnRedirectProxy,
|
|
experimental: {
|
|
...config.experimental,
|
|
},
|
|
};
|
|
// Init cookies
|
|
const cookies = [];
|
|
if (csrfDisabled) {
|
|
options.csrfTokenVerified = true;
|
|
}
|
|
else {
|
|
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,
|
|
});
|
|
}
|
|
}
|
|
const { callbackUrl, callbackUrlCookie } = await createCallbackUrl({
|
|
options,
|
|
cookieValue: reqCookies?.[options.cookies.callbackUrl.name],
|
|
paramValue: reqCallbackUrl,
|
|
});
|
|
options.callbackUrl = callbackUrl;
|
|
if (callbackUrlCookie) {
|
|
cookies.push({
|
|
name: options.cookies.callbackUrl.name,
|
|
value: callbackUrlCookie,
|
|
options: options.cookies.callbackUrl.options,
|
|
});
|
|
}
|
|
return { options, cookies };
|
|
}
|
|
/** Wraps an object of methods and adds error handling. */
|
|
function eventsErrorHandler(methods, logger) {
|
|
return Object.keys(methods).reduce((acc, name) => {
|
|
acc[name] = async (...args) => {
|
|
try {
|
|
const method = methods[name];
|
|
return await method(...args);
|
|
}
|
|
catch (e) {
|
|
logger.error(new EventError(e));
|
|
}
|
|
};
|
|
return acc;
|
|
}, {});
|
|
}
|
|
/** Handles adapter induced errors. */
|
|
function adapterErrorHandler(adapter, logger) {
|
|
if (!adapter)
|
|
return;
|
|
return Object.keys(adapter).reduce((acc, name) => {
|
|
acc[name] = async (...args) => {
|
|
try {
|
|
logger.debug(`adapter_${name}`, { args });
|
|
const method = adapter[name];
|
|
return await method(...args);
|
|
}
|
|
catch (e) {
|
|
const error = new AdapterError(e);
|
|
logger.error(error);
|
|
throw error;
|
|
}
|
|
};
|
|
return acc;
|
|
}, {});
|
|
}
|