Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f59c8a993a |
No files matched your search
@@ -63,6 +63,8 @@ UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||
|
||||
# Public URL contract. Values must be absolute HTTP(S) URLs without credentials,
|
||||
# query strings, or fragments. Trailing slashes are normalized at startup.
|
||||
# Public Homarr origin used by MCP OAuth metadata and redirects behind proxies.
|
||||
# BASE_URL=https://homarr.example.com
|
||||
HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
# PocketBase API used by Homarr and by the standalone documentation site.
|
||||
# Defaults to HOMARR_WEBSITE_URL.
|
||||
|
||||
@@ -30,6 +30,7 @@ Using the `PUID` and `PGID` will require you to set the correct permissions on t
|
||||
| `PUID` | User ID to run the container as | Any valid user ID | `0` |
|
||||
| `PGID` | Group ID to run the container as | Any valid group ID | `0` |
|
||||
| `LOG_LEVEL` | Log level to use | `debug` / `info` / `warn` / `error` | `info` |
|
||||
| `BASE_URL` | Public Homarr origin used by MCP OAuth metadata and redirects. Set this when a reverse proxy does not preserve the public host and protocol. | HTTP(S) origin without a path | - |
|
||||
| `NO_EXTERNAL_CONNECTION` | Disables Homarr's internet-backed requests, including update checks, weather, and RSS feeds | `true` or `false` | `false` |
|
||||
| `ENABLE_DNS_CACHING` | Enables dns caching. Enabled by default, but if you experience issues with IPv6 or static IPs, see [#4006](https://github.com/homarr-labs/homarr/issues/4006) or disable this flag | `true` or `false` | `true` |
|
||||
| `HOMARR_DISABLE_IPV6` | Disables the IPv6 listener of the internal nginx proxy, even when the host supports IPv6. IPv6 is normally disabled automatically when the host has no IPv6 configured. See [#4596](https://github.com/homarr-labs/homarr/issues/4596) | `true` or `false` | `false` |
|
||||
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
volumes:
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
- BASE_URL=your.internal.dns.address.here.com
|
||||
- BASE_URL=https://your.internal.dns.address.here.com
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
@@ -54,6 +54,10 @@ The **AI / MCP** tab on Homarr's API page shows the current endpoint, configurat
|
||||
MCP supports API keys and OAuth 2.1 with PKCE. API keys are simpler for direct client configuration; OAuth is available
|
||||
for clients that support dynamic authorization.
|
||||
|
||||
When Homarr is behind a reverse proxy that does not preserve the public host and protocol, set `BASE_URL` to the exact
|
||||
public origin, for example `https://homarr.example.com`. Do not include a path. OAuth clients discover the authorization
|
||||
server and protected resource through Homarr's `/.well-known/` routes.
|
||||
|
||||
Tools use the permissions of the API key owner or authorized user. Creating a key does not grant additional access.
|
||||
Custom Widget and Workshop authoring tools, prompts, and resources are visible only to administrators.
|
||||
|
||||
|
||||
@@ -65,6 +65,15 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{ source: "/.well-known/:path*", destination: "/well-known/:path*" },
|
||||
{
|
||||
source: "/api/mcp/:transport/.well-known/:path*",
|
||||
destination: "/well-known/:path*/api/mcp/:transport",
|
||||
},
|
||||
];
|
||||
},
|
||||
// skipcq: JS-0116
|
||||
// eslint-disable-next-line @typescript-eslint/require-await,no-restricted-syntax
|
||||
async headers() {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { headers } from "next/headers";
|
||||
|
||||
import { extractBaseUrlFromHeaders } from "@homarr/common";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
};
|
||||
|
||||
export async function GET() {
|
||||
const baseUrl = extractBaseUrlFromHeaders(await headers());
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
issuer: baseUrl,
|
||||
authorization_endpoint: `${baseUrl}/api/mcp/oauth/authorize`,
|
||||
token_endpoint: `${baseUrl}/api/mcp/oauth/token`,
|
||||
registration_endpoint: `${baseUrl}/api/mcp/oauth/register`,
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
token_endpoint_auth_methods_supported: ["none"],
|
||||
scopes_supported: ["mcp:tools"],
|
||||
resource_indicators_supported: true,
|
||||
},
|
||||
{ headers: corsHeaders },
|
||||
);
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
return new Response(null, { status: 204, headers: corsHeaders });
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { headers } from "next/headers";
|
||||
|
||||
import { extractBaseUrlFromHeaders } from "@homarr/common";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
};
|
||||
|
||||
export async function GET() {
|
||||
const baseUrl = extractBaseUrlFromHeaders(await headers());
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
resource: `${baseUrl}/api/mcp`,
|
||||
authorization_servers: [baseUrl],
|
||||
},
|
||||
{ headers: corsHeaders },
|
||||
);
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
return new Response(null, { status: 204, headers: corsHeaders });
|
||||
}
|
||||
@@ -22,7 +22,10 @@ vi.mock("@homarr/auth/api-key", () => ({
|
||||
API_KEY_HEADER_NAME: "ApiKey",
|
||||
getSessionFromApiKeyAsync: mocks.authenticate,
|
||||
}));
|
||||
vi.mock("@homarr/common", () => ({ extractBaseUrlFromHeaders: () => "http://localhost" }));
|
||||
vi.mock("@homarr/common", () => ({
|
||||
extractBaseUrlFromHeaders: () => "http://localhost",
|
||||
removeTrailingSlash: (value: string) => value.replace(/\/+$/u, ""),
|
||||
}));
|
||||
vi.mock("@homarr/common/server", () => ({ ipAddressFromHeaders: mocks.ipAddress }));
|
||||
vi.mock("@homarr/core/infrastructure/logs", () => ({
|
||||
createLogger: () => ({ info: vi.fn(), warn: mocks.loggerWarn, error: vi.fn() }),
|
||||
@@ -42,6 +45,7 @@ vi.mock("@homarr/custom-widgets/core", () => ({
|
||||
getCustomWidgetJsonSchema: () => ({ type: "object", title: "Custom Widget" }),
|
||||
}));
|
||||
vi.mock("@homarr/db", () => ({ db: {} }));
|
||||
vi.mock("~/env", () => ({ env: { BASE_URL: undefined } }));
|
||||
vi.mock("~/versions/package-reader", () => ({ getPackageVersion: () => "test-version" }));
|
||||
vi.mock("../_extract-tools", () => ({
|
||||
extractMcpTools: () => [
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { extractBaseUrlFromHeaders, removeTrailingSlash } from "@homarr/common";
|
||||
|
||||
import { env } from "~/env";
|
||||
|
||||
type RequestHeaders = Parameters<typeof extractBaseUrlFromHeaders>[0];
|
||||
|
||||
export const getMcpBaseUrl = (headers: RequestHeaders) =>
|
||||
removeTrailingSlash(env.BASE_URL ?? extractBaseUrlFromHeaders(headers));
|
||||
@@ -7,7 +7,7 @@ import { z } from "zod/v4";
|
||||
import type { McpTool } from "@homarr/api/mcp";
|
||||
import { createTRPCContext, mcpRouter } from "@homarr/api/mcp";
|
||||
import { API_KEY_HEADER_NAME, getSessionFromApiKeyAsync } from "@homarr/auth/api-key";
|
||||
import { extractBaseUrlFromHeaders } from "@homarr/common";
|
||||
import { removeTrailingSlash } from "@homarr/common";
|
||||
import { ipAddressFromHeaders } from "@homarr/common/server";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { buildCustomWidgetMcpPrompt } from "@homarr/custom-widgets/authoring-prompt";
|
||||
@@ -24,6 +24,7 @@ import { db } from "@homarr/db";
|
||||
|
||||
import { getPackageVersion } from "~/versions/package-reader";
|
||||
import { getSafeAssistantToolError } from "../assistant/chat/assistant-tool-error";
|
||||
import { getMcpBaseUrl } from "./_base-url";
|
||||
import { extractMcpTools } from "./_extract-tools";
|
||||
import { createMcpProtocolHandler } from "./_protocol";
|
||||
|
||||
@@ -246,6 +247,12 @@ function jsonErrorResponse(status: number, body: Record<string, string>, extraHe
|
||||
});
|
||||
}
|
||||
|
||||
const getOAuthChallenge = (req: NextRequest) => {
|
||||
const baseUrl = getMcpBaseUrl(req.headers);
|
||||
const resourcePath = removeTrailingSlash(new URL(req.url).pathname);
|
||||
return `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource${resourcePath}"`;
|
||||
};
|
||||
|
||||
export const handleMcpRequest = async (req: NextRequest) => {
|
||||
const pathname = new URL(req.url).pathname;
|
||||
if (pathname.endsWith("/sse") || pathname.endsWith("/message")) {
|
||||
@@ -269,30 +276,37 @@ export const handleMcpRequest = async (req: NextRequest) => {
|
||||
|
||||
if (!apiKeyValue) {
|
||||
recordAuthFailure(ipAddress);
|
||||
const baseUrl = extractBaseUrlFromHeaders(req.headers);
|
||||
return jsonErrorResponse(
|
||||
401,
|
||||
{
|
||||
error: "unauthorized",
|
||||
hint: "Authenticate with an ApiKey header or via OAuth at /.well-known/oauth-authorization-server",
|
||||
},
|
||||
{ "WWW-Authenticate": `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"` },
|
||||
{ "WWW-Authenticate": getOAuthChallenge(req) },
|
||||
);
|
||||
}
|
||||
|
||||
if (!apiKeyValue.includes(".")) {
|
||||
recordAuthFailure(ipAddress);
|
||||
return jsonErrorResponse(401, { error: "invalid_token", hint: "The token must be in the format '<id>.<token>'." });
|
||||
return jsonErrorResponse(
|
||||
401,
|
||||
{ error: "invalid_token", hint: "The token must be in the format '<id>.<token>'." },
|
||||
{ "WWW-Authenticate": getOAuthChallenge(req) },
|
||||
);
|
||||
}
|
||||
|
||||
const session = await getSessionFromApiKeyAsync(db, apiKeyValue, ipAddress, ua);
|
||||
|
||||
if (!session) {
|
||||
recordAuthFailure(ipAddress);
|
||||
return jsonErrorResponse(401, {
|
||||
error: "invalid_token",
|
||||
hint: "The API key was not found or the token is incorrect.",
|
||||
});
|
||||
return jsonErrorResponse(
|
||||
401,
|
||||
{
|
||||
error: "invalid_token",
|
||||
hint: "The API key was not found or the token is incorrect.",
|
||||
},
|
||||
{ "WWW-Authenticate": getOAuthChallenge(req) },
|
||||
);
|
||||
}
|
||||
|
||||
clearAuthFailures(ipAddress);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { extractBaseUrlFromHeaders } from "@homarr/common";
|
||||
|
||||
import { getMcpBaseUrl } from "../../_base-url";
|
||||
import { consumePendingAuth, createAuthCode, getClient, storePendingAuth } from "../_store";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
@@ -83,8 +83,9 @@ export async function GET(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const expectedResource = `${extractBaseUrlFromHeaders(req.headers)}/api/mcp`;
|
||||
if (resource && resource !== expectedResource) {
|
||||
const baseUrl = getMcpBaseUrl(req.headers);
|
||||
const allowedResources = new Set([`${baseUrl}/api/mcp`, `${baseUrl}/api/mcp/mcp`]);
|
||||
if (resource && !allowedResources.has(resource)) {
|
||||
return Response.json(
|
||||
{
|
||||
error: "invalid_target",
|
||||
@@ -152,7 +153,7 @@ export async function GET(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const loginUrl = new URL("/auth/login", url.origin);
|
||||
const loginUrl = new URL("/auth/login", baseUrl);
|
||||
loginUrl.searchParams.set("callbackUrl", `/api/mcp/oauth/authorize?pending=${id}`);
|
||||
return Response.redirect(loginUrl.toString());
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import { getMcpBaseUrl } from "~/app/api/mcp/_base-url";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
};
|
||||
|
||||
export async function GET(req: NextRequest, props: { params: Promise<{ path: string[] }> }) {
|
||||
const [kind, ...rest] = (await props.params).path;
|
||||
const baseUrl = getMcpBaseUrl(req.headers);
|
||||
const identifierPath = rest.join("/");
|
||||
|
||||
if (kind === "oauth-authorization-server") {
|
||||
const issuer = identifierPath ? `${baseUrl}/${identifierPath}` : baseUrl;
|
||||
return Response.json(
|
||||
{
|
||||
issuer,
|
||||
authorization_endpoint: `${baseUrl}/api/mcp/oauth/authorize`,
|
||||
token_endpoint: `${baseUrl}/api/mcp/oauth/token`,
|
||||
registration_endpoint: `${baseUrl}/api/mcp/oauth/register`,
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: ["authorization_code"],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
token_endpoint_auth_methods_supported: ["none"],
|
||||
scopes_supported: ["mcp:tools"],
|
||||
resource_indicators_supported: true,
|
||||
},
|
||||
{ headers: corsHeaders },
|
||||
);
|
||||
}
|
||||
|
||||
if (kind === "oauth-protected-resource") {
|
||||
const resource = identifierPath ? `${baseUrl}/${identifierPath}` : `${baseUrl}/api/mcp`;
|
||||
return Response.json(
|
||||
{
|
||||
resource,
|
||||
authorization_servers: [baseUrl],
|
||||
},
|
||||
{ headers: corsHeaders },
|
||||
);
|
||||
}
|
||||
|
||||
return Response.json({ error: "not_found" }, { status: 404, headers: corsHeaders });
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
return new Response(null, { status: 204, headers: corsHeaders });
|
||||
}
|
||||
@@ -16,8 +16,15 @@ const publicHttpUrl = (variableName: string) =>
|
||||
}
|
||||
});
|
||||
|
||||
const publicHttpOrigin = (variableName: string) =>
|
||||
publicHttpUrl(variableName).refine(
|
||||
(value) => value === new URL(value).origin,
|
||||
`${variableName} must be an HTTP(S) origin without a path`,
|
||||
);
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
BASE_URL: publicHttpOrigin("BASE_URL").optional(),
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION: createBooleanSchema(false),
|
||||
DEMO_MODE: createBooleanSchema(false),
|
||||
DEMO_READ_ONLY: createBooleanSchema(true),
|
||||
|
||||
Generated
+23
-23
@@ -25,11 +25,11 @@ catalogs:
|
||||
specifier: 0.14.9
|
||||
version: 0.14.9
|
||||
'@auth/core':
|
||||
specifier: ^0.41.3
|
||||
version: 0.41.3
|
||||
specifier: ^0.41.2
|
||||
version: 0.41.2
|
||||
'@auth/drizzle-adapter':
|
||||
specifier: ^1.11.3
|
||||
version: 1.11.3
|
||||
specifier: ^1.11.2
|
||||
version: 1.11.2
|
||||
'@babel/parser':
|
||||
specifier: ^7.29.7
|
||||
version: 7.29.7
|
||||
@@ -520,8 +520,8 @@ catalogs:
|
||||
specifier: 16.3.1
|
||||
version: 16.3.1
|
||||
next-auth:
|
||||
specifier: 5.0.0-beta.32
|
||||
version: 5.0.0-beta.32
|
||||
specifier: 5.0.0-beta.31
|
||||
version: 5.0.0-beta.31
|
||||
next-intl:
|
||||
specifier: 4.13.1
|
||||
version: 4.13.1
|
||||
@@ -1587,10 +1587,10 @@ importers:
|
||||
dependencies:
|
||||
'@auth/core':
|
||||
specifier: 'catalog:'
|
||||
version: 0.41.3
|
||||
version: 0.41.2
|
||||
'@auth/drizzle-adapter':
|
||||
specifier: 'catalog:'
|
||||
version: 1.11.3
|
||||
version: 1.11.2
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
@@ -1620,7 +1620,7 @@ importers:
|
||||
version: 16.3.1(@babel/core@7.29.0(supports-color@10.2.2))(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(@types/node@24.13.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)
|
||||
next-auth:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.0-beta.32(next@16.3.1(@babel/core@7.29.0(supports-color@10.2.2))(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(@types/node@24.13.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)
|
||||
version: 5.0.0-beta.31(next@16.3.1(@babel/core@7.29.0(supports-color@10.2.2))(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(@types/node@24.13.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7)
|
||||
react:
|
||||
specifier: 'catalog:'
|
||||
version: 19.2.7
|
||||
@@ -1989,7 +1989,7 @@ importers:
|
||||
dependencies:
|
||||
'@auth/core':
|
||||
specifier: 'catalog:'
|
||||
version: 0.41.3
|
||||
version: 0.41.2
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
@@ -3346,12 +3346,12 @@ packages:
|
||||
react:
|
||||
optional: true
|
||||
|
||||
'@auth/core@0.41.3':
|
||||
resolution: {integrity: sha512-sJ3JMHHkXMD3aOjopv7mOBTO1Ocw4b0fAEXJBz6k7YHLpYQI6C40jCUPc5fNvUKxXRXNE1/sRISA15UrwWJBTw==}
|
||||
'@auth/core@0.41.2':
|
||||
resolution: {integrity: sha512-Hx5MNBxN2fJTbJKGUKAA0wca43D0Akl3TvufY54Gn8lop7F+34vU1zA1pn0vQfIoVuLIrpfc2nkyjwIaPJMW7w==}
|
||||
peerDependencies:
|
||||
'@simplewebauthn/browser': ^9.0.1
|
||||
'@simplewebauthn/server': ^9.0.2
|
||||
nodemailer: ^7.0.7 || ^8.0.5
|
||||
nodemailer: ^7.0.7
|
||||
peerDependenciesMeta:
|
||||
'@simplewebauthn/browser':
|
||||
optional: true
|
||||
@@ -3360,8 +3360,8 @@ packages:
|
||||
nodemailer:
|
||||
optional: true
|
||||
|
||||
'@auth/drizzle-adapter@1.11.3':
|
||||
resolution: {integrity: sha512-TxqVasPVuf7LDAT1Yuu6bftpuet8o0tjdXW+mXpnXWdSRPQsomdzMoz9RsXkFN/JfdK+/DwdgOEmOTv1gbiFNw==}
|
||||
'@auth/drizzle-adapter@1.11.2':
|
||||
resolution: {integrity: sha512-VOuj7REI8jfJjpSbsYwDM/Zrn55T6lS9Yc+29V+EXMcel8eqG7x+7LodNfd1WHjakfkLYi+qsbYUHB3E6aDA4w==}
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||
@@ -13685,13 +13685,13 @@ packages:
|
||||
neverpanic@0.0.8:
|
||||
resolution: {integrity: sha512-vVdkelrLxaow/fdWDumzNBO+jwm6X8bxeLJc34THtpj70u0C5QBkcV6CRCu2X726km7XD45N0A3QtYCla4RvKw==}
|
||||
|
||||
next-auth@5.0.0-beta.32:
|
||||
resolution: {integrity: sha512-CGlChIEWZ6LltNVxrE5yiySMID+Idpmry47JYA5lLwgD8Sx02a8M65VL0TWVz9nbnOioS/tCW/rP/0+mE7Qp4Q==}
|
||||
next-auth@5.0.0-beta.31:
|
||||
resolution: {integrity: sha512-1OBgCKPzo+S7UWWMp3xgvGvIJ0OpV7B3vR4ZDRqD9a4Ch+OT6dakLXG9ivhtmIWVa71nTSXattOHyCg8sNi8/Q==}
|
||||
peerDependencies:
|
||||
'@simplewebauthn/browser': ^9.0.1
|
||||
'@simplewebauthn/server': ^9.0.2
|
||||
next: ^14.0.0-0 || ^15.0.0 || ^16.0.0
|
||||
nodemailer: ^7.0.7 || ^8.0.5
|
||||
nodemailer: ^7.0.7
|
||||
react: ^18.2.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@simplewebauthn/browser':
|
||||
@@ -17439,7 +17439,7 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
react: 19.2.7
|
||||
|
||||
'@auth/core@0.41.3':
|
||||
'@auth/core@0.41.2':
|
||||
dependencies:
|
||||
'@panva/hkdf': 1.2.1
|
||||
jose: 6.2.3
|
||||
@@ -17447,9 +17447,9 @@ snapshots:
|
||||
preact: 10.24.3
|
||||
preact-render-to-string: 6.5.11(preact@10.24.3)
|
||||
|
||||
'@auth/drizzle-adapter@1.11.3':
|
||||
'@auth/drizzle-adapter@1.11.2':
|
||||
dependencies:
|
||||
'@auth/core': 0.41.3
|
||||
'@auth/core': 0.41.2
|
||||
transitivePeerDependencies:
|
||||
- '@simplewebauthn/browser'
|
||||
- '@simplewebauthn/server'
|
||||
@@ -30001,9 +30001,9 @@ snapshots:
|
||||
|
||||
neverpanic@0.0.8: {}
|
||||
|
||||
next-auth@5.0.0-beta.32(next@16.3.1(@babel/core@7.29.0(supports-color@10.2.2))(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(@types/node@24.13.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7):
|
||||
next-auth@5.0.0-beta.31(next@16.3.1(@babel/core@7.29.0(supports-color@10.2.2))(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(@types/node@24.13.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0))(react@19.2.7):
|
||||
dependencies:
|
||||
'@auth/core': 0.41.3
|
||||
'@auth/core': 0.41.2
|
||||
next: 16.3.1(@babel/core@7.29.0(supports-color@10.2.2))(@opentelemetry/api@1.9.0)(@playwright/test@1.61.1)(@types/node@24.13.3)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(sass@1.101.0)
|
||||
react: 19.2.7
|
||||
|
||||
|
||||
+3
-3
@@ -14,8 +14,8 @@ catalog:
|
||||
"@codemirror/state": ^6.6.0
|
||||
"@codemirror/theme-one-dark": ^6.1.3
|
||||
"@codemirror/view": ^6.43.0
|
||||
"@auth/core": ^0.41.3
|
||||
"@auth/drizzle-adapter": ^1.11.3
|
||||
"@auth/core": ^0.41.2
|
||||
"@auth/drizzle-adapter": ^1.11.2
|
||||
"@ctrl/deluge": ^8.0.0
|
||||
"@ctrl/qbittorrent": ^9.14.0
|
||||
"@ctrl/transmission": ^7.4.0
|
||||
@@ -174,7 +174,7 @@ catalog:
|
||||
maria2: ^0.4.1
|
||||
mysql2: 3.22.6
|
||||
next: 16.3.1
|
||||
next-auth: 5.0.0-beta.32
|
||||
next-auth: 5.0.0-beta.31
|
||||
next-intl: 4.13.1
|
||||
next-themes: ^0.4.6
|
||||
node-loader: ^2.1.0
|
||||
|
||||
Reference in new issue
Block a user