@zpg6-test-pkgs/better-auth
Version:
The most comprehensive authentication library for TypeScript.
1,123 lines (1,116 loc) • 43 kB
JavaScript
'use strict';
const organization = require('../shared/better-auth.C_B-5MMv.cjs');
const plugins_twoFactor_index = require('./two-factor/index.cjs');
const plugins_username_index = require('./username/index.cjs');
const plugins_bearer_index = require('./bearer/index.cjs');
const session = require('../shared/better-auth.DmBU2Klq.cjs');
const socialProviders_index = require('../shared/better-auth.afydZyFs.cjs');
const plugins_magicLink_index = require('./magic-link/index.cjs');
const plugins_phoneNumber_index = require('./phone-number/index.cjs');
const plugins_anonymous_index = require('./anonymous/index.cjs');
const admin = require('../shared/better-auth.BGIivURf.cjs');
const plugins_genericOauth_index = require('./generic-oauth/index.cjs');
const plugins_jwt_index = require('./jwt/index.cjs');
const plugins_multiSession_index = require('./multi-session/index.cjs');
const plugins_emailOtp_index = require('./email-otp/index.cjs');
const plugins_oneTap_index = require('./one-tap/index.cjs');
const plugins_oauthProxy_index = require('./oauth-proxy/index.cjs');
const plugins_customSession_index = require('./custom-session/index.cjs');
const plugins_openApi_index = require('./open-api/index.cjs');
const plugins_oidcProvider_index = require('../shared/better-auth.Dzh-RJYq.cjs');
const plugins_captcha_index = require('./captcha/index.cjs');
const plugins_oneTimeToken_index = require('../shared/better-auth.B88rWezN.cjs');
const plugins_haveibeenpwned_index = require('./haveibeenpwned/index.cjs');
const z = require('zod/v4');
const betterCall = require('better-call');
const env = require('../shared/better-auth.B6fIklBU.cjs');
const base64 = require('@better-auth/utils/base64');
require('@better-auth/utils/hmac');
const logger = require('../shared/better-auth.B3274wGK.cjs');
const url = require('../shared/better-auth.DRmln2Nr.cjs');
require('@better-auth/utils/binary');
const cookies_index = require('../shared/better-auth.l2-e84v_.cjs');
require('../shared/better-auth.BIMq4RPW.cjs');
require('../shared/better-auth.CaFtZgwN.cjs');
require('./organization/access/index.cjs');
require('@better-auth/utils/random');
const hash = require('@better-auth/utils/hash');
require('@noble/ciphers/chacha');
require('@noble/ciphers/utils');
require('@noble/ciphers/webcrypto');
const jose = require('jose');
require('@noble/hashes/scrypt');
const utils = require('@better-auth/utils');
require('@better-auth/utils/hex');
require('@noble/hashes/utils');
const random = require('../shared/better-auth.CYeOI8C-.cjs');
require('kysely');
require('@better-auth/utils/otp');
require('./admin/access/index.cjs');
require('@better-fetch/fetch');
require('../shared/better-auth.DI0OyFsZ.cjs');
require('zod');
require('@noble/hashes/sha3');
const plugins_deviceAuthorization_index = require('./device-authorization/index.cjs');
const plugins_siwe_index = require('./siwe/index.cjs');
const client = require('../shared/better-auth.DnER2-iT.cjs');
const client$1 = require('../shared/better-auth.BMgeJg3r.cjs');
require('../shared/better-auth.C1hdVENX.cjs');
require('../shared/better-auth.ANpbi45u.cjs');
require('../shared/better-auth.DhsGZ30Q.cjs');
require('../shared/better-auth.D7N9YNr5.cjs');
require('../shared/better-auth.BT1jd5k2.cjs');
require('../crypto/index.cjs');
require('../shared/better-auth.CDXNofOe.cjs');
require('../shared/better-auth.vPQBmXQL.cjs');
require('jose/errors');
require('../shared/better-auth.Bg6iw3ig.cjs');
require('defu');
require('../shared/better-auth.DNqtHmvg.cjs');
require('../shared/better-auth.BW8BpneG.cjs');
require('../api/index.cjs');
require('../shared/better-auth.Cxlqz5AU.cjs');
require('../shared/better-auth.BEphVDyL.cjs');
require('./access/index.cjs');
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
const n = Object.create(null);
if (e) {
for (const k in e) {
n[k] = e[k];
}
}
n.default = e;
return n;
}
const z__namespace = /*#__PURE__*/_interopNamespaceCompat(z);
function redirectErrorURL(url, error, description) {
return `${url.includes("?") ? "&" : "?"}error=${error}&error_description=${description}`;
}
async function authorizeMCPOAuth(ctx, options) {
ctx.setHeader("Access-Control-Allow-Origin", "*");
ctx.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
ctx.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
ctx.setHeader("Access-Control-Max-Age", "86400");
const opts = {
codeExpiresIn: 600,
defaultScope: "openid",
...options,
scopes: [
"openid",
"profile",
"email",
"offline_access",
...options?.scopes || []
]
};
if (!ctx.request) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "request not found",
error: "invalid_request"
});
}
const session$1 = await session.getSessionFromCtx(ctx);
if (!session$1) {
await ctx.setSignedCookie(
"oidc_login_prompt",
JSON.stringify(ctx.query),
ctx.context.secret,
{
maxAge: 600,
path: "/",
sameSite: "lax"
}
);
const queryFromURL = ctx.request.url?.split("?")[1];
throw ctx.redirect(`${options.loginPage}?${queryFromURL}`);
}
const query = ctx.query;
console.log(query);
if (!query.client_id) {
throw ctx.redirect(`${ctx.context.baseURL}/error?error=invalid_client`);
}
if (!query.response_type) {
throw ctx.redirect(
redirectErrorURL(
`${ctx.context.baseURL}/error`,
"invalid_request",
"response_type is required"
)
);
}
const client = await ctx.context.adapter.findOne({
model: "oauthApplication",
where: [
{
field: "clientId",
value: ctx.query.client_id
}
]
}).then((res) => {
if (!res) {
return null;
}
return {
...res,
redirectURLs: res.redirectURLs.split(","),
metadata: res.metadata ? JSON.parse(res.metadata) : {}
};
});
console.log(client);
if (!client) {
throw ctx.redirect(`${ctx.context.baseURL}/error?error=invalid_client`);
}
const redirectURI = client.redirectURLs.find(
(url) => url === ctx.query.redirect_uri
);
if (!redirectURI || !query.redirect_uri) {
throw new betterCall.APIError("BAD_REQUEST", {
message: "Invalid redirect URI"
});
}
if (client.disabled) {
throw ctx.redirect(`${ctx.context.baseURL}/error?error=client_disabled`);
}
if (query.response_type !== "code") {
throw ctx.redirect(
`${ctx.context.baseURL}/error?error=unsupported_response_type`
);
}
const requestScope = query.scope?.split(" ").filter((s) => s) || opts.defaultScope.split(" ");
const invalidScopes = requestScope.filter((scope) => {
return !opts.scopes.includes(scope);
});
if (invalidScopes.length) {
throw ctx.redirect(
redirectErrorURL(
query.redirect_uri,
"invalid_scope",
`The following scopes are invalid: ${invalidScopes.join(", ")}`
)
);
}
if ((!query.code_challenge || !query.code_challenge_method) && options.requirePKCE) {
throw ctx.redirect(
redirectErrorURL(
query.redirect_uri,
"invalid_request",
"pkce is required"
)
);
}
if (!query.code_challenge_method) {
query.code_challenge_method = "plain";
}
if (![
"s256",
options.allowPlainCodeChallengeMethod ? "plain" : "s256"
].includes(query.code_challenge_method?.toLowerCase() || "")) {
throw ctx.redirect(
redirectErrorURL(
query.redirect_uri,
"invalid_request",
"invalid code_challenge method"
)
);
}
const code = random.generateRandomString(32, "a-z", "A-Z", "0-9");
const codeExpiresInMs = opts.codeExpiresIn * 1e3;
const expiresAt = new Date(Date.now() + codeExpiresInMs);
try {
await ctx.context.internalAdapter.createVerificationValue(
{
value: JSON.stringify({
clientId: client.clientId,
redirectURI: query.redirect_uri,
scope: requestScope,
userId: session$1.user.id,
authTime: new Date(session$1.session.createdAt).getTime(),
/**
* If the prompt is set to `consent`, then we need
* to require the user to consent to the scopes.
*
* This means the code now needs to be treated as a
* consent request.
*
* once the user consents, the code will be updated
* with the actual code. This is to prevent the
* client from using the code before the user
* consents.
*/
requireConsent: query.prompt === "consent",
state: query.prompt === "consent" ? query.state : null,
codeChallenge: query.code_challenge,
codeChallengeMethod: query.code_challenge_method,
nonce: query.nonce
}),
identifier: code,
expiresAt
},
ctx
);
} catch (e) {
throw ctx.redirect(
redirectErrorURL(
query.redirect_uri,
"server_error",
"An error occurred while processing the request"
)
);
}
const redirectURIWithCode = new URL(redirectURI);
redirectURIWithCode.searchParams.set("code", code);
redirectURIWithCode.searchParams.set("state", ctx.query.state);
if (query.prompt !== "consent") {
throw ctx.redirect(redirectURIWithCode.toString());
}
throw ctx.redirect(redirectURIWithCode.toString());
}
const getMCPProviderMetadata = (ctx, options) => {
const issuer = ctx.context.options.baseURL;
const baseURL = ctx.context.baseURL;
if (!issuer || !baseURL) {
throw new betterCall.APIError("INTERNAL_SERVER_ERROR", {
error: "invalid_issuer",
error_description: "issuer or baseURL is not set. If you're the app developer, please make sure to set the `baseURL` in your auth config."
});
}
return {
issuer,
authorization_endpoint: `${baseURL}/mcp/authorize`,
token_endpoint: `${baseURL}/mcp/token`,
userinfo_endpoint: `${baseURL}/mcp/userinfo`,
jwks_uri: `${baseURL}/mcp/jwks`,
registration_endpoint: `${baseURL}/mcp/register`,
scopes_supported: ["openid", "profile", "email", "offline_access"],
response_types_supported: ["code"],
response_modes_supported: ["query"],
grant_types_supported: ["authorization_code", "refresh_token"],
acr_values_supported: [
"urn:mace:incommon:iap:silver",
"urn:mace:incommon:iap:bronze"
],
subject_types_supported: ["public"],
id_token_signing_alg_values_supported: ["RS256", "none"],
token_endpoint_auth_methods_supported: [
"client_secret_basic",
"client_secret_post",
"none"
],
code_challenge_methods_supported: ["S256"],
claims_supported: [
"sub",
"iss",
"aud",
"exp",
"nbf",
"iat",
"jti",
"email",
"email_verified",
"name"
],
...options?.metadata
};
};
const mcp = (options) => {
const opts = {
codeExpiresIn: 600,
defaultScope: "openid",
accessTokenExpiresIn: 3600,
refreshTokenExpiresIn: 604800,
allowPlainCodeChallengeMethod: true,
...options.oidcConfig,
loginPage: options.loginPage,
scopes: [
"openid",
"profile",
"email",
"offline_access",
...options.oidcConfig?.scopes || []
]
};
const modelName = {
oauthClient: "oauthApplication",
oauthAccessToken: "oauthAccessToken"};
plugins_oidcProvider_index.oidcProvider(opts);
return {
id: "mcp",
hooks: {
after: [
{
matcher() {
return true;
},
handler: session.createAuthMiddleware(async (ctx) => {
const cookie = await ctx.getSignedCookie(
"oidc_login_prompt",
ctx.context.secret
);
const cookieName = ctx.context.authCookies.sessionToken.name;
const parsedSetCookieHeader = cookies_index.parseSetCookieHeader(
ctx.context.responseHeaders?.get("set-cookie") || ""
);
const hasSessionToken = parsedSetCookieHeader.has(cookieName);
if (!cookie || !hasSessionToken) {
return;
}
ctx.setCookie("oidc_login_prompt", "", {
maxAge: 0
});
const sessionCookie = parsedSetCookieHeader.get(cookieName)?.value;
const sessionToken = sessionCookie?.split(".")[0];
if (!sessionToken) {
return;
}
const session = await ctx.context.internalAdapter.findSession(sessionToken);
if (!session) {
return;
}
ctx.query = JSON.parse(cookie);
ctx.query.prompt = "consent";
ctx.context.session = session;
const response = await authorizeMCPOAuth(ctx, opts);
return response;
})
}
]
},
endpoints: {
getMcpOAuthConfig: session.createAuthEndpoint(
"/.well-known/oauth-authorization-server",
{
method: "GET",
metadata: {
client: false
}
},
async (c) => {
try {
const metadata = getMCPProviderMetadata(c, options);
return c.json(metadata);
} catch (e) {
console.log(e);
return c.json(null);
}
}
),
mcpOAuthAuthroize: session.createAuthEndpoint(
"/mcp/authorize",
{
method: "GET",
query: z__namespace.record(z__namespace.string(), z__namespace.any()),
metadata: {
openapi: {
description: "Authorize an OAuth2 request using MCP",
responses: {
"200": {
description: "Authorization response generated successfully",
content: {
"application/json": {
schema: {
type: "object",
additionalProperties: true,
description: "Authorization response, contents depend on the authorize function implementation"
}
}
}
}
}
}
}
},
async (ctx) => {
return authorizeMCPOAuth(ctx, opts);
}
),
mcpOAuthToken: session.createAuthEndpoint(
"/mcp/token",
{
method: "POST",
body: z__namespace.record(z__namespace.any(), z__namespace.any()),
metadata: {
isAction: false
}
},
async (ctx) => {
ctx.setHeader("Access-Control-Allow-Origin", "*");
ctx.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
ctx.setHeader(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
);
ctx.setHeader("Access-Control-Max-Age", "86400");
let { body } = ctx;
if (!body) {
throw ctx.error("BAD_REQUEST", {
error_description: "request body not found",
error: "invalid_request"
});
}
if (body instanceof FormData) {
body = Object.fromEntries(body.entries());
}
if (!(body instanceof Object)) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "request body is not an object",
error: "invalid_request"
});
}
let { client_id, client_secret } = body;
const authorization = ctx.request?.headers.get("authorization") || null;
if (authorization && !client_id && !client_secret && authorization.startsWith("Basic ")) {
try {
const encoded = authorization.replace("Basic ", "");
const decoded = new TextDecoder().decode(base64.base64.decode(encoded));
if (!decoded.includes(":")) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid authorization header format",
error: "invalid_client"
});
}
const [id, secret] = decoded.split(":");
if (!id || !secret) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid authorization header format",
error: "invalid_client"
});
}
client_id = id;
client_secret = secret;
} catch (error) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid authorization header format",
error: "invalid_client"
});
}
}
const {
grant_type,
code,
redirect_uri,
refresh_token,
code_verifier
} = body;
if (grant_type === "refresh_token") {
if (!refresh_token) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "refresh_token is required",
error: "invalid_request"
});
}
const token = await ctx.context.adapter.findOne({
model: "oauthAccessToken",
where: [
{
field: "refreshToken",
value: refresh_token.toString()
}
]
});
if (!token) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid refresh token",
error: "invalid_grant"
});
}
if (token.clientId !== client_id?.toString()) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid client_id",
error: "invalid_client"
});
}
if (token.refreshTokenExpiresAt < /* @__PURE__ */ new Date()) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "refresh token expired",
error: "invalid_grant"
});
}
const accessToken2 = random.generateRandomString(32, "a-z", "A-Z");
const newRefreshToken = random.generateRandomString(32, "a-z", "A-Z");
const accessTokenExpiresAt2 = new Date(
Date.now() + opts.accessTokenExpiresIn * 1e3
);
const refreshTokenExpiresAt2 = new Date(
Date.now() + opts.refreshTokenExpiresIn * 1e3
);
await ctx.context.adapter.create({
model: modelName.oauthAccessToken,
data: {
accessToken: accessToken2,
refreshToken: newRefreshToken,
accessTokenExpiresAt: accessTokenExpiresAt2,
refreshTokenExpiresAt: refreshTokenExpiresAt2,
clientId: client_id.toString(),
userId: token.userId,
scopes: token.scopes,
createdAt: /* @__PURE__ */ new Date(),
updatedAt: /* @__PURE__ */ new Date()
}
});
return ctx.json({
access_token: accessToken2,
token_type: "bearer",
expires_in: opts.accessTokenExpiresIn,
refresh_token: newRefreshToken,
scope: token.scopes
});
}
if (!code) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "code is required",
error: "invalid_request"
});
}
if (opts.requirePKCE && !code_verifier) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "code verifier is missing",
error: "invalid_request"
});
}
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(
code.toString()
);
if (!verificationValue) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid code",
error: "invalid_grant"
});
}
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "code expired",
error: "invalid_grant"
});
}
await ctx.context.internalAdapter.deleteVerificationValue(
verificationValue.id
);
if (!client_id) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "client_id is required",
error: "invalid_client"
});
}
if (!grant_type) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "grant_type is required",
error: "invalid_request"
});
}
if (grant_type !== "authorization_code") {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "grant_type must be 'authorization_code'",
error: "unsupported_grant_type"
});
}
if (!redirect_uri) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "redirect_uri is required",
error: "invalid_request"
});
}
const client = await ctx.context.adapter.findOne({
model: modelName.oauthClient,
where: [{ field: "clientId", value: client_id.toString() }]
}).then((res) => {
if (!res) {
return null;
}
return {
...res,
redirectURLs: res.redirectURLs.split(","),
metadata: res.metadata ? JSON.parse(res.metadata) : {}
};
});
if (!client) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid client_id",
error: "invalid_client"
});
}
if (client.disabled) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "client is disabled",
error: "invalid_client"
});
}
if (client.type === "public") {
if (!code_verifier) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "code verifier is required for public clients",
error: "invalid_request"
});
}
} else {
if (!client_secret) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "client_secret is required for confidential clients",
error: "invalid_client"
});
}
const isValidSecret = client.clientSecret === client_secret.toString();
if (!isValidSecret) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid client_secret",
error: "invalid_client"
});
}
}
const value = JSON.parse(
verificationValue.value
);
if (value.clientId !== client_id.toString()) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid client_id",
error: "invalid_client"
});
}
if (value.redirectURI !== redirect_uri.toString()) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "invalid redirect_uri",
error: "invalid_client"
});
}
if (value.codeChallenge && !code_verifier) {
throw new betterCall.APIError("BAD_REQUEST", {
error_description: "code verifier is missing",
error: "invalid_request"
});
}
const challenge = value.codeChallengeMethod === "plain" ? code_verifier : await hash.createHash("SHA-256", "base64urlnopad").digest(
code_verifier
);
if (challenge !== value.codeChallenge) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "code verification failed",
error: "invalid_request"
});
}
const requestedScopes = value.scope;
await ctx.context.internalAdapter.deleteVerificationValue(
verificationValue.id
);
const accessToken = random.generateRandomString(32, "a-z", "A-Z");
const refreshToken = random.generateRandomString(32, "A-Z", "a-z");
const accessTokenExpiresAt = new Date(
Date.now() + opts.accessTokenExpiresIn * 1e3
);
const refreshTokenExpiresAt = new Date(
Date.now() + opts.refreshTokenExpiresIn * 1e3
);
await ctx.context.adapter.create({
model: modelName.oauthAccessToken,
data: {
accessToken,
refreshToken,
accessTokenExpiresAt,
refreshTokenExpiresAt,
clientId: client_id.toString(),
userId: value.userId,
scopes: requestedScopes.join(" "),
createdAt: /* @__PURE__ */ new Date(),
updatedAt: /* @__PURE__ */ new Date()
}
});
const user = await ctx.context.internalAdapter.findUserById(
value.userId
);
if (!user) {
throw new betterCall.APIError("UNAUTHORIZED", {
error_description: "user not found",
error: "invalid_grant"
});
}
let secretKey = {
alg: "HS256",
key: await utils.subtle.generateKey(
{
name: "HMAC",
hash: "SHA-256"
},
true,
["sign", "verify"]
)
};
const profile = {
given_name: user.name.split(" ")[0],
family_name: user.name.split(" ")[1],
name: user.name,
profile: user.image,
updated_at: user.updatedAt.toISOString()
};
const email = {
email: user.email,
email_verified: user.emailVerified
};
const userClaims = {
...requestedScopes.includes("profile") ? profile : {},
...requestedScopes.includes("email") ? email : {}
};
const additionalUserClaims = opts.getAdditionalUserInfoClaim ? await opts.getAdditionalUserInfoClaim(
user,
requestedScopes,
client
) : {};
const idToken = await new jose.SignJWT({
sub: user.id,
aud: client_id.toString(),
iat: Date.now(),
auth_time: ctx.context.session ? new Date(ctx.context.session.session.createdAt).getTime() : void 0,
nonce: value.nonce,
acr: "urn:mace:incommon:iap:silver",
// default to silver - ⚠︎ this should be configurable and should be validated against the client's metadata
...userClaims,
...additionalUserClaims
}).setProtectedHeader({ alg: secretKey.alg }).setIssuedAt().setExpirationTime(
Math.floor(Date.now() / 1e3) + opts.accessTokenExpiresIn
).sign(secretKey.key);
return ctx.json(
{
access_token: accessToken,
token_type: "Bearer",
expires_in: opts.accessTokenExpiresIn,
refresh_token: requestedScopes.includes("offline_access") ? refreshToken : void 0,
scope: requestedScopes.join(" "),
id_token: requestedScopes.includes("openid") ? idToken : void 0
},
{
headers: {
"Cache-Control": "no-store",
Pragma: "no-cache"
}
}
);
}
),
registerMcpClient: session.createAuthEndpoint(
"/mcp/register",
{
method: "POST",
body: z__namespace.object({
redirect_uris: z__namespace.array(z__namespace.string()),
token_endpoint_auth_method: z__namespace.enum(["none", "client_secret_basic", "client_secret_post"]).default("client_secret_basic").optional(),
grant_types: z__namespace.array(
z__namespace.enum([
"authorization_code",
"implicit",
"password",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:jwt-bearer",
"urn:ietf:params:oauth:grant-type:saml2-bearer"
])
).default(["authorization_code"]).optional(),
response_types: z__namespace.array(z__namespace.enum(["code", "token"])).default(["code"]).optional(),
client_name: z__namespace.string().optional(),
client_uri: z__namespace.string().optional(),
logo_uri: z__namespace.string().optional(),
scope: z__namespace.string().optional(),
contacts: z__namespace.array(z__namespace.string()).optional(),
tos_uri: z__namespace.string().optional(),
policy_uri: z__namespace.string().optional(),
jwks_uri: z__namespace.string().optional(),
jwks: z__namespace.record(z__namespace.string(), z__namespace.any()).optional(),
metadata: z__namespace.record(z__namespace.any(), z__namespace.any()).optional(),
software_id: z__namespace.string().optional(),
software_version: z__namespace.string().optional(),
software_statement: z__namespace.string().optional()
}),
metadata: {
openapi: {
description: "Register an OAuth2 application",
responses: {
"200": {
description: "OAuth2 application registered successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the OAuth2 application"
},
icon: {
type: "string",
nullable: true,
description: "Icon URL for the application"
},
metadata: {
type: "object",
additionalProperties: true,
nullable: true,
description: "Additional metadata for the application"
},
clientId: {
type: "string",
description: "Unique identifier for the client"
},
clientSecret: {
type: "string",
description: "Secret key for the client. Not included for public clients."
},
redirectURLs: {
type: "array",
items: { type: "string", format: "uri" },
description: "List of allowed redirect URLs"
},
type: {
type: "string",
description: "Type of the client",
enum: ["web", "public"]
},
authenticationScheme: {
type: "string",
description: "Authentication scheme used by the client",
enum: ["client_secret", "none"]
},
disabled: {
type: "boolean",
description: "Whether the client is disabled",
enum: [false]
},
userId: {
type: "string",
nullable: true,
description: "ID of the user who registered the client, null if registered anonymously"
},
createdAt: {
type: "string",
format: "date-time",
description: "Creation timestamp"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Last update timestamp"
}
},
required: [
"name",
"clientId",
"redirectURLs",
"type",
"authenticationScheme",
"disabled",
"createdAt",
"updatedAt"
]
}
}
}
}
}
}
}
},
async (ctx) => {
const body = ctx.body;
const session$1 = await session.getSessionFromCtx(ctx);
ctx.setHeader("Access-Control-Allow-Origin", "*");
ctx.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
ctx.setHeader(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
);
ctx.setHeader("Access-Control-Max-Age", "86400");
ctx.headers?.set("Access-Control-Max-Age", "86400");
if ((!body.grant_types || body.grant_types.includes("authorization_code") || body.grant_types.includes("implicit")) && (!body.redirect_uris || body.redirect_uris.length === 0)) {
throw new betterCall.APIError("BAD_REQUEST", {
error: "invalid_redirect_uri",
error_description: "Redirect URIs are required for authorization_code and implicit grant types"
});
}
if (body.grant_types && body.response_types) {
if (body.grant_types.includes("authorization_code") && !body.response_types.includes("code")) {
throw new betterCall.APIError("BAD_REQUEST", {
error: "invalid_client_metadata",
error_description: "When 'authorization_code' grant type is used, 'code' response type must be included"
});
}
if (body.grant_types.includes("implicit") && !body.response_types.includes("token")) {
throw new betterCall.APIError("BAD_REQUEST", {
error: "invalid_client_metadata",
error_description: "When 'implicit' grant type is used, 'token' response type must be included"
});
}
}
const clientId = opts.generateClientId?.() || random.generateRandomString(32, "a-z", "A-Z");
const clientSecret = opts.generateClientSecret?.() || random.generateRandomString(32, "a-z", "A-Z");
const clientType = body.token_endpoint_auth_method === "none" ? "public" : "web";
const finalClientSecret = clientType === "public" ? "" : clientSecret;
await ctx.context.adapter.create({
model: modelName.oauthClient,
data: {
name: body.client_name,
icon: body.logo_uri,
metadata: body.metadata ? JSON.stringify(body.metadata) : null,
clientId,
clientSecret: finalClientSecret,
redirectURLs: body.redirect_uris.join(","),
type: clientType,
authenticationScheme: body.token_endpoint_auth_method || "client_secret_basic",
disabled: false,
userId: session$1?.session.userId,
createdAt: /* @__PURE__ */ new Date(),
updatedAt: /* @__PURE__ */ new Date()
}
});
const responseData = {
client_id: clientId,
client_id_issued_at: Math.floor(Date.now() / 1e3),
redirect_uris: body.redirect_uris,
token_endpoint_auth_method: body.token_endpoint_auth_method || "client_secret_basic",
grant_types: body.grant_types || ["authorization_code"],
response_types: body.response_types || ["code"],
client_name: body.client_name,
client_uri: body.client_uri,
logo_uri: body.logo_uri,
scope: body.scope,
contacts: body.contacts,
tos_uri: body.tos_uri,
policy_uri: body.policy_uri,
jwks_uri: body.jwks_uri,
jwks: body.jwks,
software_id: body.software_id,
software_version: body.software_version,
software_statement: body.software_statement,
metadata: body.metadata,
...clientType !== "public" ? {
client_secret: finalClientSecret,
client_secret_expires_at: 0
// 0 means it doesn't expire
} : {}
};
return new Response(JSON.stringify(responseData), {
status: 201,
headers: {
"Cache-Control": "no-store",
Pragma: "no-cache"
}
});
}
),
getMcpSession: session.createAuthEndpoint(
"/mcp/get-session",
{
method: "GET",
requireHeaders: true
},
async (c) => {
const accessToken = c.headers?.get("Authorization")?.replace("Bearer ", "");
if (!accessToken) {
c.headers?.set("WWW-Authenticate", "Bearer");
return c.json(null);
}
const accessTokenData = await c.context.adapter.findOne({
model: modelName.oauthAccessToken,
where: [
{
field: "accessToken",
value: accessToken
}
]
});
if (!accessTokenData) {
return c.json(null);
}
return c.json(accessTokenData);
}
)
},
schema: plugins_oidcProvider_index.schema
};
};
const withMcpAuth = (auth, handler) => {
return async (req) => {
const baseURL = url.getBaseURL(auth.options.baseURL, auth.options.basePath);
if (!baseURL && !env.isProduction) {
logger.logger.warn("Unable to get the baseURL, please check your config!");
}
const session = await auth.api.getMcpSession({
headers: req.headers
});
const wwwAuthenticateValue = `Bearer resource_metadata=${baseURL}/api/auth/.well-known/oauth-authorization-server`;
if (!session) {
return Response.json(
{
jsonrpc: "2.0",
error: {
code: -32e3,
message: "Unauthorized: Authentication required",
"www-authenticate": wwwAuthenticateValue
},
id: null
},
{
status: 401,
headers: {
"WWW-Authenticate": wwwAuthenticateValue
}
}
);
}
return handler(req, session);
};
};
const oAuthDiscoveryMetadata = (auth) => {
return async (request) => {
const res = await auth.api.getMcpOAuthConfig();
return new Response(JSON.stringify(res), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400"
}
});
};
};
exports.organization = organization.organization;
exports.parseRoles = organization.parseRoles;
exports.TWO_FACTOR_ERROR_CODES = plugins_twoFactor_index.TWO_FACTOR_ERROR_CODES;
exports.twoFactor = plugins_twoFactor_index.twoFactor;
exports.USERNAME_ERROR_CODES = plugins_username_index.USERNAME_ERROR_CODES;
exports.username = plugins_username_index.username;
exports.bearer = plugins_bearer_index.bearer;
exports.createAuthEndpoint = session.createAuthEndpoint;
exports.createAuthMiddleware = session.createAuthMiddleware;
exports.optionsMiddleware = session.optionsMiddleware;
exports.HIDE_METADATA = socialProviders_index.HIDE_METADATA;
exports.magicLink = plugins_magicLink_index.magicLink;
exports.phoneNumber = plugins_phoneNumber_index.phoneNumber;
exports.anonymous = plugins_anonymous_index.anonymous;
exports.admin = admin.admin;
exports.genericOAuth = plugins_genericOauth_index.genericOAuth;
exports.generateExportedKeyPair = plugins_jwt_index.generateExportedKeyPair;
exports.getJwtToken = plugins_jwt_index.getJwtToken;
exports.jwt = plugins_jwt_index.jwt;
exports.multiSession = plugins_multiSession_index.multiSession;
exports.emailOTP = plugins_emailOtp_index.emailOTP;
exports.oneTap = plugins_oneTap_index.oneTap;
exports.oAuthProxy = plugins_oauthProxy_index.oAuthProxy;
exports.customSession = plugins_customSession_index.customSession;
exports.openAPI = plugins_openApi_index.openAPI;
exports.getClient = plugins_oidcProvider_index.getClient;
exports.getMetadata = plugins_oidcProvider_index.getMetadata;
exports.oidcProvider = plugins_oidcProvider_index.oidcProvider;
exports.captcha = plugins_captcha_index.captcha;
exports.API_KEY_TABLE_NAME = plugins_oneTimeToken_index.API_KEY_TABLE_NAME;
exports.ERROR_CODES = plugins_oneTimeToken_index.ERROR_CODES;
exports.apiKey = plugins_oneTimeToken_index.apiKey;
exports.defaultKeyHasher = plugins_oneTimeToken_index.defaultKeyHasher;
exports.oneTimeToken = plugins_oneTimeToken_index.oneTimeToken;
exports.haveIBeenPwned = plugins_haveibeenpwned_index.haveIBeenPwned;
exports.$deviceAuthorizationOptionsSchema = plugins_deviceAuthorization_index.$deviceAuthorizationOptionsSchema;
exports.deviceAuthorization = plugins_deviceAuthorization_index.deviceAuthorization;
exports.siwe = plugins_siwe_index.siwe;
exports.twoFactorClient = client.twoFactorClient;
exports.deviceAuthorizationClient = client$1.deviceAuthorizationClient;
exports.getMCPProviderMetadata = getMCPProviderMetadata;
exports.mcp = mcp;
exports.oAuthDiscoveryMetadata = oAuthDiscoveryMetadata;
exports.withMcpAuth = withMcpAuth;