Skip to content
Closed
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
19 changes: 17 additions & 2 deletions apps/web/src/apis/applications/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export const ApplicationsQueryKeys = {
applicationPreview: "applicationPreview",
} as const;

// ====== Utils ======
/** 유효한 양의 정수만 쿼리 파라미터로 내보낸다. (universities/api.ts 와 동일한 규칙) */
const normalizePositiveInt = (value: unknown) => {
const numberValue = typeof value === "string" && value.trim() !== "" ? Number(value) : value;

return typeof numberValue === "number" && Number.isInteger(numberValue) && numberValue > 0 ? numberValue : undefined;
};

// ====== Types ======
export interface UseSubmitApplicationResponse {
totalApplyCount: number;
Expand Down Expand Up @@ -37,9 +45,16 @@ export interface CompetitorsResponse {
export const applicationsApi = {
/**
* 지원 목록 조회
*
* homeUniversityId 는 클라이언트가 임의로 정하는 값이 아니라,
* access token 에서 파싱된 로그인 사용자의 소속 대학(useAuthStore.homeUniversityId)을 그대로 전달한다.
*/
getApplicationsList: async (): Promise<AxiosResponse<ApplicationListResponse>> => {
return axiosInstance.get("/applications");
getApplicationsList: async (params?: {
homeUniversityId?: number | null;
}): Promise<AxiosResponse<ApplicationListResponse>> => {
return axiosInstance.get("/applications", {
params: { homeUniversityId: normalizePositiveInt(params?.homeUniversityId) },
});
Comment on lines +48 to +57

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 대학 식별자와 애플리케이션 조회의 서버 처리 위치를 찾는다.
rg -n -C 8 'homeUniversityId|home_university|/applications' .

Repository: solid-connection/solid-connect-web

Length of output: 50397


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== apps/web/src/apis/applications/api.ts =="
cat -n apps/web/src/apis/applications/api.ts

echo
echo "== token parsing and store =="
cat -n apps/web/src/lib/zustand/useAuthStore.ts | sed -n '1,120p'

echo
echo "== application calls/usages in apps/web =="
rg -n 'getApplicationsList|ApplicationsList|applicationsList|homeUniversityId' apps/web --glob '*.{ts,tsx}'

Repository: solid-connection/solid-connect-web

Length of output: 16914


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== GET /applications and competitors fixtures =="
for f in \
  packages/bruno-api-typescript/tests/fixtures/bruno-v2/applications/get-applicants.bru-1-meta \
  packages/bruno-api-typescript/tests/fixtures/bruno-v2/applications/get-applicants.bru \
  packages/bruno-api-typescript/tests/fixtures/bruno-v2/applications/get-competitors.bru \
  packages/bruno-api-typescript/tests/fixtures/bruno/applications/get-applicants.bru-1-meta \
  packages/bruno-api-typescript/tests/fixtures/bruno/applications/get-applicants.bru \
  packages/bruno-api-typescript/tests/fixtures/bruno/applications/get-competitors.bru
do
  if [ -f "$f" ]; then
    echo "--- $f"
    cat -n "$f" | sed -n '1,220p'
  fi
done

echo "== API definitions for /applications =="
rg -n -C 12 '"path": "\{\{URL\}\}/applications"|"/applications"|applicants|homeUniversityId' packages/api-schema src apps packages/bruno-api-typescript/tests/fixtures/bruno packages/bruno-api-typescript/tests/fixtures/bruno-v2 | sed -n '1,260p'

echo "== generated schema paths containing applications =="
python3 - <<'PY'
import json, pathlib
for p in pathlib.Path('packages/api-schema').rglob('*.json'):
    try:
        data=json.loads(p.read_text())
    except Exception: continue
    k='/applications'
    ks='/applications/competitors'
    ks2='/applications/applicants'
    hits=[]
    for path, methods in data.get('paths',{}).items():
        if path == k or path == ks or path == ks2:
            hits.append((str(p),path,methods))
    if hits:
        for hit in hits:
            print('---',hit[0])
            print(hit[1], list(hit[2]))
            for m,v in hit[2].items():
                print(m,v)
PY

Repository: solid-connection/solid-connect-web

Length of output: 22682


1. homeUniversityId가 클라이언트 임의 값임을 문서에 포함하지 마세요.

- `GET /applications`는 현재 accept header와 query params만 명시되어 있어, client가 token에서 읽은 값을 그대로 query string으로 바꿀 수 있습니다.
- `"homeUniversityId 는 클라이언트가 임의로 정하는 값이 아니라"`라는 주석은 서버 계약과 다릅니다.

2. 서버는 token 기반으로 접근 범위를 검증하세요.

- 서버가 `homeUniversityId`를 사용한다면 서버 내부 인증 정보에서 파손되지 않은 값으로 권한 경계를 확인한 후 필터로만 사용하세요.
- request가 단순 필터처럼 보이거나 서버 검증이 보이지 않으면 권한 bypass 위험을 문서화해 서버 쪽 보안 검증 계약과 맞추세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/apis/applications/api.ts` around lines 48 - 57, The comment
above getApplicationsList incorrectly describes homeUniversityId as exclusively
derived from the authenticated user and raises a server authorization concern.
Remove or revise that client-side assertion to match the API contract, and
ensure the server-side applications endpoint validates the token-derived
university scope before using homeUniversityId only as a permitted filter;
document the required authorization contract if server validation is outside
this diff.

},

/**
Expand Down
10 changes: 8 additions & 2 deletions apps/web/src/apis/applications/getApplicants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type UseQueryOptions, type UseQueryResult, useQuery } from "@tanstack/react-query";
import type { AxiosError, AxiosResponse } from "axios";

import useAuthStore from "@/lib/zustand/useAuthStore";
import type { ApplicationListResponse } from "@/types/application";
import { ApplicationsQueryKeys, applicationsApi } from "./api";

Expand All @@ -11,13 +12,18 @@ type UseGetApplicationsListOptions = Omit<

/**
* @description 지원 목록 조회 훅
*
* 소속 대학(homeUniversityId)은 access token 에서 파싱되어 useAuthStore 에 담긴 값을 사용한다.
* 다른 소속 대학의 응답이 캐시에 섞이지 않도록 queryKey 에도 포함한다.
*/
const useGetApplicationsList = (
props?: UseGetApplicationsListOptions,
): UseQueryResult<ApplicationListResponse, AxiosError<{ message: string }>> => {
const homeUniversityId = useAuthStore((state) => state.homeUniversityId);

return useQuery({
queryKey: [ApplicationsQueryKeys.competitorsApplicationList],
queryFn: applicationsApi.getApplicationsList,
queryKey: [ApplicationsQueryKeys.competitorsApplicationList, homeUniversityId],
queryFn: () => applicationsApi.getApplicationsList({ homeUniversityId }),
staleTime: 1000 * 60 * 5, // 5분간 캐시
select: (response) => response.data,
...props,
Expand Down
Loading