fix(sso): prevent auto-linking with an existing account in a different org (#607)
* fix(sso): prevent auto-linking with an existing account in a different org * chore: lint issue * chore: bump @better-auth/sso
This commit is contained in:
parent
b082afe761
commit
f511095fde
12 changed files with 124 additions and 36 deletions
|
|
@ -165,7 +165,11 @@ export function SettingsPage({ appContext, initialMembers, initialSsoSettings, i
|
|||
<CardContent className="p-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Username</Label>
|
||||
<Input value={appContext.user?.username || ""} disabled className="max-w-md" />
|
||||
<Input value={appContext.user?.username} disabled className="max-w-md" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Email</Label>
|
||||
<Input value={appContext.user?.email} disabled className="max-w-md" />
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const Route = createFileRoute("/(dashboard)/settings/")({
|
|||
}),
|
||||
]);
|
||||
const orgRole = orgContext.activeMember?.role;
|
||||
const shouldPrefetchOrgQueries = authContext.user?.role === "admin" || orgRole === "owner" || orgRole === "admin";
|
||||
const shouldPrefetchOrgQueries = orgRole === "owner" || orgRole === "admin";
|
||||
|
||||
if (shouldPrefetchOrgQueries) {
|
||||
const [org, members, appOrigin] = await Promise.all([
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { beforeEach, describe, expect, test } from "bun:test";
|
||||
import type { GenericEndpointContext } from "@better-auth/core";
|
||||
import type { GenericEndpointContext } from "better-auth";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "~/server/db/db";
|
||||
import { account, invitation, member, organization, ssoProvider, usersTable } from "~/server/db/schema";
|
||||
|
|
@ -119,6 +119,45 @@ describe("createUserDefaultOrg", () => {
|
|||
expect(createUserDefaultOrg(userId, ctx)).rejects.toThrow("invite-only");
|
||||
});
|
||||
|
||||
test("blocks existing users with a personal org from SSO orgs they were not invited to", async () => {
|
||||
const userId = await createUser("alice@example.com", randomSlug("alice"));
|
||||
const inviterId = await createUser("inviter@example.com", randomSlug("inviter"));
|
||||
|
||||
const personalOrgId = randomId();
|
||||
await db.insert(organization).values({
|
||||
id: personalOrgId,
|
||||
name: "Alice's Workspace",
|
||||
slug: randomSlug("alice"),
|
||||
createdAt: new Date(),
|
||||
});
|
||||
await db.insert(member).values({
|
||||
id: randomId(),
|
||||
userId,
|
||||
organizationId: personalOrgId,
|
||||
role: "owner",
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
const ssoOrgId = randomId();
|
||||
await db.insert(organization).values({
|
||||
id: ssoOrgId,
|
||||
name: "Acme Corp",
|
||||
slug: randomSlug("acme"),
|
||||
createdAt: new Date(),
|
||||
});
|
||||
await db.insert(ssoProvider).values({
|
||||
id: randomId(),
|
||||
providerId: "oidc-acme",
|
||||
organizationId: ssoOrgId,
|
||||
userId: inviterId,
|
||||
issuer: "https://issuer.example.com",
|
||||
domain: "example.com",
|
||||
});
|
||||
|
||||
const ctx = createMockSsoCallbackContext("oidc-acme");
|
||||
await expect(createUserDefaultOrg(userId, ctx)).rejects.toThrow("invite-only");
|
||||
});
|
||||
|
||||
test("returns existing membership without creating another workspace", async () => {
|
||||
const userId = await createUser("existing-member@example.com", randomSlug("existing-member"));
|
||||
const organizationId = randomId();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { and, eq, gt } from "drizzle-orm";
|
||||
import { UnauthorizedError } from "http-errors-enhanced";
|
||||
import type { GenericEndpointContext } from "@better-auth/core";
|
||||
import type { GenericEndpointContext } from "better-auth";
|
||||
import { db } from "~/server/db/db";
|
||||
import { invitation, member, organization, ssoProvider, usersTable, type User } from "~/server/db/schema";
|
||||
import { cryptoUtils } from "~/server/utils/crypto";
|
||||
|
|
@ -150,17 +150,36 @@ export async function createUserDefaultOrg(userId: string, ctx: GenericEndpointC
|
|||
throw new UnauthorizedError("User not found");
|
||||
}
|
||||
|
||||
const providerId = extractProviderIdFromContext(ctx);
|
||||
if (providerId) {
|
||||
const [ssoProviderRecord] = await db
|
||||
.select()
|
||||
.from(ssoProvider)
|
||||
.where(eq(ssoProvider.providerId, providerId))
|
||||
.limit(1);
|
||||
|
||||
if (ssoProviderRecord) {
|
||||
// If the user is already a member of this SSO org (accepted a past invitation), let them through
|
||||
const existingSsoMembership = await findMembershipWithOrganization(user.id, ssoProviderRecord.organizationId);
|
||||
if (existingSsoMembership) {
|
||||
return existingSsoMembership;
|
||||
}
|
||||
|
||||
// Not yet a member of this SSO org, a valid pending invitation is required.
|
||||
const invitedMembership = await tryCreateInvitedMembership(userId, normalizeEmail(user.email), ctx);
|
||||
if (invitedMembership) {
|
||||
return invitedMembership;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-SSO path: check for any existing membership before creating a personal org.
|
||||
const existingMembership = await findMembershipWithOrganization(user.id);
|
||||
if (existingMembership) {
|
||||
logger.debug("User already has an organization membership, skipping default org creation", { userId });
|
||||
return existingMembership;
|
||||
}
|
||||
|
||||
const invitedMembership = await tryCreateInvitedMembership(userId, normalizeEmail(user.email), ctx);
|
||||
if (invitedMembership) {
|
||||
return invitedMembership;
|
||||
}
|
||||
|
||||
await createDefaultOrganizationMembership(user);
|
||||
|
||||
const newMembership = await findMembershipWithOrganization(userId);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { beforeEach, describe, expect, test } from "bun:test";
|
||||
import type { GenericEndpointContext } from "@better-auth/core";
|
||||
import type { GenericEndpointContext } from "better-auth";
|
||||
import { db } from "~/server/db/db";
|
||||
import { account, invitation, member, organization, ssoProvider, usersTable } from "~/server/db/schema";
|
||||
import { isSsoCallbackRequest, requireSsoInvitation } from "../require-sso-invitation";
|
||||
|
|
|
|||
|
|
@ -71,4 +71,22 @@ describe("validateSsoCallbackUrls", () => {
|
|||
|
||||
expect(validateSsoCallbackUrls(ctx)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("rejects URL-encoded external URL (%2F%2Fevil.example)", async () => {
|
||||
const ctx = createContext("/sign-in/sso", { callbackURL: "%2F%2Fevil.example" });
|
||||
|
||||
expect(validateSsoCallbackUrls(ctx)).rejects.toThrow("callbackURL");
|
||||
});
|
||||
|
||||
test("rejects URL-encoded reserved path (%2Fsso%2Fcallback%2Ffoo)", async () => {
|
||||
const ctx = createContext("/sign-in/sso", { callbackURL: "%2Fsso%2Fcallback%2Ffoo" });
|
||||
|
||||
expect(validateSsoCallbackUrls(ctx)).rejects.toThrow("callbackURL");
|
||||
});
|
||||
|
||||
test("rejects invalid URL encoding (malformed)", async () => {
|
||||
const ctx = createContext("/sign-in/sso", { callbackURL: "%ZZ" });
|
||||
|
||||
expect(validateSsoCallbackUrls(ctx)).rejects.toThrow("callbackURL");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { APIError } from "better-auth/api";
|
||||
import type { GenericEndpointContext } from "@better-auth/core";
|
||||
import type { GenericEndpointContext } from "better-auth";
|
||||
import { db } from "~/server/db/db";
|
||||
import { logger } from "~/server/utils/logger";
|
||||
import { extractProviderIdFromContext, extractProviderIdFromUrl, normalizeEmail } from "../utils/sso-context";
|
||||
|
|
|
|||
|
|
@ -2,11 +2,17 @@ import { APIError } from "better-auth/api";
|
|||
import type { AuthMiddlewareContext } from "~/server/lib/auth";
|
||||
|
||||
function isValidCallbackPath(value: string): boolean {
|
||||
if (!value.startsWith("/") || value.startsWith("//") || value.includes("\\")) {
|
||||
let decoded: string;
|
||||
try {
|
||||
decoded = decodeURIComponent(value);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (!decoded.startsWith("/") || decoded.startsWith("//") || decoded.includes("\\")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.startsWith("/sso/callback/") || value.startsWith("/sso/saml2/")) {
|
||||
if (decoded.startsWith("/sso/callback/") || decoded.startsWith("/sso/saml2/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { GenericEndpointContext } from "@better-auth/core";
|
||||
import type { GenericEndpointContext } from "better-auth";
|
||||
|
||||
const SSO_CALLBACK_PATH_SEGMENTS = ["/sso/callback/", "/sso/saml2/callback/", "/sso/saml2/sp/acs/"] as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -46,20 +46,22 @@ export const authController = new Hono()
|
|||
}
|
||||
|
||||
const [providersData, invitationsData, autoLinkingSettings] = await Promise.all([
|
||||
auth.api.listSSOProviders({ headers }),
|
||||
auth.api.listSSOProviders({ headers, query: { organizationId: activeOrganizationId } }),
|
||||
auth.api.listInvitations({ headers, query: { organizationId: activeOrganizationId } }),
|
||||
authService.getSsoProviderAutoLinkingSettings(activeOrganizationId),
|
||||
]);
|
||||
|
||||
return c.json<SsoSettingsDto>({
|
||||
providers: providersData.providers.map((provider) => ({
|
||||
providerId: provider.providerId,
|
||||
type: provider.type,
|
||||
issuer: provider.issuer,
|
||||
domain: provider.domain,
|
||||
autoLinkMatchingEmails: autoLinkingSettings[provider.providerId] ?? false,
|
||||
organizationId: provider.organizationId,
|
||||
})),
|
||||
providers: providersData.providers
|
||||
.map((provider) => ({
|
||||
providerId: provider.providerId,
|
||||
type: provider.type,
|
||||
issuer: provider.issuer,
|
||||
domain: provider.domain,
|
||||
autoLinkMatchingEmails: autoLinkingSettings[provider.providerId] ?? false,
|
||||
organizationId: provider.organizationId,
|
||||
}))
|
||||
.filter((p) => p.organizationId === activeOrganizationId),
|
||||
invitations: invitationsData.map((invitation) => ({
|
||||
id: invitation.id,
|
||||
email: invitation.email,
|
||||
|
|
|
|||
22
bun.lock
22
bun.lock
|
|
@ -5,7 +5,7 @@
|
|||
"": {
|
||||
"name": "zerobyte",
|
||||
"dependencies": {
|
||||
"@better-auth/sso": "1.5.0",
|
||||
"@better-auth/sso": "^1.5.2",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"@tanstack/react-router-ssr-query": "^1.162.9",
|
||||
"@tanstack/react-start": "^1.162.9",
|
||||
"arktype": "^2.1.28",
|
||||
"better-auth": "1.5.0",
|
||||
"better-auth": "^1.5.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"commander": "^14.0.2",
|
||||
|
|
@ -216,21 +216,21 @@
|
|||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@better-auth/core": ["@better-auth/core@1.5.0", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "better-call": "1.3.2", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-nDPmW7I9VGRACEei31fHaZxGwD/yICraDllZ/f25jbWXYaxDaW88RuH1ZhbOUKmGJlZtDxcjN1+YmcVIc1ioNw=="],
|
||||
"@better-auth/core": ["@better-auth/core@1.5.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "better-call": "1.3.2", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-svaKRVN/p3+g++kljLEedHC+RgDlGsVr87tKiATr5xIE7xqLO1If906pMTNMfhF08N5r7pMbix/mRYdObuPKHA=="],
|
||||
|
||||
"@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.5.0", "", { "peerDependencies": { "@better-auth/core": "1.5.0", "@better-auth/utils": "^0.3.0", "drizzle-orm": ">=0.41.0" } }, "sha512-qNKAoe+ViiHznipkoOCLo3pDvQo4/6mYbCwnfo2mNbjjopqIn0c3IKW2t+e/Mg4Y18PJcEFeOiIRoY9pUyTtAg=="],
|
||||
"@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.5.2", "", { "peerDependencies": { "@better-auth/core": "1.5.2", "@better-auth/utils": "^0.3.0", "drizzle-orm": ">=0.41.0" } }, "sha512-29e7UCwqTriIuDdEr1xbSx4qGg6Ag3aTopzRavPyOCYJyzTwePw8iZ9zaJF1fsLmLeany7LW069NMDf6+3tz/w=="],
|
||||
|
||||
"@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.5.0", "", { "peerDependencies": { "@better-auth/core": "1.5.0", "@better-auth/utils": "^0.3.0", "kysely": "^0.27.0 || ^0.28.0" } }, "sha512-dSC7yzF58YMrn39srrX/LwMLjd11PtTORjn3RKFwuzVCS07mqMZpPkk3XbQW/ZXxXbdUByrgYQo9z9zFCF37RQ=="],
|
||||
"@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.5.2", "", { "peerDependencies": { "@better-auth/core": "1.5.2", "@better-auth/utils": "^0.3.0", "kysely": "^0.27.0 || ^0.28.0" } }, "sha512-PaP+KPJ6Cw0DxzuZLH6eR0oFhS5Iq/KjYvmZt76fCl5Q7Ys9Cct0kXVVlrD1bftiznTpV+UKMfwjcCWvtc1l4w=="],
|
||||
|
||||
"@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.5.0", "", { "peerDependencies": { "@better-auth/core": "1.5.0", "@better-auth/utils": "^0.3.0" } }, "sha512-fp0OtEpWi4RgfxrhhAI8tKW8yHTHx49V12UW1XyuUKrEK0jbu+CzVJSzoMkv/q3fJfjGqDe/vNvWtVBtpIRpQw=="],
|
||||
"@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.5.2", "", { "peerDependencies": { "@better-auth/core": "1.5.2", "@better-auth/utils": "^0.3.0" } }, "sha512-vCbmMpUAisISRVxzNFIxkvO3wm63fzwSxAOO9Xktl77VyEyh8zliBNcW8S6+9DOeolVRKhjWEg7UaBOtIhFX/Q=="],
|
||||
|
||||
"@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.5.0", "", { "peerDependencies": { "@better-auth/core": "1.5.0", "@better-auth/utils": "^0.3.0", "mongodb": "^6.0.0 || ^7.0.0" } }, "sha512-qI+MiewH6kzUbNkKBX4fdhPl1MkIXq8V7v3TEt/DLAHC4/QxmPqhxQHEDeZBxO+NH8+Zekr2sQzKRRFfbaQKbw=="],
|
||||
"@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.5.2", "", { "peerDependencies": { "@better-auth/core": "1.5.2", "@better-auth/utils": "^0.3.0", "mongodb": "^6.0.0 || ^7.0.0" } }, "sha512-fJd+Z7fgRFgK2W7oxbFYM9K10yEUs/hlALzihCdDgSyRg9XFLBMJkMcECRqBsop/cZUjwZxLBTQPsIpgx9ix1g=="],
|
||||
|
||||
"@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.5.0", "", { "dependencies": { "@prisma/client": "^7.4.1" }, "peerDependencies": { "@better-auth/core": "1.5.0", "@better-auth/utils": "^0.3.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-Sga8DTeCCsamGZ7/54kH0HKlrCrgW4EApadAgsufsIcRqHteeKC5rNCLIppfyRa/xJxm5xImUeks6Nvz3zL1tw=="],
|
||||
"@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.5.2", "", { "dependencies": { "@prisma/client": "^7.4.1" }, "peerDependencies": { "@better-auth/core": "1.5.2", "@better-auth/utils": "^0.3.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-9GB4q91Q16wSSsi1vFP/RZNW0GNmdxTiDcE2MX1fC4Hlmo6UuT4WRv+94YPYs3/tPhsJ17k5Lt+rKTOK8S6Kbw=="],
|
||||
|
||||
"@better-auth/sso": ["@better-auth/sso@1.5.0", "", { "dependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "fast-xml-parser": "^5.4.1", "jose": "^6.1.3", "samlify": "^2.10.2", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "1.5.0", "better-auth": "1.5.0", "better-call": "1.3.2" } }, "sha512-HuomPV25de9y2A/8WX4UtDHdHj4OUlwbnTH/NAuhmqUGyj7VZrOBwUaZhmD6roVhF18+YigOmGPetajukMZrQQ=="],
|
||||
"@better-auth/sso": ["@better-auth/sso@1.5.2", "", { "dependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "fast-xml-parser": "^5.4.1", "jose": "^6.1.3", "samlify": "^2.10.2", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "1.5.2", "better-auth": "1.5.2", "better-call": "1.3.2" } }, "sha512-qPOkai2aBjJzwee3RuSYc17R5OPB9P1IONpKFHiAXBmp1RkUUwMwwN/QZC7PuJaecaZYSgKwRQ4+Ip4XYqYTeA=="],
|
||||
|
||||
"@better-auth/telemetry": ["@better-auth/telemetry@1.5.0", "", { "dependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.5.0" } }, "sha512-/6ThGSnGPVTR4A/6F8kv65UomDHtM24y2yRZuJfWYbqkve0jn8+WVsxOfQN9bx7J16zIvYw5hJAYCwxoj20BTQ=="],
|
||||
"@better-auth/telemetry": ["@better-auth/telemetry@1.5.2", "", { "dependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.5.2" } }, "sha512-Cukyj5yciFNGtx5SkQCop2wOjpHuq2ZHL3V7ydVFe51WUNCXd+v1Sbed2sIfT/8hOcSB+NefALXNv1yTSxKkAQ=="],
|
||||
|
||||
"@better-auth/utils": ["@better-auth/utils@0.3.1", "", {}, "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg=="],
|
||||
|
||||
|
|
@ -1000,7 +1000,7 @@
|
|||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
||||
|
||||
"better-auth": ["better-auth@1.5.0", "", { "dependencies": { "@better-auth/core": "1.5.0", "@better-auth/drizzle-adapter": "1.5.0", "@better-auth/kysely-adapter": "1.5.0", "@better-auth/memory-adapter": "1.5.0", "@better-auth/mongo-adapter": "1.5.0", "@better-auth/prisma-adapter": "1.5.0", "@better-auth/telemetry": "1.5.0", "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.2", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.11", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-vbRKSxDa1vwXzEmR7+kXJMDs2pRXLOvD4MiqQwL4r6kkjzok5kOyLD200ZUg24Jtx2Xho1oA2drlxT6GqknH1A=="],
|
||||
"better-auth": ["better-auth@1.5.2", "", { "dependencies": { "@better-auth/core": "1.5.2", "@better-auth/drizzle-adapter": "1.5.2", "@better-auth/kysely-adapter": "1.5.2", "@better-auth/memory-adapter": "1.5.2", "@better-auth/mongo-adapter": "1.5.2", "@better-auth/prisma-adapter": "1.5.2", "@better-auth/telemetry": "1.5.2", "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.2", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.11", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-xEnt/RBsu/cjcN+IEsgeBqka/6QRaGzPFl5hnnJfZU/JxRGnDsVUDHXvJQRfecj8qA/1nPJOQjZ6qQL1Z7aN+Q=="],
|
||||
|
||||
"better-call": ["better-call@1.3.2", "", { "dependencies": { "@better-auth/utils": "^0.3.1", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-4cZIfrerDsNTn3cm+MhLbUePN0gdwkhSXEuG7r/zuQ8c/H7iU0/jSK5TD3FW7U0MgKHce/8jGpPYNO4Ve+4NBw=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"test:codegen": "playwright codegen localhost:4096"
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-auth/sso": "1.5.0",
|
||||
"@better-auth/sso": "^1.5.2",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
"@tanstack/react-router-ssr-query": "^1.162.9",
|
||||
"@tanstack/react-start": "^1.162.9",
|
||||
"arktype": "^2.1.28",
|
||||
"better-auth": "1.5.0",
|
||||
"better-auth": "^1.5.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"commander": "^14.0.2",
|
||||
|
|
|
|||
Loading…
Reference in a new issue