Add Better Auth admin authentication
This commit is contained in:
45
tests/better-auth-component.test.ts
Normal file
45
tests/better-auth-component.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
test("local Better Auth component exports adapter functions", async () => {
|
||||
const source = await readFile(
|
||||
join(process.cwd(), "convex/betterAuth/adapter.ts"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
for (const exportName of [
|
||||
"create",
|
||||
"findOne",
|
||||
"findMany",
|
||||
"updateOne",
|
||||
"updateMany",
|
||||
"deleteOne",
|
||||
"deleteMany",
|
||||
]) {
|
||||
assert.match(source, new RegExp(`\\b${exportName}\\b`));
|
||||
}
|
||||
|
||||
assert.match(source, /createApi\(schema, createSchemaAuthOptions\)/);
|
||||
});
|
||||
|
||||
test("Better Auth email/password signup is disabled server-side", async () => {
|
||||
const source = await readFile(
|
||||
join(process.cwd(), "convex/betterAuth/auth.ts"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.match(source, /disableSignUp:\s*true/);
|
||||
});
|
||||
|
||||
test("auth entry only exposes sign in", async () => {
|
||||
const source = await readFile(
|
||||
join(process.cwd(), "components/auth-entry.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
assert.doesNotMatch(source, /signUp/);
|
||||
assert.doesNotMatch(source, /Account anlegen/);
|
||||
assert.match(source, /authClient\.signIn\.email/);
|
||||
});
|
||||
47
tests/better-auth-env.test.ts
Normal file
47
tests/better-auth-env.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { getBetterAuthServerConfig } from "../convex/betterAuth/env";
|
||||
|
||||
test("getBetterAuthServerConfig requires BETTER_AUTH_SECRET", () => {
|
||||
assert.throws(
|
||||
() => getBetterAuthServerConfig({}),
|
||||
/Missing BETTER_AUTH_SECRET/,
|
||||
);
|
||||
});
|
||||
|
||||
test("getBetterAuthServerConfig can use a placeholder secret for schema generation", () => {
|
||||
const config = getBetterAuthServerConfig(
|
||||
{},
|
||||
{ allowMissingSecret: true },
|
||||
);
|
||||
|
||||
assert.deepEqual(config, {
|
||||
appUrl: "http://localhost:3000",
|
||||
secret: "convex-better-auth-schema-generation-secret",
|
||||
});
|
||||
});
|
||||
|
||||
test("getBetterAuthServerConfig uses BETTER_AUTH_URL before NEXT_PUBLIC_APP_URL", () => {
|
||||
const config = getBetterAuthServerConfig({
|
||||
BETTER_AUTH_SECRET: "test-secret",
|
||||
BETTER_AUTH_URL: "http://auth.local",
|
||||
NEXT_PUBLIC_APP_URL: "http://app.local",
|
||||
});
|
||||
|
||||
assert.deepEqual(config, {
|
||||
appUrl: "http://auth.local",
|
||||
secret: "test-secret",
|
||||
});
|
||||
});
|
||||
|
||||
test("getBetterAuthServerConfig falls back to local app URL", () => {
|
||||
const config = getBetterAuthServerConfig({
|
||||
BETTER_AUTH_SECRET: "test-secret",
|
||||
});
|
||||
|
||||
assert.deepEqual(config, {
|
||||
appUrl: "http://localhost:3000",
|
||||
secret: "test-secret",
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
MOCK_SESSION_COOKIE_NAME,
|
||||
MOCK_SESSION_COOKIE_VALUE,
|
||||
createClearedMockSessionCookie,
|
||||
createMockSessionCookie,
|
||||
getMockSession,
|
||||
hasMockSession,
|
||||
} from "../lib/mock-auth";
|
||||
|
||||
type CookieLookupName = string;
|
||||
|
||||
test("hasMockSession returns true only for the expected mock session cookie", () => {
|
||||
assert.equal(
|
||||
hasMockSession({
|
||||
get: (name: CookieLookupName) =>
|
||||
name === MOCK_SESSION_COOKIE_NAME
|
||||
? { name, value: MOCK_SESSION_COOKIE_VALUE }
|
||||
: undefined,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
hasMockSession({
|
||||
get: () => ({ name: MOCK_SESSION_COOKIE_NAME, value: "wrong" }),
|
||||
}),
|
||||
false,
|
||||
);
|
||||
|
||||
assert.equal(hasMockSession({ get: () => undefined }), false);
|
||||
});
|
||||
|
||||
test("createMockSessionCookie creates an http-only lax root cookie", () => {
|
||||
assert.deepEqual(createMockSessionCookie(), {
|
||||
name: MOCK_SESSION_COOKIE_NAME,
|
||||
value: MOCK_SESSION_COOKIE_VALUE,
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
});
|
||||
});
|
||||
|
||||
test("getMockSession returns the simulated admin user only when the cookie is valid", () => {
|
||||
const validStore = {
|
||||
get: (name: CookieLookupName) =>
|
||||
name === MOCK_SESSION_COOKIE_NAME
|
||||
? { name, value: MOCK_SESSION_COOKIE_VALUE }
|
||||
: undefined,
|
||||
};
|
||||
|
||||
assert.deepEqual(getMockSession(validStore), {
|
||||
name: "Matthias Meister",
|
||||
email: "matthias@webdev-pipeline.local",
|
||||
});
|
||||
|
||||
assert.equal(getMockSession({ get: () => undefined }), null);
|
||||
});
|
||||
|
||||
test("createClearedMockSessionCookie expires the mock session at the root path", () => {
|
||||
assert.deepEqual(createClearedMockSessionCookie(), {
|
||||
name: MOCK_SESSION_COOKIE_NAME,
|
||||
value: "",
|
||||
path: "/",
|
||||
maxAge: 0,
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { shouldRedirectDashboardRequest } from "../lib/proxy-auth";
|
||||
|
||||
test("shouldRedirectDashboardRequest protects dashboard paths without a valid mock cookie", () => {
|
||||
assert.equal(
|
||||
shouldRedirectDashboardRequest("/dashboard", undefined),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
shouldRedirectDashboardRequest("/dashboard/leads", "wrong"),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldRedirectDashboardRequest allows valid mock sessions and non-dashboard paths", () => {
|
||||
assert.equal(
|
||||
shouldRedirectDashboardRequest(
|
||||
"/dashboard",
|
||||
"mock-admin",
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(shouldRedirectDashboardRequest("/audit/example", undefined), false);
|
||||
});
|
||||
@@ -1,18 +1,39 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { getDashboardRedirectPath } from "../lib/route-guards";
|
||||
import {
|
||||
getDashboardRedirectPath,
|
||||
shouldRedirectDashboardRequest,
|
||||
} from "../lib/route-guards";
|
||||
|
||||
test("getDashboardRedirectPath sends guests back to the auth entry", () => {
|
||||
assert.equal(getDashboardRedirectPath(null), "/");
|
||||
test("getDashboardRedirectPath keeps authenticated users on dashboard", () => {
|
||||
assert.equal(getDashboardRedirectPath(true), null);
|
||||
});
|
||||
|
||||
test("getDashboardRedirectPath lets authenticated mock users stay on dashboard", () => {
|
||||
test("getDashboardRedirectPath redirects unauthenticated users to the login page", () => {
|
||||
assert.equal(getDashboardRedirectPath(false), "/login");
|
||||
});
|
||||
|
||||
test("shouldRedirectDashboardRequest protects dashboard paths without authentication", () => {
|
||||
assert.equal(
|
||||
getDashboardRedirectPath({
|
||||
name: "Matthias Meister",
|
||||
email: "matthias@webdev-pipeline.local",
|
||||
}),
|
||||
null,
|
||||
shouldRedirectDashboardRequest("/dashboard", false),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
shouldRedirectDashboardRequest("/dashboard/leads", false),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldRedirectDashboardRequest allows non-dashboard paths without authentication", () => {
|
||||
assert.equal(
|
||||
shouldRedirectDashboardRequest("/audit/example", false),
|
||||
false,
|
||||
);
|
||||
assert.equal(shouldRedirectDashboardRequest("/", false), false);
|
||||
});
|
||||
|
||||
test("shouldRedirectDashboardRequest allows authenticated dashboard sessions", () => {
|
||||
assert.equal(shouldRedirectDashboardRequest("/dashboard", true), false);
|
||||
assert.equal(shouldRedirectDashboardRequest("/dashboard/audit", true), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user