UNPKG

@mastra/core

Version:

Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

498 lines (492 loc) • 16.2 kB
import { MastraBase } from '../chunk-WENZPAHS.js'; import { RegisteredLogger } from '../chunk-DBBWTK24.js'; import { MastraError } from '../chunk-FJEVLHJT.js'; // src/server/auth.ts var MastraAuthProvider = class extends MastraBase { protected; public; constructor(options) { super({ component: "AUTH", name: options?.name }); if (options?.authorizeUser) { this.authorizeUser = options.authorizeUser.bind(this); } this.protected = options?.protected; this.public = options?.public; this.mapUserToResourceId = options?.mapUserToResourceId; } registerOptions(opts) { if (opts?.authorizeUser) { this.authorizeUser = opts.authorizeUser.bind(this); } if (opts?.mapUserToResourceId) { this.mapUserToResourceId = opts.mapUserToResourceId; } if (opts?.protected) { this.protected = opts.protected; } if (opts?.public) { this.public = opts.public; } } }; // src/server/composite-auth.ts function isSSOProvider(p) { return p !== null && typeof p === "object" && typeof p.getLoginUrl === "function" && typeof p.handleCallback === "function"; } function isSessionProvider(p) { return p !== null && typeof p === "object" && typeof p.validateSession === "function" && typeof p.createSession === "function"; } function isUserProvider(p) { return p !== null && typeof p === "object" && typeof p.getCurrentUser === "function"; } function isObjectLike(value) { return typeof value === "object" && value !== null || typeof value === "function"; } var CompositeAuth = class extends MastraAuthProvider { providers; authenticatedProviderByObject = /* @__PURE__ */ new WeakMap(); authenticatedProviderByPrimitive = /* @__PURE__ */ new Map(); constructor(providers) { const combinedPublic = providers.flatMap((provider) => provider.public ?? []); const combinedProtected = providers.flatMap((provider) => provider.protected ?? []); super({ public: combinedPublic, protected: combinedProtected }); this.providers = providers; if (providers.some((provider) => typeof provider.mapUserToResourceId === "function")) { this.mapUserToResourceId = (user) => this.mapAuthenticatedUserToResourceId(user); } if (!providers.some(isSSOProvider)) { this.getLoginUrl = void 0; this.handleCallback = void 0; this.getLoginButtonConfig = void 0; } if (!providers.some(isSessionProvider)) { this.createSession = void 0; this.validateSession = void 0; this.getSessionIdFromRequest = void 0; } if (!providers.some(isUserProvider)) { this.getCurrentUser = void 0; this.getUser = void 0; } } // Find first provider implementing an interface findProvider(check) { return this.providers.find(check); } rememberAuthenticatedProvider(user, provider) { if (isObjectLike(user)) { this.authenticatedProviderByObject.set(user, provider); return; } this.authenticatedProviderByPrimitive.set(user, provider); } takeAuthenticatedProvider(user) { if (isObjectLike(user)) { const provider2 = this.authenticatedProviderByObject.get(user); this.authenticatedProviderByObject.delete(user); return provider2; } const primitiveUser = user; const provider = this.authenticatedProviderByPrimitive.get(primitiveUser); this.authenticatedProviderByPrimitive.delete(primitiveUser); return provider; } mapAuthenticatedUserToResourceId(user) { const provider = this.takeAuthenticatedProvider(user); return provider?.mapUserToResourceId?.(user); } // ============================================================================ // License Exemption Markers // Expose these if any underlying provider has them // ============================================================================ /** * True if any provider is MastraCloudAuth (exempt from license requirement). */ get isMastraCloudAuth() { return this.providers.some( (p) => "isMastraCloudAuth" in p && p.isMastraCloudAuth === true ); } /** * True if any provider is SimpleAuth (exempt from license requirement). */ get isSimpleAuth() { return this.providers.some((p) => "isSimpleAuth" in p && p.isSimpleAuth === true); } // ============================================================================ // MastraAuthProvider Implementation // ============================================================================ async authenticateToken(token, request) { for (const provider of this.providers) { try { const user = await provider.authenticateToken(token, request); if (user) { this.rememberAuthenticatedProvider(user, provider); return user; } } catch { } } return null; } async authorizeUser(user, request) { for (const provider of this.providers) { const authorized = await provider.authorizeUser(user, request); if (authorized) { return true; } } return false; } // ============================================================================ // ISSOProvider Implementation // ============================================================================ /** * Forward cookie header to SSO provider for PKCE validation. * Called by auth handler before handleCallback(). */ setCallbackCookieHeader(cookieHeader) { const sso = this.findProvider(isSSOProvider); if (sso && typeof sso.setCallbackCookieHeader === "function") { sso.setCallbackCookieHeader(cookieHeader); } } getLoginUrl(redirectUri, state) { const sso = this.findProvider(isSSOProvider); if (!sso) throw new Error("No SSO provider configured in CompositeAuth"); return sso.getLoginUrl(redirectUri, state); } getLoginCookies(redirectUri, state) { const sso = this.findProvider(isSSOProvider); return sso?.getLoginCookies?.(redirectUri, state); } async handleCallback(code, state) { const sso = this.findProvider(isSSOProvider); if (!sso) throw new Error("No SSO provider configured in CompositeAuth"); return sso.handleCallback(code, state); } getLoginButtonConfig() { const sso = this.findProvider(isSSOProvider); if (!sso) return { provider: "unknown", text: "Sign in" }; return sso.getLoginButtonConfig(); } async getLogoutUrl(redirectUri, request) { for (const provider of this.providers) { if (isSSOProvider(provider) && provider.getLogoutUrl) { try { const url = await provider.getLogoutUrl(redirectUri, request); if (url) return url; } catch { } } } return null; } // ============================================================================ // ISessionProvider Implementation // ============================================================================ async createSession(userId, metadata) { const session = this.findProvider(isSessionProvider); if (!session) throw new Error("No session provider configured in CompositeAuth"); return session.createSession(userId, metadata); } async validateSession(sessionId) { for (const provider of this.providers) { if (isSessionProvider(provider)) { try { const session = await provider.validateSession(sessionId); if (session) return session; } catch { } } } return null; } async destroySession(sessionId) { const destroyPromises = []; for (const provider of this.providers) { if (isSessionProvider(provider)) { destroyPromises.push( provider.destroySession(sessionId).catch(() => { }) ); } } await Promise.all(destroyPromises); } async refreshSession(sessionId) { for (const provider of this.providers) { if (isSessionProvider(provider)) { try { const session = await provider.refreshSession(sessionId); if (session) return session; } catch { } } } return null; } getSessionIdFromRequest(request) { for (const provider of this.providers) { if (isSessionProvider(provider)) { try { const sessionId = provider.getSessionIdFromRequest(request); if (sessionId) return sessionId; } catch { } } } return null; } getSessionHeaders(session) { const sessionProvider = this.findProvider(isSessionProvider); return sessionProvider?.getSessionHeaders(session) ?? {}; } getClearSessionHeaders() { const headers = {}; for (const provider of this.providers) { if (isSessionProvider(provider)) { try { const providerHeaders = provider.getClearSessionHeaders(); Object.assign(headers, providerHeaders); } catch { } } } return headers; } // ============================================================================ // IUserProvider Implementation // Try each provider until one returns a user (like authenticateToken) // ============================================================================ async getCurrentUser(request) { for (const provider of this.providers) { if (isUserProvider(provider)) { try { const user = await provider.getCurrentUser(request); if (user) return user; } catch { } } } return null; } async getUser(userId) { for (const provider of this.providers) { if (isUserProvider(provider)) { try { const user = await provider.getUser(userId); if (user) return user; } catch { } } } return null; } }; // src/server/base.ts var MastraServerBase = class extends MastraBase { #app; constructor({ app, name }) { super({ component: RegisteredLogger.SERVER, name: name ?? "Server" }); this.#app = app; } /** * Get the app instance. * * Returns the server app that was passed to the constructor. This allows users * to access the underlying server framework's app for direct operations * like calling routes via app.fetch() (Hono) or using the app for testing. * * @template T - The expected type of the app (defaults to TApp) * @returns The app instance cast to T. Callers are responsible for ensuring T matches the actual app type. * * @example * ```typescript * const app = adapter.getApp<Hono>(); * const response = await app.fetch(new Request('http://localhost/api/agents')); * ``` */ getApp() { return this.#app; } /** * Protected getter for subclasses to access the app. * This allows subclasses to use `this.app` naturally. */ get app() { return this.#app; } }; // src/server/simple-auth.ts var DEFAULT_HEADERS = ["Authorization", "X-Playground-Access"]; var SimpleAuth = class extends MastraAuthProvider { /** * Marker to exempt SimpleAuth from EE license requirement. * SimpleAuth is for development/testing and should work without a license. */ isSimpleAuth = true; tokens; headers; users; userById; constructor(options) { super(options); this.tokens = options.tokens; this.users = Object.values(this.tokens); this.headers = [...DEFAULT_HEADERS].concat(options.headers || []); this.userById = new Map(this.users.map((u) => [String(u?.id), u])); } async authenticateToken(token, request) { const requestTokens = this.getTokensFromHeaders(token, request); for (const requestToken of requestTokens) { const tokenToUser = this.tokens[requestToken]; if (tokenToUser) { return tokenToUser; } } return this.getUserFromCookie(this.getRequestHeader(request, "Cookie")); } async authorizeUser(user, _request) { return this.users.includes(user); } /** Get current user from request headers or cookie. */ async getCurrentUser(request) { for (const headerName of this.headers) { const headerValue = request.headers.get(headerName); if (headerValue) { const token = this.stripBearerPrefix(headerValue); const user = this.tokens[token]; if (user) { return user; } } } return this.getUserFromCookie(request.headers.get("Cookie")); } getUserFromCookie(cookieHeader) { if (!cookieHeader) return null; const cookies = cookieHeader.split(";").map((c) => c.trim()); for (const cookie of cookies) { if (cookie.startsWith("mastra-token=")) { const token = cookie.slice("mastra-token=".length); const user = this.tokens[token]; if (user) { return user; } } } return null; } /** Get user by ID. */ async getUser(userId) { return this.userById.get(userId) ?? null; } /** * Sign in with token (passed as password field). * The email field is ignored - only the token matters. */ async signIn(_email, password, _request) { const token = password; const user = this.tokens[token]; if (!user) { throw new Error("Invalid token"); } const cookie = `mastra-token=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`; return { user, token, cookies: [cookie] }; } async signUp() { throw new Error("Sign up is not supported with SimpleAuth. Use pre-configured tokens."); } isSignUpEnabled() { return false; } /** * Get headers to clear the session cookie on logout. * Partial ISessionProvider implementation for logout support. */ getClearSessionHeaders() { return { "Set-Cookie": "mastra-token=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0" }; } stripBearerPrefix(token) { return token.startsWith("Bearer ") ? token.slice(7) : token; } /** * Get a header value from either a HonoRequest or standard Request. * The auth middleware passes a raw Request (c.req.raw), not a HonoRequest, * so we need to handle both APIs. */ getRequestHeader(request, name) { if (typeof request.header === "function") { return request.header(name); } return request.headers?.get(name) ?? void 0; } getTokensFromHeaders(token, request) { const tokens = [token]; for (const headerName of this.headers) { const headerValue = this.getRequestHeader(request, headerName); if (headerValue) { tokens.push(this.stripBearerPrefix(headerValue)); } } return tokens; } }; // src/server/index.ts function validateOptions(path, options) { if (options.method === void 0) { throw new MastraError({ id: "MASTRA_SERVER_API_INVALID_ROUTE_OPTIONS", text: `Invalid options for route "${path}", missing "method" property`, domain: "MASTRA_SERVER" /* MASTRA_SERVER */, category: "USER" /* USER */ }); } if (options.handler === void 0 && options.createHandler === void 0) { throw new MastraError({ id: "MASTRA_SERVER_API_INVALID_ROUTE_OPTIONS", text: `Invalid options for route "${path}", you must define a "handler" or "createHandler" property`, domain: "MASTRA_SERVER" /* MASTRA_SERVER */, category: "USER" /* USER */ }); } if (options.handler !== void 0 && options.createHandler !== void 0) { throw new MastraError({ id: "MASTRA_SERVER_API_INVALID_ROUTE_OPTIONS", text: `Invalid options for route "${path}", you can only define one of the following properties: "handler" or "createHandler"`, domain: "MASTRA_SERVER" /* MASTRA_SERVER */, category: "USER" /* USER */ }); } } function registerApiRoute(path, options) { validateOptions(path, options); return { path, method: options.method, handler: options.handler, createHandler: options.createHandler, openapi: options.openapi, middleware: options.middleware, cors: options.cors, requiresAuth: options.requiresAuth, requiresPermission: options.requiresPermission, fga: options.fga }; } function defineAuth(config) { return config; } export { CompositeAuth, MastraAuthProvider, MastraServerBase, SimpleAuth, defineAuth, registerApiRoute }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map