Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/web/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import nextCoreWebVitals from 'eslint-config-next/core-web-vitals';
import tseslint from 'typescript-eslint';
import tanstackQuery from '@tanstack/eslint-plugin-query';
import authzLocal from './tools/eslint-plugin-local/index.mjs';

const config = [
...nextCoreWebVitals,
...tseslint.configs.recommended,
...tanstackQuery.configs['flat/recommended'],
{
plugins: {
authz: authzLocal,
},
rules: {
'authz/require-auth-wrapper': 'error',
},
},
{
rules: {
// New react-hooks v7 rules disabled as too strict for this codebase's existing patterns.
Expand Down
5 changes: 5 additions & 0 deletions packages/web/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ export const getOrgAccountRequests = async () => sew(() =>
}));
}));

// eslint-disable-next-line authz/require-auth-wrapper -- calls getAuthenticatedUser() directly; runs pre-org-membership so cannot use withAuth
export const createAccountRequest = async () => sew(async () => {
const authResult = await getAuthenticatedUser();
if (!authResult) {
Expand Down Expand Up @@ -920,6 +921,7 @@ export const createAccountRequest = async () => sew(async () => {
}
});

// eslint-disable-next-line authz/require-auth-wrapper -- public org-config bit consulted on login/signup screens before any session exists
export const getMemberApprovalRequired = async (): Promise<boolean | ServiceError> => sew(async () => {
const org = await __unsafePrisma.org.findUnique({
where: {
Expand Down Expand Up @@ -1181,6 +1183,7 @@ export const getRepoImage = async (repoId: number): Promise<ArrayBuffer | Servic
})
});

// eslint-disable-next-line authz/require-auth-wrapper -- public org-config bit consulted before authentication to decide whether to gate the UI
export const getAnonymousAccessStatus = async (): Promise<boolean | ServiceError> => sew(async () => {
const org = await __unsafePrisma.org.findUnique({
where: { id: SINGLE_TENANT_ORG_ID },
Expand Down Expand Up @@ -1244,6 +1247,7 @@ export const setAnonymousAccessStatus = async (enabled: boolean): Promise<Servic
});
});

// eslint-disable-next-line authz/require-auth-wrapper -- UI-only preference cookie, no DB access
export const setAgenticSearchTutorialDismissedCookie = async (dismissed: boolean) => sew(async () => {
const cookieStore = await cookies();
cookieStore.set(AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME, dismissed ? "true" : "false", {
Expand All @@ -1253,6 +1257,7 @@ export const setAgenticSearchTutorialDismissedCookie = async (dismissed: boolean
return true;
});

// eslint-disable-next-line authz/require-auth-wrapper -- UI-only preference cookie, no DB access
export const dismissMobileUnsupportedSplashScreen = async () => sew(async () => {
const cookieStore = await cookies();
cookieStore.set(MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, 'true');
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/[...slug]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ const handler = () => {
});
}

// eslint-disable-next-line authz/require-auth-wrapper -- 404 catch-all for unknown API endpoints, returns no user data
export { handler as GET, handler as POST, handler as PUT, handler as PATCH, handler as DELETE }
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import { handlers } from "@/auth";
// eslint-disable-next-line authz/require-auth-wrapper -- NextAuth's own auth-flow handlers, not user-data endpoints
export const { GET, POST } = handlers;
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/blame/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { queryParamsSchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getFileBlame() which calls withOptionalAuth
export const GET = apiHandler(async (request: NextRequest) => {
const rawParams = Object.fromEntries(
Object.keys(fileBlameRequestSchema.shape).map(key => [
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/chat/blocking/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const blockingChatRequestSchema = z.object({
* The chat session is persisted to the database, allowing users to view the full
* conversation (including tool calls and reasoning) in the web UI.
*/
// eslint-disable-next-line authz/require-auth-wrapper -- delegates to askCodebase() which calls withOptionalAuth
export const POST = apiHandler(async (request: NextRequest) => {
const requestBody = await request.json();
const parsed = await blockingChatRequestSchema.safeParseAsync(requestBody);
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/commit/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { queryParamsSchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getCommit() which calls withOptionalAuth
export const GET = apiHandler(async (request: NextRequest): Promise<Response> => {
const rawParams = Object.fromEntries(
Object.keys(getCommitQueryParamsSchema.shape).map(key => [
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/commits/authors/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { serviceErrorResponse, queryParamsSchemaValidationError } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to listCommitAuthors() which calls withOptionalAuth
export const GET = apiHandler(async (request: NextRequest): Promise<Response> => {
const rawParams = Object.fromEntries(
Object.keys(listCommitAuthorsQueryParamsSchema.shape).map(key => [
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/commits/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { serviceErrorResponse, queryParamsSchemaValidationError } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to listCommits() which calls withOptionalAuth
export const GET = apiHandler(async (request: NextRequest): Promise<Response> => {
const rawParams = Object.fromEntries(
Object.keys(listCommitsQueryParamsSchema.shape).map(key => [
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/diff/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { queryParamsSchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getDiff() which calls withOptionalAuth
export const GET = apiHandler(async (request: NextRequest): Promise<Response> => {
const rawParams = Object.fromEntries(
Object.keys(getDiffRequestSchema.shape).map(key => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OAUTH_NOT_SUPPORTED_ERROR_MESSAGE } from '@/ee/features/oauth/constants

// RFC 8414: OAuth 2.0 Authorization Server Metadata
// @see: https://datatracker.ietf.org/doc/html/rfc8414
// eslint-disable-next-line authz/require-auth-wrapper -- RFC 8414 public metadata endpoint
export const GET = oauthApiHandler(async () => {
if (!hasEntitlement('oauth')) {
return Response.json(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const PROTECTED_RESOURCES = new Set([
'api/mcp'
]);

// eslint-disable-next-line authz/require-auth-wrapper -- RFC 9728 public metadata endpoint
export const GET = oauthApiHandler(async (_request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) => {
if (!hasEntitlement('oauth')) {
return Response.json(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const queryParamsSchema = z.object({
jobId: z.string(),
});

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getAccountSyncStatus() which calls withAuth
export const GET = apiHandler(async (request: NextRequest) => {
const rawParams = {
jobId: request.nextUrl.searchParams.get('jobId') ?? undefined,
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/ee/audit/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const auditQueryParamsSchema = auditQueryParamsBaseSchema.refine(
{ message: "'since' must be before 'until'", path: ["since"] }
);

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to fetchAuditRecords() which calls withAuth + withMinimumOrgRole(OWNER)
export const GET = apiHandler(async (request: NextRequest) => {
const entitlements = getEntitlements();
if (!entitlements.includes('audit')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const registerRequestSchema = z.object({
logo_uri: z.string().url().nullish(),
});

// eslint-disable-next-line authz/require-auth-wrapper -- RFC 7591 dynamic client registration, intentionally unauthenticated
export const POST = oauthApiHandler(async (request: NextRequest) => {
if (!hasEntitlement('oauth')) {
return Response.json(
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/ee/oauth/revoke/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { OAUTH_NOT_SUPPORTED_ERROR_MESSAGE } from '@/ee/features/oauth/constants
// RFC 7009: OAuth 2.0 Token Revocation
// Always returns 200 regardless of whether the token existed.
// @see: https://datatracker.ietf.org/doc/html/rfc7009
// eslint-disable-next-line authz/require-auth-wrapper -- RFC 7009 token revocation, no user session required
export const POST = oauthApiHandler(async (request: NextRequest) => {
if (!hasEntitlement('oauth')) {
return Response.json(
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/ee/oauth/token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { OAUTH_NOT_SUPPORTED_ERROR_MESSAGE } from '@/ee/features/oauth/constants
// OAuth 2.0 Token Endpoint
// Supports grant_type=authorization_code with PKCE (RFC 7636).
// @see: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2
// eslint-disable-next-line authz/require-auth-wrapper -- OAuth token endpoint, authenticated via PKCE code / refresh token, not user session
export const POST = oauthApiHandler(async (request: NextRequest) => {
if (!hasEntitlement('oauth')) {
return Response.json(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getPermissionSyncStatus } from "./api";
* Returns whether a user has a account that has it's permissions
* synced for the first time.
*/
// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getPermissionSyncStatus() which calls withAuth
export const GET = apiHandler(async () => {
const result = await getPermissionSyncStatus();

Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/files/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getFiles() which calls withOptionalAuth
export const POST = apiHandler(async (request: NextRequest) => {
const body = await request.json();
const parsed = await getFilesRequestSchema.safeParseAsync(body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to findSearchBasedSymbolDefinitions() which calls withOptionalAuth
export const POST = apiHandler(async (request: NextRequest) => {
const body = await request.json();
const parsed = await findRelatedSymbolsRequestSchema.safeParseAsync(body);
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/find_references/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to findSearchBasedSymbolReferences() which calls withOptionalAuth
export const POST = apiHandler(async (request: NextRequest) => {
const body = await request.json();
const parsed = await findRelatedSymbolsRequestSchema.safeParseAsync(body);
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/health/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createLogger } from "@sourcebot/shared";

const logger = createLogger('health-check');

// eslint-disable-next-line authz/require-auth-wrapper -- public health check, no user data returned
export const GET = apiHandler(async () => {
logger.debug('health check');
return Response.json({ status: 'ok' });
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/mcp/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export const DELETE = apiHandler(async (request: NextRequest) => {
// supported. Per the MCP Streamable HTTP spec, servers that do not offer a GET SSE
// stream MUST return 405 Method Not Allowed.
// @see: https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#listening-for-messages-from-the-server
// eslint-disable-next-line authz/require-auth-wrapper -- MCP spec mandates 405 for GET when SSE stream is unsupported; no user data
export const GET = apiHandler(async (_request: NextRequest) => {
return new Response(null, {
status: StatusCodes.METHOD_NOT_ALLOWED,
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/openapi.json/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ async function loadOpenApiDocument() {
return JSON.parse(await fs.readFile(openApiPath, 'utf8'));
}

// eslint-disable-next-line authz/require-auth-wrapper -- public OpenAPI spec, intentionally unauthenticated
export const GET = apiHandler(async () => {
const document = await loadOpenApiDocument();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { serviceErrorResponse } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getRepoInfo() which calls withOptionalAuth
export const GET = apiHandler(async (
_request: NextRequest,
{ params }: { params: Promise<{ repoId: string }> }
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/repos/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";
import { listRepos } from "./listReposApi";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to listRepos() which calls withOptionalAuth
export const GET = apiHandler(async (request: NextRequest) => {
const rawParams = Object.fromEntries(
Object.keys(listReposQueryParamsSchema.shape).map(key => [
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to search() which calls withOptionalAuth
export const POST = apiHandler(async (request: NextRequest) => {
const body = await request.json();
const parsed = await searchRequestSchema.safeParseAsync(body);
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/source/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { queryParamsSchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getFileSource() which calls withOptionalAuth
export const GET = apiHandler(async (request: NextRequest) => {
const rawParams = Object.fromEntries(
Object.keys(fileSourceRequestSchema.shape).map(key => [
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/stream_search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { requestBodySchemaValidationError, serviceErrorResponse } from '@/lib/se
import { isServiceError } from '@/lib/utils';
import { NextRequest } from 'next/server';

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to streamSearch() which calls withOptionalAuth
export const POST = apiHandler(async (request: NextRequest) => {
const body = await request.json();
const parsed = await searchRequestSchema.safeParseAsync(body);
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/tree/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/se
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getTree() which calls withOptionalAuth
export const POST = apiHandler(async (request: NextRequest) => {
const body = await request.json();
const parsed = await getTreeRequestSchema.safeParseAsync(body);
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/version/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { GetVersionResponse } from "@/lib/types";
// @see: https://nextjs.org/docs/14/app/building-your-application/routing/route-handlers#caching
export const dynamic = "force-dynamic";

// eslint-disable-next-line authz/require-auth-wrapper -- public Sourcebot version string, no user data
export const GET = apiHandler(async () => {
return Response.json({
version: SOURCEBOT_VERSION,
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/(server)/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ if (env.GITLAB_REVIEW_AGENT_TOKEN) {
}
}

// eslint-disable-next-line authz/require-auth-wrapper -- authenticated via GitHub App / GitLab webhook secrets, not user session
export const POST = async (request: NextRequest) => {
const body = await request.json();
const headers = Object.fromEntries(Array.from(request.headers.entries(), ([key, value]) => [key.toLowerCase(), value]));
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/minidenticon/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { apiHandler } from '@/lib/apiHandler';

// Generates a minidenticon avatar PNG from an email address.
// Used as a fallback avatar in emails where data URIs aren't supported.
// eslint-disable-next-line authz/require-auth-wrapper -- public identicon generator, no user data returned
export const GET = apiHandler(async (request: NextRequest) => {
const email = request.nextUrl.searchParams.get('email');
if (!email) {
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/api/repos/[repoId]/image/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { apiHandler } from "@/lib/apiHandler";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";

// eslint-disable-next-line authz/require-auth-wrapper -- delegates to getRepoImage() action which calls withOptionalAuth
export const GET = apiHandler(async (
_request: NextRequest,
{ params }: { params: Promise<{ repoId: string }> }
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/app/invite/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getAuditService } from "@/ee/features/audit/factory";

const auditService = getAuditService();

// eslint-disable-next-line authz/require-auth-wrapper -- runs pre-org-membership; uses getAuthenticatedUser() directly since withAuth requires a user-to-org link this call is establishing
export const joinOrganization = async (inviteLinkId?: string) => sew(async () => {
const authResult = await getAuthenticatedUser();
if (!authResult) {
Expand Down Expand Up @@ -71,6 +72,7 @@ export const joinOrganization = async (inviteLinkId?: string) => sew(async () =>
}
});

// eslint-disable-next-line authz/require-auth-wrapper -- runs pre-org-membership; uses getAuthenticatedUser() directly since withAuth requires a user-to-org link this call is establishing
export const redeemInvite = async (inviteId: string): Promise<{ success: boolean; } | ServiceError> => sew(async () => {
const authResult = await getAuthenticatedUser();
if (!authResult) {
Expand Down Expand Up @@ -161,6 +163,7 @@ export const redeemInvite = async (inviteId: string): Promise<{ success: boolean
});


// eslint-disable-next-line authz/require-auth-wrapper -- runs pre-org-membership; uses getAuthenticatedUser() directly since the invitee is not yet a member
export const getInviteInfo = async (inviteId: string) => sew(async () => {
const authResult = await getAuthenticatedUser();
if (!authResult) {
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/ee/features/sso/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export const unlinkLinkedAccountProvider = async (provider: string) => sew(() =>
)
);

// eslint-disable-next-line authz/require-auth-wrapper -- UI-only preference cookie, no DB access
export const skipOptionalProvidersLink = async () => sew(async () => {
const cookieStore = await cookies();
cookieStore.set(OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME, 'true', {
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/features/chat/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ export const submitFeedback = async ({
})
)

// eslint-disable-next-line authz/require-auth-wrapper -- returns identity provider metadata for the login wall, consulted before auth
export const getAskGhLoginWallData = async () => sew(async () => {
const isEnabled = env.EXPERIMENT_ASK_GH_ENABLED === 'true';
if (!isEnabled) {
Expand Down
12 changes: 12 additions & 0 deletions packages/web/tools/eslint-plugin-local/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import requireAuthWrapper from './rules/requireAuthWrapper.mjs';

const plugin = {
meta: {
name: 'eslint-plugin-authz-local',
},
rules: {
'require-auth-wrapper': requireAuthWrapper,
},
};

export default plugin;
Loading
Loading