@auth/core
Version:
Authentication for the Web.
166 lines (165 loc) • 7.19 kB
JavaScript
import { defaultCookies } from "./cookie.js";
import { AuthError, DuplicateConditionalUI, ExperimentalFeatureNotEnabled, InvalidCallbackUrl, InvalidEndpoints, MissingAdapter, MissingAdapterMethods, MissingAuthorize, MissingSecret, MissingWebAuthnAutocomplete, UnsupportedStrategy, UntrustedHost, } from "../../errors.js";
let warned = false;
function isValidHttpUrl(url, baseUrl) {
try {
return /^https?:/.test(new URL(url, url.startsWith("/") ? baseUrl : undefined).protocol);
}
catch {
return false;
}
}
function isSemverString(version) {
return /^v\d+(?:\.\d+){0,2}$/.test(version);
}
let hasCredentials = false;
let hasEmail = false;
let hasWebAuthn = false;
const emailMethods = [
"createVerificationToken",
"useVerificationToken",
"getUserByEmail",
];
const sessionMethods = [
"createUser",
"getUser",
"getUserByEmail",
"getUserByAccount",
"updateUser",
"linkAccount",
"createSession",
"getSessionAndUser",
"updateSession",
"deleteSession",
];
const webauthnMethods = [
"createUser",
"getUser",
"linkAccount",
"getAccount",
"getAuthenticator",
"createAuthenticator",
"listAuthenticatorsByUserId",
"updateAuthenticatorCounter",
];
/**
* Verify that the user configured Auth.js correctly.
* Good place to mention deprecations as well.
*
* This is invoked before the init method, so default values are not available yet.
*/
export function assertConfig(request, options) {
const { url } = request;
const warnings = [];
if (!warned && options.debug)
warnings.push("debug-enabled");
if (!options.trustHost) {
return new UntrustedHost(`Host must be trusted. URL was: ${request.url}`);
}
if (!options.secret?.length) {
return new MissingSecret("Please define a `secret`");
}
const callbackUrlParam = request.query?.callbackUrl;
if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, url.origin)) {
return new InvalidCallbackUrl(`Invalid callback URL. Received: ${callbackUrlParam}`);
}
const { callbackUrl: defaultCallbackUrl } = defaultCookies(options.useSecureCookies ?? url.protocol === "https:");
const callbackUrlCookie = request.cookies?.[options.cookies?.callbackUrl?.name ?? defaultCallbackUrl.name];
if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, url.origin)) {
return new InvalidCallbackUrl(`Invalid callback URL. Received: ${callbackUrlCookie}`);
}
// Keep track of webauthn providers that use conditional UI
let hasConditionalUIProvider = false;
for (const p of options.providers) {
const provider = typeof p === "function" ? p() : p;
if ((provider.type === "oauth" || provider.type === "oidc") &&
!(provider.issuer ?? provider.options?.issuer)) {
const { authorization: a, token: t, userinfo: u } = provider;
let key;
if (typeof a !== "string" && !a?.url)
key = "authorization";
else if (typeof t !== "string" && !t?.url)
key = "token";
else if (typeof u !== "string" && !u?.url)
key = "userinfo";
if (key) {
return new InvalidEndpoints(`Provider "${provider.id}" is missing both \`issuer\` and \`${key}\` endpoint config. At least one of them is required`);
}
}
if (provider.type === "credentials")
hasCredentials = true;
else if (provider.type === "email")
hasEmail = true;
else if (provider.type === "webauthn") {
hasWebAuthn = true;
// Validate simpleWebAuthnBrowserVersion
if (provider.simpleWebAuthnBrowserVersion &&
!isSemverString(provider.simpleWebAuthnBrowserVersion)) {
return new AuthError(`Invalid provider config for "${provider.id}": simpleWebAuthnBrowserVersion "${provider.simpleWebAuthnBrowserVersion}" must be a valid semver string.`);
}
if (provider.enableConditionalUI) {
// Make sure only one webauthn provider has "enableConditionalUI" set to true
if (hasConditionalUIProvider) {
return new DuplicateConditionalUI(`Multiple webauthn providers have 'enableConditionalUI' set to True. Only one provider can have this option enabled at a time`);
}
hasConditionalUIProvider = true;
// Make sure at least one formField has "webauthn" in its autocomplete param
const hasWebauthnFormField = Object.values(provider.formFields).some((f) => f.autocomplete && f.autocomplete.toString().indexOf("webauthn") > -1);
if (!hasWebauthnFormField) {
return new MissingWebAuthnAutocomplete(`Provider "${provider.id}" has 'enableConditionalUI' set to True, but none of its formFields have 'webauthn' in their autocomplete param`);
}
}
}
}
if (hasCredentials) {
const dbStrategy = options.session?.strategy === "database";
const onlyCredentials = !options.providers.some((p) => (typeof p === "function" ? p() : p).type !== "credentials");
if (dbStrategy && onlyCredentials) {
return new UnsupportedStrategy("Signing in with credentials only supported if JWT strategy is enabled");
}
const credentialsNoAuthorize = options.providers.some((p) => {
const provider = typeof p === "function" ? p() : p;
return provider.type === "credentials" && !provider.authorize;
});
if (credentialsNoAuthorize) {
return new MissingAuthorize("Must define an authorize() handler to use credentials authentication provider");
}
}
const { adapter, session } = options;
const requiredMethods = [];
if (hasEmail ||
session?.strategy === "database" ||
(!session?.strategy && adapter)) {
if (hasEmail) {
if (!adapter)
return new MissingAdapter("Email login requires an adapter");
requiredMethods.push(...emailMethods);
}
else {
if (!adapter)
return new MissingAdapter("Database session requires an adapter");
requiredMethods.push(...sessionMethods);
}
}
if (hasWebAuthn) {
// Log experimental warning
if (options.experimental?.enableWebAuthn) {
warnings.push("experimental-webauthn");
}
else {
return new ExperimentalFeatureNotEnabled("WebAuthn is an experimental feature. To enable it, set `experimental.enableWebAuthn` to `true` in your config");
}
if (!adapter)
return new MissingAdapter("WebAuthn requires an adapter");
requiredMethods.push(...webauthnMethods);
}
if (adapter) {
const missing = requiredMethods.filter((m) => !(m in adapter));
if (missing.length) {
return new MissingAdapterMethods(`Required adapter methods were missing: ${missing.join(", ")}`);
}
}
if (!warned)
warned = true;
return warnings;
}