Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added warning message that fires on startup when host environment contains env vars that simple-git flags as unsafe. [#1193](https://github.com/sourcebot-dev/sourcebot/pull/1193)
- Added a loading skeleton to the latest commit info bar in the code browser. [#1195](https://github.com/sourcebot-dev/sourcebot/pull/1195)
- Added `authz/require-auth-wrapper` ESLint rule that flags API route handlers and server actions missing a `withAuth`/`withOptionalAuth` wrapper at the export boundary. [#1199](https://github.com/sourcebot-dev/sourcebot/pull/1199)

### Fixed
- Add missing schema changes introduced in [#1170](https://github.com/sourcebot-dev/sourcebot/pull/1170). [#1176](https://github.com/sourcebot-dev/sourcebot/pull/1176)
Expand Down
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