하루50분4교시(교시별 정리 포함)+휴식40분으로4시간,5일 합계20시간이다. 생성도구·Git·구축 절차·프로젝트 사례를 관련 교시에서 함께 설명한다.
일차
주제
결과
1
무료 AI로 시작하는 나의 앱
나의 앱 기획 카드·첫 공개 링크·파일/CSV 자동화 결과
2
Codex와 Git 작업 흐름
개선 앱·커밋2개·PR과 Pages 재배포·Docker 환경
3
과제 앱의 DB·로그인·Vercel 공개
회원별 과제 DB·이메일 가입/로그인·권한 검사·Vercel URL
4
과제 앱의 SNS 로그인과 MFA
SNS 설정표·네이버 어댑터·TOTP MFA·DB 권한 검증
5
과제 앱의 CSV·검증·최종 릴리스
CSV 앱·계정별 검사표·최종 배포 기록·발표와 인수인계
하루 시간표
구분
경과 시간
1교시
00:00–00:50
휴식
00:50–01:00
2교시
01:00–01:50
휴식
01:50–02:00
3교시
02:00–02:50
휴식
02:50–03:10
4교시
03:10–04:00
사전 준비와 진도 대비
강사는 새 수업용 저장소와 테스트 계정을 준비하고 핵심 프롬프트를 미리 실행해 기준 앱과 교시별 복구 지점을 만든다. Resend DNS 검증과 SMTP, SNS 개발자 앱·대상 사용자·필요 승인·callback 설정은 수업 전에 준비한다. 네이버 어댑터와 별도 이메일 확인은 기준 구현을 마련한다. 수강생은 기능 연결·상태 관찰·오류 경로 검증에 집중한다. 실습 ZIP은 과제 체크 완성 HTML·입력 데이터·프롬프트·SQL 참고·설정 양식으로 구성한다.
자료 사용 방법
메인 자료 허브에서 일차별 발표·교재·예제 코드·심화를 선택한다. 발표는 DAY01~05 순서로 진행한다. 방향키·N노트·O목차·F전체화면을 사용한다.
각 교시의 R-Dx-y 카드에서 관련 생성도구·Git·구축 절차를 확인하고 구체적인 프로젝트 판단을 실습에 적용한다.
긴 명령/설정표/전체 연표는 별도4파일에 있다. 교재의 선택 심화를 펼치거나 상단 심화 자료 링크에서 일차별로 찾는다.
선택 심화는 해당 교시의 설명을 대체하거나 실습 뒤 사용한다. 추가 수업시간이 아니다.
각 일차 발표는 day-1.html~day-5.html의 독립 파일이다. #slide=H01 또는 #lesson=D2-3으로 해당 파일 안의 위치를 공유한다. 이전 index.html 슬라이드 주소도 해당 일차로 이어진다.
노트와 예제 요청은 수강생도 볼 수 있는 통합본이다. 왼쪽 교재 검색은 섹션 단위 필터다.
현재 본문은196장이다. 기존240장판의5일 실습을 유지하고 뒤쪽65장에 분리돼 있던 원문을 별도 파일로 옮겼다.20개 교시별 연결 카드와 Git 상태 시뮬레이터를 본문에 배치했다. HTML 링크/앵커/식별자, 브라우저16:9·일차이동·검색·노트·복사·별도파일 왕복을 확인한다. 프로젝트 기록의 조사 시점과 실제/모의/미확인 범위는 각 원문에 보존했다.
세 프로젝트의 주 DB는 Supabase PostgreSQL이다. MySQL·SQLite는 관계형 DB, MongoDB는 문서형 DB의 비교 예시다. uc-anchor의 선택형 Python PDF 서비스는 Next.js·Vercel 기본 배포와 분리해 설명한다. 현재 운영 전체 기능을 재검증한 표는 아니다.
누가 어떤 순간에 불편한가요? / 이 앱으로 어떤 행동이 쉬워지나요? / 오늘 한 가지 기능만 만든다면 무엇인가요?
007
어디에서 운영하고 싶은가요?
웹 vs 모바일?
사용자는 PC와 휴대폰 중 어디에서 주로 쓰나요? / 주소 공유·홈 화면 설치·스토어 배포 중 무엇이 필요한가요?
008
웹 · PWA · 모바일 앱, 이렇게 고릅니다
009
5분 활동: 나의 앱 기획 카드
아래 단계별 실습을 진행한다.
010
무료 AI에 입력할 첫 프롬프트
아래 전체 구현 요청을 사용한다.
011
따라 만들기: 과제 체크 앱
필수: 과제명·마감일 입력,완료 표시,새로고침 후 복원 / 선택 개선: 필터·오늘/기한지남 집계·삭제 되돌리기 / 단일 index.html · 서버/DB/API/설치 없음 / 생성12분 · 배포8분 · 짝 검증6분에 사용
012
과제 체크 앱: 무료 AI 생성 요청
아래 전체 구현 요청을 사용한다.
013
로컬 확인: index.html 저장과 실행
아래 단계별 실습을 진행한다.
014
GitHub: 공개 저장소에 index.html 올리기
아래 단계별 실습을 진행한다.
015
GitHub Pages: main / (root)로 배포
아래 단계별 실습을 진행한다.
016
짝 검증과 강사 확인: 실행한 것만 통과
필수: 빈값 거절·추가·완료·새로고침 후 저장·휴대폰 실행 / 선택: 어제/오늘/내일 과제의 집계와 필터 확인 / 선택: 삭제와 되돌리기 후 원래 상태 확인 / 기록: URL·확인한 기능·실패 또는 미확인·다음 기능 한 개
017
무료 AI로 시작하는 나의 앱
무료 개발·배포 경로를 고르고, 내가 만들 앱의 사용자·핵심 기능·운영 형태를 설명한다. / 산출물: 나의 앱 기획 카드와 기능 한 개가 동작하는 공개 웹앱 링크 / 시연8 · 질문7 · 카드5 · 생성12 · 배포8 · 짝검증6 · 정리4분
018
핵심 개념
무료 AI 개발 도구와 무료 호스팅의 역할 / 만들고 싶은 앱의 사용자·문제·핵심 동작 / 웹·PWA·모바일 앱의 실행과 배포 경로
019
생성도구: 빠른 시작과 첫 공개 링크
무료 AI로 작은 HTML 앱을 만들고 GitHub Pages에서 첫 링크를 공유한다. / AI Studio·Sites·v0·Lovable·Bolt는 같은 요구를 시제품으로 확인하는 선택지다. / 생성 화면·실제 저장·로그인·공개 배포를 각각 확인한다. / 과제 앱의 필수 기능3개를 먼저 완성하고 도구 비교는 필요할 때 연다.
020
kysong · 이번 실습에 적용할 판단
기록: 연구 포털을 만든 뒤 자료·설문·내보내기 기능을 순차 확장했다. / 비교: 첫날 과제 앱도 작은 목록과 공개 링크부터 완성한다. / 적용: 만들고 싶은 앱·사용자·웹/모바일 운영 위치를 한 장에 적는다.
021
실습 절차
아래 단계별 실습을 진행한다.
022
무료 AI에 입력할 기획 요청
아래 전체 구현 요청을 사용한다.
023
완료 확인과 오류 대응
나의 앱과 사용자·해결할 문제를 한 문장으로 말한다. / 웹·PWA·모바일 선택 이유와 무료 배포 조건을 설명한다. / 공개 URL에서 핵심 동작1개를 짝이 확인한다.
학생용 시작: 무료 경로 제안 → 두 질문 → 기획 카드
진행
분
무료 경로 시연
8
앱·운영 방식 질문
7
기획 카드
5
작은 앱 생성
12
공개 배포
8
짝 검증
6
정리
4
FIELD GUIDE
S01 · 학생이 무료로 시작할 수 있는 AI 도구
지금 접근 가능한 도구 한 개를 골라 작은 앱을 만든다.
도구
첫 수업 활용
무료 이용 조건
Gemini 기본 / Canvas
기획·화면·코드 초안
표준 사용량·학교 계정 정책 확인
ChatGPT / Codex Free
기획·코드 작성·수정
사용량 제한, Codex 데스크톱은 순차 제공
Google AI Studio Build
대화로 웹 시제품 생성
18세 이상·지원 계정/지역, 배포 조건 별도
Copilot Free / Student
에디터에서 코드 도움
Free 한도, Student는 학생 인증·활성화
강사 해설과 적용 범위
2026-11-10 공식 문서 확인. Gemini 기본 플랜과 Canvas, OpenAI Docs의 Free Codex 데스크톱 제공 조건을 참고한다. 특정 모델이나 요청 횟수를 무제한으로 약속하지 않는다. Copilot 학생 인증은 수업 중 기다리지 않고 사전 준비한다. 18세 미만 학생에게 AI Studio를 필수로 요구하지 않는다. 도구 접근이 어려우면 강사 예제 코드로 실습을 이어간다.
추천 기본 경로는 별도 API 키·서버 없이 실행되는 정적 웹앱이다. AI는 개발을 돕고 완성 앱은 브라우저에서 동작한다. localStorage는 같은 origin의 브라우저 저장이며 다른 기기와 자동 공유되지 않는다. GitHub Pages는 HTML·CSS·JS를 제공하며 서버·DB·비밀키를 실행하는 곳으로 설명하지 않는다.
GitHub Pages는 수업용 공개 소스와 가상 데이터만 올린다. Vercel Hobby는 개인 비상업 프로젝트에 적용한다. AI Studio Starter Tier는 적격 계정 최대2개 앱을 공개할 수 있으나 Workspace for Education 등 Workspace 계정, 현재 또는 과거 유료 Cloud billing 보유 계정 등이 부적격일 수 있다. 일반 Cloud 배포는 billing을 요구하며 Gemini API·외부 DB·메일 등은 별도 요금과 한도를 확인한다. 유료 전환이나 카드 등록을 수업 참여 조건으로 삼지 않는다.
여기부터 학생의 답을 먼저 듣는다. AI 도구 목록을 추가로 소개하지 않는다. 1분 개인 메모 뒤2명에게 사용자가 누구인지와 불편한 순간을 묻는다. “학교생활 관리 앱”처럼 넓은 답은 “내일 마감 과제를 빠뜨리지 않기”처럼 관찰 가능한 상황으로 줄인다. 첫 기능 예시는 과제 추가·완료 표시, 학습 타이머 시작/중지, 단어 카드 뒤집기다.
직전 질문에서 고른 앱의 사용자와 이용 장소를 연결한다. “휴대폰에서 쓰니까 모바일 앱”으로 바로 결정하지 않고, 브라우저 링크가 충분한지, 홈 화면 설치가 필요한지, OS 기능과 스토어 유통이 핵심인지 이유를 말하게 한다. 정답을 고르는 시험이 아니라 배포와 검증 범위를 결정하는 활동이다.
웹도 휴대폰에서 사용할 수 있다. PWA는 웹 기술로 만들며 설치 조건과 기기·브라우저 지원을 확인한다. 오프라인·알림은 자동으로 생기지 않고 구현과 지원 검증이 필요하다. 모바일 학습 경로는 React Native/Expo 예시로 소개하고 Expo Go 기기 실행과 실제 앱스토어 출시를 구분한다. 스토어 등록·빌드·심사·개발자 비용은 플랫폼별 별도 확인 항목이다. 이번5일 과정의 공통 실습은 웹 배포이며 모바일 선택자는 웹으로 핵심 흐름을 먼저 검증하고 후속 계획을 남긴다.
실습ZIP의 worksheets/student-app-card.csv를 채운다. 생성형 AI를 기능으로 넣거나 동시 공유·회원가입이 필요한 앱은 그 부분을 다음 단계로 적는다. 오늘은 가상 데이터와 기기 내부 저장으로 핵심 동작을 확인한다. 짝에게30초 소개하고 사용자가 실제로 필요로 하는 기능인지 피드백을 받는다.
학생이 사용하는 [앱 이름]을 만들어줘. 사용자 [누구], 해결할 문제 [상황], 핵심 동작 [한 가지]다. HTML·CSS·JavaScript를 index.html 한 파일에 작성해줘. 휴대폰과 PC에서 읽기 쉬운 화면, 입력 검증, 빈 상태, 오류 안내, 초기화 기능을 넣어줘. 저장이 필요하면 이 기기의 localStorage만 사용하고 다른 사람이나 기기와 공유되지 않는다고 표시해줘. 로그인·DB·유료 API·외부 AI 호출·결제·서버 기능은 추가하지 마. 가상 데이터만 사용해줘. 실행할 전체 코드, 저장과 테스트 방법, GitHub Pages 공개 저장소의 main/(root)에서 배포하는 절차와 확인 질문을 제시해줘.
강사 해설과 적용 범위
기획 카드의 앱 이름·사용자·문제·핵심 동작을 자리표시자에 넣고 요청한다. 출력 코드를 index.html로 저장해 실행하고 하나씩 수정한다. 생성 앱에 런타임 AI API·서버가 들어가면 요청한 무료 정적 범위로 되돌린다. 생성 프롬프트와 검증 프롬프트를 복사해 사용한다. 수업 전 준비한 공개 저장소를 사용하고 첫 배포 대기는 다음 활동과 병행한다.
Codex 구현 요청
학생이 사용하는 [앱 이름]을 만들어줘. 사용자 [누구], 해결할 문제 [상황], 핵심 동작 [한 가지]다. HTML·CSS·JavaScript를 index.html 한 파일에 작성해줘. 휴대폰과 PC에서 읽기 쉬운 화면, 입력 검증, 빈 상태, 오류 안내, 초기화 기능을 넣어줘. 저장이 필요하면 이 기기의 localStorage만 사용하고 다른 사람이나 기기와 공유되지 않는다고 표시해줘. 로그인·DB·유료 API·외부 AI 호출·결제·서버 기능은 추가하지 마. 가상 데이터만 사용해줘. 실행할 전체 코드, 저장과 테스트 방법, GitHub Pages 공개 저장소의 main/(root)에서 배포하는 절차와 확인 질문을 제시해줘.
Codex 검증 요청
새로고침 뒤 이 기기의 저장 유지, 빈 입력과 잘못된 값, 초기화, 휴대폰 화면, 공개 URL을 다른 브라우저에서 열었을 때를 확인해줘. 같은 origin·브라우저의 localStorage에만 저장되고 기기 간 동기화되지 않음을 설명해줘. 서버·API·결제·비밀키 의존성이 없는지 확인하고 통과한 항목과 미확인 항목을 나눠줘.
기존1교시50분 중 생성12분·배포8분·짝검증6분의 예제다. 계정은 수업 전에 준비한다. 무료 한도나 코드 오류로 막히면 완성 ZIP으로 이어가며 나머지 필수 동작을 확인한다. 기준 코드는 강사 검증용 준비본이며 특정 무료 AI에서 실제 생성했다는 실행 기록을 뜻하지 않는다.
완성 ZIP의 assignment-check-demo 폴더 안 index.html을 사용한다. Pages에 올릴 때는 이 파일을 저장소 최상위에 둔다. 앱의 입력값은 브라우저별 localStorage에 저장된다.
Gemini 기본 또는 ChatGPT Free 등 접근 가능한 무료 도구 한 개를 사용한다. 생성→선택 개선→검증을 나누고 전체 수정 코드를 확인한다.
1. 생성 요청 · 필수 기능
학생용 과제 체크 웹앱을 만들어줘. HTML·CSS·JavaScript를 index.html 한 파일에 넣고 외부 라이브러리·서버·DB·로그인·유료 API·앱 내 AI 호출 없이 실행되게 해줘. 과제명(필수, 공백만 불가,60자 이내), 과목(선택,20자 이내), 마감일(필수)을 입력해 목록에 추가하고 완료 체크를 할 수 있게 해줘. 현재 브라우저의 localStorage에 저장하고 새로고침 뒤 복원해줘. 이 브라우저에만 저장되고 다른 기기와 공유되지 않는다고 화면에 표시해줘. 날짜는 기기의 로컬 달력 날짜로 비교해줘. label과 네이티브 입력·버튼,빈 상태·입력 오류·저장 실패 안내, 휴대폰에서 한 열로 보이는 디자인을 넣어줘. 사용자 입력은 textContent로 표시해줘. 처음에는 목록을 비워두고 가상 예제 버튼은 선택으로 제공해줘. 전체 코드와 저장/실행 방법, 실제 확인할 체크리스트를 줘.
2. 선택 개선 요청
현재 동작하는 과제 추가·완료·localStorage 기능을 유지하며 최소 수정해줘. 전체/미완료/완료 필터와 미완료 우선·마감일순 정렬,남은 과제·오늘 마감·기한 지남 집계를 추가해줘. 오늘 마감은 오늘과 같은 날짜, 기한 지남은 오늘보다 이전인 미완료 과제만 세어줘. 개별 삭제와 되돌리기도 추가해줘. 기능을 바꾸기 전 현재 코드를 읽고 변경 지점을 설명한 뒤 전체 수정 코드를 줘. 새 서버·API·결제·공동 저장은 추가하지 마.
3. 검증·오류 해결 요청
이 과제 체크 앱을 실제 확인한 결과로 검토해줘. 빈 과제명과 공백만 입력,오늘/어제/내일 마감,완료 표시,새로고침 후 복원,휴대폰 화면,공개 Pages URL을 검사해줘. 필터·집계·삭제 되돌리기를 구현했다면 해당 동작도 확인해줘. 다른 브라우저나 기기에 데이터가 자동 공유되지 않는 이유와 file/localhost/Pages 저장 영역 차이를 설명해줘. 오류가 있으면 재현 단계·현재 코드·실제 결과·예상 결과를 근거로 최소 수정해줘. 실행하지 않은 항목을 통과라고 쓰지 마. API 키나 계정 비밀번호는 요구하지 마.
일반 텍스트 편집기에 붙여넣고 UTF-8의 index.html로 저장한다. ```html 같은 코드 울타리는 넣지 않는다. 확장자가 .html.txt가 아닌지 확인하고 브라우저에서 연다. “국어 독서 감상문”,과목“국어”,오늘 날짜를 넣고 추가·완료·새로고침을 시험한다.
학생 확인
파일 환경의 저장이 제한될 수 있다. 저장 오류 안내를 보고, 최종 보존 확인은 공개 HTTPS Pages에서 한다. 초기 화면의 빈 목록은 정상이다.
강사용 설명
문서 편집기나 워드 형식으로 저장하지 않도록 시연한다. 오류를 고칠 때 현재 코드·입력·실제 결과·예상 결과를 AI에 준다. 수정 후 성공한 기능을 다시 확인한다.
GitHub에서 New repository를 열고 이름 assignment-check,Public을 선택한다. Add a README file을 켜고 Create repository로 만든다. Code 화면의 Add file→Upload files에서 index.html을 최상위에 올리고 Commit changes로 저장한다.
학생 확인
저장소 첫 화면에서 README.md와 index.html이 같은 깊이에 보여야 한다. 완성 ZIP을 풀었다면 안쪽 index.html 파일을 올리며 ZIP 자체나 한 단계 위 폴더를 올리지 않는다.
강사용 설명
공개할 소스에는 가상 데이터만 둔다. 브라우저에서 입력한 과제 목록이 GitHub에 자동 올라가는 것은 아니다. 파일 업로드와 저장소 commit,Pages 공개는 다른 단계임을 보여준다.
저장소 Settings→Pages로 이동한다. Build and deployment의 Source에서 Deploy from a branch를 고른다. Branch는 index.html을 올린 main,폴더는/(root),Save를 누른다. 기본 브랜치 이름이 다르면 실제 파일을 올린 브랜치를 선택한다.
학생 확인
별도 유료 서비스·결제·API 키·커스텀 도메인은 필요하지 않다. GitHub Free의 이 경로는 공개 저장소로 진행한다. 저장소에 파일과 브랜치가 먼저 있어야 한다.
강사용 설명
Save 직후 바로 성공이라고 말하지 않는다. Pages 설정과 Actions에서 배포 작업의 진행·완료를 확인한다. 공개 예제는 main/(root)로 실제 배포한다.
Pages가 알려주는 Visit site 주소를 열고 완료 상태를 확인한다. 프로젝트 주소 형식은 https://사용자명.github.io/assignment-check/이다. 휴대폰이나 짝에게 링크를 보내 핵심 기능을 시험한다. 배포가 대기 중이면 상태를 기록하고 다음 활동 중 완료를 확인한다.
학생 확인
github.com의 소스 저장소 주소와 github.io의 실행 앱 주소를 구분한다. 로컬 파일에 넣은 목록이 공개 앱에 자동 복사되지 않는 것은 정상이다.
강사용 설명
Pages 배포에는 시간이 걸릴 수 있다. 배포8분은 설정·상태 확인 시간이며 대기 중에는 강사 예제로 짝 검증을 이어간다. 완료 후 각자 URL을 기록한다.
도구별 새 계정 가입을 모두 실습하지 않는다. 첫날은 무료 시작과 Pages 완주에 시간을 쓰고 도구 비교표는 선택 심화로 연결한다.
강사용 설명
수업 전 GitHub 계정·무료 AI 접근·공개 저장소를 준비한다. S01~S08을 먼저 사용한다. 무료경로시연8·질문7·카드5·생성12·배포8·짝검증6·정리4=50분이다. 모든 AI 도구에 가입시키지 않고 접근 가능한 한 개만 고른다. 강사는 결제 없이 실행하는 단일 HTML 예제를 준비한다. Pages 배포가 대기 중이면 로컬 동작과 저장소 설정을 검증하고 링크 확인은 다음 교시 시작에 마무리한다. 학생이 제안한 복잡한 앱은 핵심 동작 한 개로 줄인다. 학교 계정·연령·지역 정책을 확인하며, Google AI Studio는18세 이상 조건이 있어 미충족 학생에게 계정 생성을 요구하지 않는다. 앱 제작에 AI를 쓰는 것과 배포 앱에서 모델 API를 호출하는 것은 사용량과 비용이 다르다. 2~4교시 파일·CSV 자동화는 앱의 입력 데이터를 다루는 연습,4교시는 오늘 카드의 상세 설계로 연결한다. 선택 실습은 H01~H06의 과제 체크 예제다. 생성12분·배포8분·짝검증6분을 이 예제 또는 자율 앱 한 개에 사용한다. 기준 HTML과 단계별 교재·완성 ZIP을 제공하며 필수 기능부터 완주한다.
실습 절차
8분 시연: 무료 AI 한 개로 만든 index.html을 GitHub Pages에서 공개하는 경로를 본다.
7분 질문: 진짜 만들고 싶은 앱과 사용할 사람, 웹·PWA·모바일 운영 형태를 고른다.
5분 카드: 사용자·문제·기능1개·첫 화면·저장·성공 기준을 적는다.
12분 생성: S08의 무료 시작 프롬프트로 단일 HTML 앱을 만들고 로컬에서 실행한다.
8분 배포: 준비된 공개 저장소에 index.html을 올리고 Settings→Pages→Deploy from a branch→main/(root)→Save, 배포 URL을 확인한다.
6분 짝 검증: 휴대폰 또는 다른 브라우저로 링크를 열고 핵심 동작과 빈 입력·저장 범위를 확인한다.
4분 정리: 30초 앱 소개와 다음에 구현할 기능 한 개를 남긴다.
무료 AI 기획 요청
학생인 내가 진짜 만들고 싶은 앱을 구체화하도록 도와줘. 먼저 “당신이 진짜 만들고 싶은 앱은 무엇인가요?”를 묻고 답을 기다려줘. 이어 “어디에서 운영하고 싶은가요? 웹 vs 모바일?”을 묻고, 웹·휴대폰 설치형 웹앱(PWA)·모바일 앱 중 선택 이유를 확인해줘. 사용자·해결할 문제·핵심 동작1개·첫 화면·데이터 저장 위치·무료 개발 도구와 배포 대상·성공 기준을 한 장 기획 카드로 정리해줘. 첫 수업에서는 단일 HTML과 가상 데이터로 20분 안에 만들 범위로 줄여줘. 앱 안에서 AI를 호출해야 하는 기능은 다음 단계 후보로 남기고 별도 API 비용을 설명해줘.
AI 검증·오류 해결 요청
이 학생 앱 기획 카드를 검토해줘. 사용자가 겪는 문제가 구체적인지, 첫 기능이 하나인지, 웹·PWA·모바일 선택에 이유가 있는지, 실제 무료 경로와 사용 조건을 구분했는지 확인해줘. 첫 버전은 유료 API나 서버 없이 실행 가능해야 한다. localStorage가 이 브라우저·기기에만 저장됨을 설명하고, 동작을 확인할 입력과 예상 출력 한 쌍을 만들어줘. 범위가 크면 오늘 만들 기능 하나로 줄여줘.
완료 확인
나의 앱과 사용자·해결할 문제를 한 문장으로 말한다.
웹·PWA·모바일 선택 이유와 무료 배포 조건을 설명한다.
공개 URL에서 핵심 동작1개를 짝이 확인한다.
자주 막히는 지점
AI 한도나 계정 제한: 강사의 단일 HTML 예제로 계속 실습한다.
Pages 미표시: 공개 저장소·index.html 위치·main/(root)·배포 진행 상태를 확인한다.
휴대폰 데이터 없음: localStorage의 기기·브라우저별 저장 범위를 확인한다.
확인 질문
당신이 진짜 만들고 싶은 앱은 무엇인가요? 어디에서 운영하고 싶은가요? 웹 vs 모바일?
예: 나는 과제를 놓치는 학생을 위해 마감일을 기록하는 웹앱을 만든다. 링크로 바로 공유하려고 웹을 고르고, 첫 기능은 과제 추가와 완료 표시로 줄인다.
선택형 PDF 서비스가 Vercel 기본 배포에 자동 포함된다고 설명하지 않는다. 실습은 원본 보존과 변경 예정 목록 확인을 중심으로 진행한다.
강사용 설명
시연에서는 적용 전 파일 수를 함께 센다. 미리보기와 적용 옵션이 있다는 사실을 말로 끝내지 말고 같은 도구를 두 방식으로 실행한다. 삭제와 이름 덮어쓰기를 이 실습에 추가하지 않는다. 학생이 다른 자료를 넣고 싶어 하면 수업용 복사본으로 먼저 확인하도록 안내한다.
실습 절차
실습 ZIP의 sample-files 폴더를 작업 폴더로 복사하고 원본을 보존한다.
확장자를 기준으로 documents와 images에 분류하는 규칙을 적는다.
Codex에게 Node.js 도구를 만들되 기본 동작을 미리보기로 지정한다.
예정 목록을 보고 대상 폴더 밖의 파일이 없는지, 중복 이름이 생기는지 확인한다.
복사본에만 --apply 옵션으로 실행하고 결과 목록을 저장한다.
같은 명령을 다시 실행해 추가 변경이 없는지 확인한다.
Codex 구현 요청
현재 작업 폴더에서 tools/organize-files.mjs를 만들어줘. Node.js 내장 모듈을 사용하고 입력은 labs/sample-files의 복사본으로 제한해줘.
.txt와 .csv는 documents, .svg는 images로 분류해줘. 기본 실행은 원본을 바꾸지 않는 미리보기이고 --apply일 때만 이동해줘. 기존 파일을 덮어쓰지 말고 중복은 건너뛰어 이유를 출력해줘. 심볼릭 링크와 작업 폴더 밖 경로는 거부해줘. 실행 방법을 설명하고 미리보기를 먼저 실행해줘.
AI 검증·오류 해결 요청
도구의 변경 범위를 확인해줘. 아직 적용하지 말고 예상 이동 건수와 제외 이유를 보여줘. 적용 후 같은 입력으로 재실행해 추가 이동이 없는지 확인할 명령도 제안해줘. 실제 사용자 폴더를 탐색하지 마.
생성도구의 시각적 완성도보다 원본 대조를 평가한다. 실제 개인정보 대신 가상 수치와 문장으로 결과물을 만든다.
강사용 설명
정리 규칙은 데이터에 맞춰 먼저 정한다. 같은 이메일이라고 무조건 중복으로 지우는 예시는 사용하지 않는다. kysong의 설문 내보내기와 임시저장 복구를 연결해 원본 보존과 결과 대조가 자동화의 일부임을 설명한다. 함수가 생성됐다는 말보다 정해진 12행이 어떻게 8행이 되는지를 학생이 설명하도록 한다.
실습 절차
labs/registrations.csv의 열 이름과 12개 원본 행을 확인한다.
신청 id가 같은 행은 중복이며 처음의 유효 행을 유지하는 규칙을 정한다.
금액이 음수이거나 숫자가 아닌 행은 별도 오류 목록에 남기도록 요청한다.
program별 인원·금액 합계와 상태별 집계를 만드는 도구를 실행한다.
제공된 expected-summary.json과 행 수·금액을 대조한다.
정리된 CSV를 다시 입력해 결과가 안정적인지 확인하고 처리 규칙을 설명한다.
Codex 구현 요청
labs/registrations.csv를 읽는 tools/summarize-csv.mjs를 만들어줘. 따옴표가 포함된 CSV도 처리할 수 있게 해줘.
id 중복은 처음의 유효 행을 유지하고, amount가 음수이거나 유한한 숫자가 아니면 errors.csv로 분리해줘. 원본은 변경하지 마.
output/clean.csv와 summary.json, 사람이 읽을 수 있는 요약 보고서를 만들어줘. program별 인원과 금액, status별 인원을 집계해줘. 실제 결과를 expected-summary.json과 비교해 차이를 설명해줘.
AI 검증·오류 해결 요청
원본 행 수, 유효 고유 행 수, 중복 수, 오류 수가 맞는지 검산해줘. 합계만 맞추려고 자료를 수정하지 말고, 제외한 행의 id와 이유를 보여줘. CSV 셀의 쉼표·따옴표·빈값도 확인해줘.
완료 확인
원본 12행에서 유효 고유 8행이 나온다.
중복 2행과 오류 2행을 구분한다.
유효 금액 합계 160000원과 프로그램별 집계가 맞다.
자주 막히는 지점
금액 합계가 문자열처럼 붙음: 숫자 변환과 유효성 검사를 확인한다.
CSV 열이 밀림: 쉼표로 단순 분리하지 않고 CSV 인용 규칙을 처리한다.
확인 질문
중복을 제거할 때 이메일보다 신청 id를 먼저 쓰는 이유는?
한 사람이 여러 프로그램에 신청할 수 있기 때문이다. 업무에서 정의한 고유 식별자를 사용해야 한다.
한 종류의 사용자 흐름을 구현 가능한 계획으로 나눈다. / 산출물: 요구사항 한 장과 데이터·화면 초안 / 도입8 · 실습30 · 짝검증8 · 정리4분
039
핵심 개념
프런트엔드 HTML CSS JavaScript와 TypeScript / 백엔드 Node.js와 TypeScript API / 관계형 DB PostgreSQL과 SQL
040
구축 지도를 그리기: 언어·서버·DB
HTML/CSS는 구조·표현, JavaScript/TypeScript는 동작을 담당한다. / React/Next.js는 UI·웹 프레임워크, Node.js는 서버 실행 환경이다. / PostgreSQL은 DB, SQL은 질의 언어, Supabase는 백엔드 플랫폼이다. / Codex→GitHub→Supabase→Vercel→Resend/SNS의 역할과 설정 위치를 적는다.
041
uc-anchor · 이번 실습에 적용할 판단
프런트엔드: TypeScript·React·Next.js로 화면과 상호작용을 구성한다. / 백엔드: Node.js·TypeScript API가 서버의 처리와 검증을 담당한다. / DB: Supabase PostgreSQL과 SQL·RLS가 저장·조회·행 접근을 맡는다.
042
실습 절차
아래 단계별 실습을 진행한다.
043
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
044
완료 확인과 오류 대응
필수 기능이 세 묶음 이내로 정리된다. / 각 화면이 읽고 쓰는 데이터와 권한이 연결된다. / 완료 기준을 실제 행동으로 표현한다.
구축 지도를 그리기: 언어·서버·DB
HTML/CSS는 구조·표현, JavaScript/TypeScript는 동작을 담당한다.
React/Next.js는 UI·웹 프레임워크, Node.js는 서버 실행 환경이다.
PostgreSQL은 DB, SQL은 질의 언어, Supabase는 백엔드 플랫폼이다.
Codex→GitHub→Supabase→Vercel→Resend/SNS의 역할과 설정 위치를 적는다.
uc-anchor 사례 · 이번 실습에 적용할 판단
프런트엔드: TypeScript·React·Next.js로 화면과 상호작용을 구성한다.
백엔드: Node.js·TypeScript API가 서버의 처리와 검증을 담당한다.
DB: Supabase PostgreSQL과 SQL·RLS가 저장·조회·행 접근을 맡는다.
프레임워크 전환 과정 대신 언어·실행 환경·DB의 종류와 역할을 예로 든다. MySQL/SQLite/MongoDB는 비교 예시이며 세 프로젝트의 실제 주 DB와 혼동하지 않는다.
강사용 설명
u-livet이 데이터 구조와 교육 운영 흐름을 먼저 만든 기록을 보여준다. 단순히 예쁘게 만들어 달라는 요청에서 누가 무엇을 저장하는지 설명하는 요청으로 바꾸는 시간이다. 수강신청과 결제, 출석, 증명 발급 전체를 5일 안에 모두 구현하는 목표로 넓히지 않는다. 1교시의 student-app-card.csv를 다시 열고 학생이 고른 앱의 사용자·문제·운영 방식·기능1개를 입력·처리·출력·권한·예외로 확장한다. 아직 구현하지 않은 서버·로그인 기능은 다음 날 과제로 표시한다.
실습 절차
공통 프로젝트인 미니 교육 운영 포털의 방문자·회원·운영자 역할을 정리한다.
필수 기능을 프로그램 조회, 회원 신청, 내 신청 조회로 제한한다.
programs와 applications의 필요한 열을 적고 user_id의 의미를 설명한다.
목록·내 신청·로그인·보안 화면을 종이에 그리거나 문서로 작성한다.
Codex에게 파일을 만들기 전에 계획과 구현 순서, 검증 방법을 요청한다.
짝과 계획을 검토하고 민감한 작업은 MFA 단원에서 확장한다고 표시한다.
Codex 구현 요청
미니 교육 운영 포털의 요구사항을 정리해줘. 대상은 방문자와 로그인 회원이다. 필수 기능은 프로그램 목록, 신청, 본인 신청 조회·메모 수정·취소다.
Next.js App Router와 TypeScript, Supabase Auth·DB·RLS, Vercel을 사용한다. Git으로 변경을 기록하고 Docker는 로컬 Supabase 실험에 사용한다.
화면, 두 테이블의 데이터 모델, 사용자별 권한, 구현 순서와 완료 기준을 먼저 작성해줘. 결제·정원 동시성·실제 문자 발송은 이번 범위에 넣지 마.
AI 검증·오류 해결 요청
계획의 각 기능에 사용자가 실행할 검증 한 가지를 붙여줘. 개인정보 없이 만들 수 있는 샘플 3개를 제안하고, 아직 결정하지 않은 요구사항을 구분해줘. 설계와 무관한 기능을 추가하지 마.
완료 확인
필수 기능이 세 묶음 이내로 정리된다.
각 화면이 읽고 쓰는 데이터와 권한이 연결된다.
완료 기준을 실제 행동으로 표현한다.
자주 막히는 지점
기능이 계속 늘어남: 필수·선택·후속 작업으로 나눈다.
권한이 화면 설명에만 있음: 데이터 읽기와 쓰기 규칙을 함께 적는다.
확인 질문
화면이 세 개인 것과 기능이 세 개인 것은 같은 의미인가?
같지 않다. 기능 하나가 여러 화면과 데이터 변경을 포함할 수 있으므로 사용자 행동과 완료 조건으로 범위를 잡는다.
Git 명령 전체를 암기시키지 않는다. 이번 변경의 diff·검증·커밋을 한 묶음으로 설명하고 긴 명령 예제는 원문 파일에서 선택한다.
강사용 설명
1교시 도입 8분에 전날 앱을 다시 연다. Codex 접근이 제한되면 ZIP의 starter→checkpoint-edit→solution을 비교하며 Git 절차를 계속한다. 무료 AI로 시작한 앱을 Codex의 실제 파일 편집으로 이어가는 과정이다. GitHub는 원격 저장소, clone은 내 컴퓨터 복사본, Codex는 이 폴더의 파일을 읽고 수정하는 도구라고 설명한다. 복사본 폴더를 여러 개 만들지 않는다. 로컬 서버가 필요하면 Codex가 설치된 실행 도구를 확인해 띄우게 한다. Python 등 새 도구 설치를 수업의 필수로 만들지 않는다. 브랜치를 만들었다고 폴더나 배포 사이트가 새로 생기지 않는다. Git 이력의 작업 지점을 나눈 것이다. Worktree는 별도 폴더가 필요한 때의 선택 시연으로만 다룬다. 기존 작업은 보존한다. 1교시 50분은 도입8·clone/브랜치10·읽기/수정20·짝 검증8·커밋/정리4로 진행한다. AI가 전체 앱을 다시 만들면 저장 키와 id 보존 조건을 재확인한다. 코드 저장과 commit의 차이를 보여준다. 상세 실습은 assignment-day2-lab.html과 J01~J06을 사용한다. Next.js·React·TypeScript는 언어/프레임워크 비교 확장으로 설명하며 이 앱의 필수 실습은 단일 HTML을 유지한다.
실습 절차
전날 앱과 오늘의 성공 기준 확인: 본인의 1일차 Pages 앱에서 가상 과제 2개를 만들고 하나를 완료한다. 현재 공개 URL과 수정 전 화면을 기록한다. 오늘은 수정·검색을 추가하고 커밋 2개와 PR로 변경 이유를 남긴다.
내 저장소 clone → 같은 폴더를 Codex에서 열기: YOUR_ID를 본인 GitHub 아이디로 바꾸고 아래 명령을 한 줄씩 실행한다. 이미 clone했다면 해당 폴더에서 status·remote만 확인한다. Codex 프로젝트에 그 폴더를 연결하고 index.html과 .git이 있는 저장소를 기본 작업 폴더로 선택한다. Codex에 현재 실행 환경을 확인시켜 로컬 미리보기를 연다.
깨끗한 main → 기능 브랜치 만들기: status에 미커밋 변경이 없고 기본 브랜치가 main인 것을 확인한 뒤 아래 명령을 실행한다. 기본 브랜치가 다르면 실제 이름으로 바꾼다. feature/edit-tasks가 이미 있으면 기존 작업을 확인하고 새 이름을 정한다.
Codex로 과제 수정 구현 → 첫 커밋: 아래 수정 요청을 Codex에 입력한다. 수정 버튼→기존 값 불러오기→변경 저장→취소를 직접 실행한다. 완료한 과제를 수정해도 완료 상태와 개수가 같은지 확인한다. 통과한 뒤 diff를 읽고 index.html만 stage·commit한다.
Codex 구현 요청
현재 폴더의 과제 체크 앱을 읽고 현재 경로, Git 브랜치와 status, index.html의 역할, localStorage 키와 데이터 구조를 먼저 요약해줘. 이어 각 과제에 수정 버튼을 추가하고 기존 입력 폼에서 제목·과목·마감일을 수정·저장·취소할 수 있게 최소 변경해줘. 같은 id 항목만 변경하고 done·createdAt·과제 개수와 기존 저장 데이터는 유지해줘. 취소는 저장하지 않고 수정 버튼으로 초점을 돌려줘. 빈값·공백·길이·날짜 검증, textContent, 입력 label과 네이티브 버튼을 유지해줘. 수정 중인 항목을 삭제할 때 폼도 정리해줘. 변경 파일과 diff, 실제 검증 결과를 설명해줘. 서버·DB·외부 패키지·API는 추가하지 마. 아직 commit·push·PR·merge는 실행하지 마.
AI 검증·오류 해결 요청
현재 과제 체크 앱의 diff를 검토하고 실제 실행한 검사와 미확인을 구분해줘. 전날 데이터가 유지되는지, 완료한 과제를 수정해도 id·done·createdAt·개수가 같은지, 취소 전후 저장 데이터가 같은지, 공백 수정 거절, 제목/과목 검색·대소문자·빈 결과·완료 필터 조합·검색 지우기, 수정 중 삭제/되돌리기, 추가·완료·새로고침·날짜 집계·저장 실패 안내를 확인해줘. 390px 화면과 키보드 초점도 확인해줘. 문제는 재현 절차와 최소 수정으로 보고해줘. 마지막으로 기능별 커밋 메시지와 PR 제목·본문 초안을 제안해줘. 현재 경로·브랜치·remote와 대상 파일을 다시 확인해주고 사용자 데이터·비밀값을 출력하지 마. 원격 push·PR 생성·merge·배포는 내가 해당 단계에서 실행할게.
완료 확인
수정 후 id·완료·생성시각·개수가 유지된다.
취소와 공백 입력을 확인한다.
첫 기능 커밋을 만들고 diff를 설명한다.
자주 막히는 지점
Author identity unknown: 본인 이름과 GitHub의 noreply 이메일을 저장소 로컬 설정으로 등록한 뒤 재시도
not a git repository: cd와 Codex 기본 폴더를 맞춘다
미커밋 변경/ff-only 실패: 기존 변경을 보존하고 강사와 통합한다
push 인증 실패: GitHub 로그인 안내를 따르고 토큰을 코드에 넣지 않는다
PR 변경이 없음: 본인 저장소의 기능 브랜치를 compare로 선택
기존 과제가 사라짐: 데이터를 지우지 말고 원인과 백업부터 확인
수정했는데 새 항목이 생김: 동일 항목의 세 필드만 수정
검색이 안 됨: 검색과 완료 조건을 함께 검사
병합 후 이전 화면: 완료 후 새로고침하고 직접 기능 검사
확인 질문
Codex가 파일을 만들기 전에 기존 구조를 읽게 하는 이유는?
현재 버전과 경로, 프로젝트 규칙을 알아야 중복 파일과 잘못된 API 사용을 줄일 수 있다.
완료한 과제 수정: id·완료·생성시각·개수 유지 / 수정 취소: 저장 값 그대로 · 공백 입력 거절 / 검색 + 완료 필터 · 없는 결과 · 지우기 / 추가·삭제/되돌리기·날짜 집계·새로고침·390px
058
과제 검색과 기존 기능 검증
제목·과목 검색을 추가하고 기존 기능의 회귀를 검사한다. / 산출물: 검색 기능의 두 번째 커밋과 실제 검사표 / 검색요구8 · 구현15 · 회귀15 · 짝검증8 · 정리4분
059
핵심 개념
검색과 완료 필터의 교집합 / 동일 항목 수정과 취소 / 새 기능 뒤 기존 동작을 다시 검사하는 회귀 검증
060
검색 개선과 회귀 검사·복구의 기준
검색은 표시 조건만 바꾸고 실제 저장 데이터를 바꾸지 않는다. / 회귀 검사로 추가·수정·완료·삭제·새로고침을 다시 확인한다. / 충돌은 파일별 의도를 읽고 해결한 뒤 실행과 diff를 검사한다. / 복구 대상이 작업 파일·로컬 commit·공유 이력 중 무엇인지 먼저 정한다.
061
uc-anchor · 이번 실습에 적용할 판단
기록: 화면과 업무 규칙이 결합되며 기존 동작의 일관성이 중요해졌다. / 비교: 검색 구현도 목록 표시와 저장 결과를 따로 검증한다. / 적용: 이전 기능을 확인한 체크리스트와 작은 기능별 commit을 남긴다.
062
실습 절차
아래 단계별 실습을 진행한다.
063
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
064
완료 확인과 오류 대응
제목·과목 검색과 완료 필터가 함께 작동한다. / 검색·취소가 저장 데이터를 바꾸지 않는다. / 회귀 검사표와 두 번째 커밋을 남긴다.
과제 체크 앱 이어 만들기
J01~J06과 B01~B08을 사용한다. 학생 절차·명령·예제 요청·강사 설명과 체크포인트 파일을 제공한다.
충돌/복구 명령은 별도 실습 저장소에서 선택 심화로 진행한다. 학생 과제의 미커밋 변경을 지우는 명령을 기본 진행 순서에 넣지 않는다.
강사용 설명
2교시 50분은 검색 요구8·구현15·회귀 검증15·짝 검증8·커밋/정리4다. 검색 기능이 쉬워 보여도 저장 데이터와 필터 조합에 영향을 줄 수 있음을 보여준다. 첫 커밋 덕분에 두 변경의 이유를 따로 설명할 수 있다. 개발자 도구의 localStorage를 지우거나 운영 데이터를 덮어쓰지 않는다. localhost와 Pages는 서로 다른 origin이라 목록이 다른 것은 정상이다. 강사 공개 앱의 루트와 /day2/는 같은 origin·키로 목록을 이어 읽도록 설계했다. 상세 실습은 assignment-day2-lab.html과 J01~J06을 사용한다. Next.js·React·TypeScript는 언어/프레임워크 비교 확장으로 설명하며 이 앱의 필수 실습은 단일 HTML을 유지한다.
실습 절차
제목·과목 검색 구현 → 두 번째 커밋: 검색 요청을 Codex에 입력한다. 제목 일부와 과목을 각각 검색하고 영어 대소문자와 공백, 결과 없음, 완료 필터를 함께 시험한다. 검색 지우기 뒤 정상 목록이 돌아오는지 확인한 뒤 두 번째 commit을 만든다.
회귀 검사와 변경 파일 검토: 검증 요청과 아래 체크표로 확인한다. 새로고침 뒤 수정 내용과 완료 상태를 확인한다. 같은 제목을 새로 추가한 것이 아닌지, 취소가 저장하지 않는지 검사한다. 수정 중 삭제와 되돌리기, 빈 검색, 휴대폰 화면도 확인한다.
Codex 구현 요청
현재 수정 기능과 기존 추가·완료·삭제/되돌리기·저장·날짜 집계를 보존하고 제목·과목 부분 검색을 추가해줘. 앞뒤 공백을 제거하고 대소문자를 무시하며 전체/미완료/완료 필터와 함께 적용해줘. 검색어가 비면 기존 필터 목록, 결과가 없으면 안내, 검색 지우기 버튼과 현재 표시 개수를 제공해줘. 검색은 과제를 삭제하거나 전체 집계·저장 데이터를 바꾸지 않는 화면 조건이야. label이 연결된 네이티브 search 입력을 쓰고 검색 조건은 저장하지 마. 추가·수정 저장 뒤 검색과 완료 필터를 초기화해 결과를 보여줘. 모바일 가로 넘침과 키보드 조작을 확인해줘. 변경 파일·diff·검증 결과를 보여주되 commit·push·PR·merge는 아직 하지 마.
AI 검증·오류 해결 요청
현재 과제 체크 앱의 diff를 검토하고 실제 실행한 검사와 미확인을 구분해줘. 전날 데이터가 유지되는지, 완료한 과제를 수정해도 id·done·createdAt·개수가 같은지, 취소 전후 저장 데이터가 같은지, 공백 수정 거절, 제목/과목 검색·대소문자·빈 결과·완료 필터 조합·검색 지우기, 수정 중 삭제/되돌리기, 추가·완료·새로고침·날짜 집계·저장 실패 안내를 확인해줘. 390px 화면과 키보드 초점도 확인해줘. 문제는 재현 절차와 최소 수정으로 보고해줘. 마지막으로 기능별 커밋 메시지와 PR 제목·본문 초안을 제안해줘. 현재 경로·브랜치·remote와 대상 파일을 다시 확인해주고 사용자 데이터·비밀값을 출력하지 마. 원격 push·PR 생성·merge·배포는 내가 해당 단계에서 실행할게.
완료 확인
제목·과목 검색과 완료 필터가 함께 작동한다.
검색·취소가 저장 데이터를 바꾸지 않는다.
회귀 검사표와 두 번째 커밋을 남긴다.
자주 막히는 지점
Author identity unknown: 본인 이름과 GitHub의 noreply 이메일을 저장소 로컬 설정으로 등록한 뒤 재시도
not a git repository: cd와 Codex 기본 폴더를 맞춘다
미커밋 변경/ff-only 실패: 기존 변경을 보존하고 강사와 통합한다
push 인증 실패: GitHub 로그인 안내를 따르고 토큰을 코드에 넣지 않는다
PR 변경이 없음: 본인 저장소의 기능 브랜치를 compare로 선택
기존 과제가 사라짐: 데이터를 지우지 말고 원인과 백업부터 확인
수정했는데 새 항목이 생김: 동일 항목의 세 필드만 수정
검색이 안 됨: 검색과 완료 조건을 함께 검사
병합 후 이전 화면: 완료 후 새로고침하고 직접 기능 검사
확인 질문
검색했을 때 목록이 줄어드는 것과 실제 과제 삭제는 어떻게 구분하는가?
검색은 표시 조건만 바꾼다. 검색 지우기와 새로고침 뒤 원래 과제·개수·완료 상태가 유지되는지 확인한다.
stage·commit·push의 위치와 역할 / PR의 base/head·diff·검토·병합 / main·Pages 배포 커밋·공개 동작의 서로 다른 근거
069
origin/main·PR·병합을 눈으로 확인
origin은 원격 별칭, main은 로컬 브랜치, origin/main은 원격 추적 참조다. / fetch는 참조를 갱신하고 pull은 현재 브랜치에 통합한다. / PR 검토·merge·배포 완료는 서로 다른 단계다. / 아래 Git 상태 시뮬레이터로 add→commit→push→원격 변경→fetch를 비교한다.
070
관찰 실습: Git 상태 변화
이 화면은 가상 이력 시뮬레이션이며 실제 Git 저장소를 변경하지 않는다. / 원격 추가 commit 이후 fetch를 해도 로컬 파일은 그대로 남는다.
071
kysong · 이번 실습에 적용할 판단
기록: GitHub 저장소와 Vercel 배포를 연결해 기능을 확장했다. / 비교: 로컬 commit·원격 push·서비스 배포의 결과를 각각 확인한다. / 적용: 학생 과제 앱은 PR 병합 뒤 Pages의 실제 공개 기능을 재검사한다.
072
실습 절차
아래 단계별 실습을 진행한다.
073
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
074
완료 확인과 오류 대응
내 저장소에 커밋2개를 포함한 PR을 만든다. / 병합과 Pages 배포 상태를 구분한다. / 공개 URL에서 실제 기능을 검사한다.
과제 체크 앱 이어 만들기
J01~J06과 B01~B08을 사용한다. 학생 절차·명령·예제 요청·강사 설명과 체크포인트 파일을 제공한다.
Thomas dot의 브랜치·origin/main 설명을 이 교시에서 사용한다. 병합 방식/30분 추가 연습/질문 카드는 선택 참고이며 기존 PR 실습시간을 연장하지 않는다.
강사용 설명
3교시 도입8분에 G05 상태 변화를 보여준다. push/PR12·짝 검토10·병합/Pages8·배포 검증8·정리4분이다. PR 제목보다 변경 이유·검증 근거를 읽게 한다. private 토큰·실제 학생 과제는 본문에 넣지 않는다. PR 병합은 소스 반영, Pages built는 배포 결과, 브라우저 검사는 사용자가 보는 동작의 근거다. 서로 구분한다. 원격 main을 병합한 뒤 로컬 main도 갱신한다. 기능 브랜치 삭제·충돌 해결은 필수가 아니다. 4교시는 기존 Docker·로컬 Supabase 준비로 이어가며 하루 시간은 늘리지 않는다. 상세 실습은 assignment-day2-lab.html과 J01~J06을 사용한다. Next.js·React·TypeScript는 언어/프레임워크 비교 확장으로 설명하며 이 앱의 필수 실습은 단일 HTML을 유지한다.
실습 절차
push → 내 저장소의 Pull Request 만들기: 대상 remote와 브랜치를 확인한 뒤 push한다. 본인 GitHub 저장소의 Pull requests→New pull request에서 base:main, compare:feature/edit-tasks를 선택한다. 제목과 아래 PR 본문 예시를 채우고 Files changed에서 변경을 읽은 뒤 PR을 만든다.
짝 검토 → 병합 → Pages 재배포 확인: 짝이 PR의 변경과 검사 결과를 읽고 개선점을 한 가지 적는다. 본인 PR을 Merge pull request로 병합한다. Settings→Pages/Actions에서 배포 완료와 사용한 커밋을 확인하고 본인 github.io 주소에서 수정·검색·새로고침을 다시 검사한다. 로컬 main을 아래 명령으로 갱신한다.
Codex 구현 요청
현재 과제 체크 앱의 diff를 검토하고 실제 실행한 검사와 미확인을 구분해줘. 전날 데이터가 유지되는지, 완료한 과제를 수정해도 id·done·createdAt·개수가 같은지, 취소 전후 저장 데이터가 같은지, 공백 수정 거절, 제목/과목 검색·대소문자·빈 결과·완료 필터 조합·검색 지우기, 수정 중 삭제/되돌리기, 추가·완료·새로고침·날짜 집계·저장 실패 안내를 확인해줘. 390px 화면과 키보드 초점도 확인해줘. 문제는 재현 절차와 최소 수정으로 보고해줘. 마지막으로 기능별 커밋 메시지와 PR 제목·본문 초안을 제안해줘. 현재 경로·브랜치·remote와 대상 파일을 다시 확인해주고 사용자 데이터·비밀값을 출력하지 마. 원격 push·PR 생성·merge·배포는 내가 해당 단계에서 실행할게.
AI 검증·오류 해결 요청
현재 과제 체크 앱의 diff를 검토하고 실제 실행한 검사와 미확인을 구분해줘. 전날 데이터가 유지되는지, 완료한 과제를 수정해도 id·done·createdAt·개수가 같은지, 취소 전후 저장 데이터가 같은지, 공백 수정 거절, 제목/과목 검색·대소문자·빈 결과·완료 필터 조합·검색 지우기, 수정 중 삭제/되돌리기, 추가·완료·새로고침·날짜 집계·저장 실패 안내를 확인해줘. 390px 화면과 키보드 초점도 확인해줘. 문제는 재현 절차와 최소 수정으로 보고해줘. 마지막으로 기능별 커밋 메시지와 PR 제목·본문 초안을 제안해줘. 현재 경로·브랜치·remote와 대상 파일을 다시 확인해주고 사용자 데이터·비밀값을 출력하지 마. 원격 push·PR 생성·merge·배포는 내가 해당 단계에서 실행할게.
완료 확인
내 저장소에 커밋2개를 포함한 PR을 만든다.
병합과 Pages 배포 상태를 구분한다.
공개 URL에서 실제 기능을 검사한다.
자주 막히는 지점
Author identity unknown: 본인 이름과 GitHub의 noreply 이메일을 저장소 로컬 설정으로 등록한 뒤 재시도
분리된 로컬 DB에서 인증과 데이터 실험을 시작한다. / 산출물: 로컬 Supabase 실행과 연결 확인 / 도입8 · 실습30 · 짝검증8 · 정리4분
076
핵심 개념
컨테이너 실행 환경 / 로컬과 클라우드의 분리 / 설정과 로그의 확인
077
분리한 작업 폴더와 Docker 개발 환경
Worktree는 같은 저장소의 다른 작업 폴더이며 브랜치와 다르다. / 폴더가 달라도 포트·환경 파일·DB 연결은 충돌할 수 있다. / Docker 로컬 Supabase는 운영 데이터를 건드리지 않는 검증 환경이다. / 별도 작업 폴더·포트·DB 대상을 기록하고 실습 뒤 본인 환경만 정리한다.
078
u-livet · 이번 실습에 적용할 판단
기록: Docker의 로컬 Supabase에서 인증·업무 기능을 검증했다. / 판단: 운영은 관리형 Supabase와 Vercel로 역할을 나눴다. / 적용: 과제 앱도 로컬 가상 계정으로 RLS를 실험한다.
079
실습 절차
아래 단계별 실습을 진행한다.
080
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
081
완료 확인과 오류 대응
수업용 로컬 프로젝트를 식별한다. / 앱과 로컬 DB의 연결을 확인한다. / 대상 서비스의 시작·로그·종료 방법을 설명한다.
Docker가 브랜치마다 DB를 자동 분리해준다고 설명하지 않는다. Worktree 생성·멀티기기·정리는 선택 심화이고 본 실습은 로컬 스택의 실행과 연결 확인이다.
강사용 설명
u-livet README의 실제 로컬 실행 흐름을 보여준다. Docker는 로컬 Supabase를 재현하는 환경이며, 프로젝트를 Vercel에 공개하는 단계와 역할이 다르다. 첫 이미지 다운로드는 수업 전에 끝내 둔다. 실습에서 reset이나 일괄 정리 명령을 기본 복구법으로 가르치지 않는다.
실습 절차
Docker Desktop을 실행하고 docker info로 엔진 연결을 확인한다.
새 수업용 프로젝트에서 Supabase CLI의 --help와 버전을 확인한다.
프로젝트를 초기화하고 supabase start로 로컬 서비스를 실행한다.
CLI가 안내한 로컬 URL과 Studio·메일 확인 도구 주소를 기록한다. 키는 공개 화면에 출력하지 않는다.
수업용 환경 파일에 로컬 접속 설정을 입력하고 앱과 연결한다.
docker ps로 컨테이너를 확인하고 한 서비스의 로그를 읽는다. 종료·재시작 후 설정과 데이터를 구분한다.
Codex 구현 요청
Docker와 Supabase CLI의 현재 상태를 읽기 전용으로 확인해줘. 기존 다른 프로젝트의 컨테이너와 포트를 보존해줘.
이 새 수업용 폴더에만 로컬 Supabase를 초기화하고 실행할 명령을 --help 기준으로 설명해줘. 다른 프로젝트와 포트가 겹치면 이 프로젝트 설정만 바꿔줘.
앱에 로컬 URL과 공개 가능한 키를 연결하는 방법을 알려주되 비밀값은 출력하지 마. 원격 DB 연결·초기화·컨테이너 전체 정리는 하지 마.
AI 검증·오류 해결 요청
실행 실패의 원인을 Docker 엔진, 포트 충돌, 이미지 다운로드, 프로젝트 설정 순서로 확인해줘. 대상 프로젝트의 컨테이너와 로그만 읽고 원인을 설명해줘. docker system prune이나 다른 프로젝트 삭제로 해결하지 마.
Codex 로그인·이용 범위·사용량, Git 설치·GitHub 인증과 본인 저장소를 확인한다. 계정 제한 때는 ZIP 체크포인트로 이어간다. 학생은 본인 저장소의 최상위 index.html을 수정한다. 강사 공개 예제만 1일차 화면 보존을 위해 /day2/에 둔다. starter는 전날 기준, checkpoint-edit는 수정 기능, solution은 수정+검색이다. ZIP은 Git 이력을 포함하지 않으므로 clone한 저장소 안에서 해당 HTML을 참고한다.
Codex는 현재 소스 파일을 직접 읽고 수정한다. 파일 저장, commit, push, PR, merge, Pages 배포는 서로 다른 단계다. Git에는 소스 이력이 기록되고 브라우저에 입력한 과제 목록이 자동 저장되는 것은 아니다.
요청 1→검사→커밋 1, 요청 2→검사→커밋 2 순서다. 계정 이용 조건은 수업 전에 확인하며 이 앱 안에서는 AI API를 호출하지 않는다.
1. 기존 앱 읽기와 수정
현재 폴더의 과제 체크 앱을 읽고 현재 경로, Git 브랜치와 status, index.html의 역할, localStorage 키와 데이터 구조를 먼저 요약해줘. 이어 각 과제에 수정 버튼을 추가하고 기존 입력 폼에서 제목·과목·마감일을 수정·저장·취소할 수 있게 최소 변경해줘. 같은 id 항목만 변경하고 done·createdAt·과제 개수와 기존 저장 데이터는 유지해줘. 취소는 저장하지 않고 수정 버튼으로 초점을 돌려줘. 빈값·공백·길이·날짜 검증, textContent, 입력 label과 네이티브 버튼을 유지해줘. 수정 중인 항목을 삭제할 때 폼도 정리해줘. 변경 파일과 diff, 실제 검증 결과를 설명해줘. 서버·DB·외부 패키지·API는 추가하지 마. 아직 commit·push·PR·merge는 실행하지 마.
2. 제목·과목 검색
현재 수정 기능과 기존 추가·완료·삭제/되돌리기·저장·날짜 집계를 보존하고 제목·과목 부분 검색을 추가해줘. 앞뒤 공백을 제거하고 대소문자를 무시하며 전체/미완료/완료 필터와 함께 적용해줘. 검색어가 비면 기존 필터 목록, 결과가 없으면 안내, 검색 지우기 버튼과 현재 표시 개수를 제공해줘. 검색은 과제를 삭제하거나 전체 집계·저장 데이터를 바꾸지 않는 화면 조건이야. label이 연결된 네이티브 search 입력을 쓰고 검색 조건은 저장하지 마. 추가·수정 저장 뒤 검색과 완료 필터를 초기화해 결과를 보여줘. 모바일 가로 넘침과 키보드 조작을 확인해줘. 변경 파일·diff·검증 결과를 보여주되 commit·push·PR·merge는 아직 하지 마.
3. 회귀 검증과 PR 초안
현재 과제 체크 앱의 diff를 검토하고 실제 실행한 검사와 미확인을 구분해줘. 전날 데이터가 유지되는지, 완료한 과제를 수정해도 id·done·createdAt·개수가 같은지, 취소 전후 저장 데이터가 같은지, 공백 수정 거절, 제목/과목 검색·대소문자·빈 결과·완료 필터 조합·검색 지우기, 수정 중 삭제/되돌리기, 추가·완료·새로고침·날짜 집계·저장 실패 안내를 확인해줘. 390px 화면과 키보드 초점도 확인해줘. 문제는 재현 절차와 최소 수정으로 보고해줘. 마지막으로 기능별 커밋 메시지와 PR 제목·본문 초안을 제안해줘. 현재 경로·브랜치·remote와 대상 파일을 다시 확인해주고 사용자 데이터·비밀값을 출력하지 마. 원격 push·PR 생성·merge·배포는 내가 해당 단계에서 실행할게.
YOUR_ID를 본인 GitHub 아이디로 바꾸고 아래 명령을 한 줄씩 실행한다. 이미 clone했다면 해당 폴더에서 status·remote만 확인한다. Codex 프로젝트에 그 폴더를 연결하고 index.html과 .git이 있는 저장소를 기본 작업 폴더로 선택한다. Codex에 현재 실행 환경을 확인시켜 로컬 미리보기를 연다.
한 줄씩 읽고 실행
git clone https://github.com/YOUR_ID/assignment-check.git
cd assignment-check
git status -sb
git remote -v
git branch --show-current
학생 확인
git remote -v가 본인 저장소인지 확인한다. 다운로드 ZIP에는 Git 이력이 없으므로 commit·push 실습은 clone한 폴더에서 한다. 편집기와 터미널이 같은 폴더여야 한다.
강사용 설명
GitHub는 원격 저장소, clone은 내 컴퓨터 복사본, Codex는 이 폴더의 파일을 읽고 수정하는 도구라고 설명한다. 복사본 폴더를 여러 개 만들지 않는다. 로컬 서버가 필요하면 Codex가 설치된 실행 도구를 확인해 띄우게 한다. Python 등 새 도구 설치를 수업의 필수로 만들지 않는다.
현재 폴더의 과제 체크 앱을 읽고 현재 경로, Git 브랜치와 status, index.html의 역할, localStorage 키와 데이터 구조를 먼저 요약해줘. 이어 각 과제에 수정 버튼을 추가하고 기존 입력 폼에서 제목·과목·마감일을 수정·저장·취소할 수 있게 최소 변경해줘. 같은 id 항목만 변경하고 done·createdAt·과제 개수와 기존 저장 데이터는 유지해줘. 취소는 저장하지 않고 수정 버튼으로 초점을 돌려줘. 빈값·공백·길이·날짜 검증, textContent, 입력 label과 네이티브 버튼을 유지해줘. 수정 중인 항목을 삭제할 때 폼도 정리해줘. 변경 파일과 diff, 실제 검증 결과를 설명해줘. 서버·DB·외부 패키지·API는 추가하지 마. 아직 commit·push·PR·merge는 실행하지 마.
학생 확인
제목·과목·마감일만 바뀌고 원래 과제는 1개다. 취소하면 원래 목록이 유지된다. git diff --cached에 의도한 파일만 있다. 커밋 메시지는 Add assignment editing이다.
강사용 설명
1교시 50분은 도입8·clone/브랜치10·읽기/수정20·짝 검증8·커밋/정리4로 진행한다. AI가 전체 앱을 다시 만들면 저장 키와 id 보존 조건을 재확인한다. 코드 저장과 commit의 차이를 보여준다.
현재 수정 기능과 기존 추가·완료·삭제/되돌리기·저장·날짜 집계를 보존하고 제목·과목 부분 검색을 추가해줘. 앞뒤 공백을 제거하고 대소문자를 무시하며 전체/미완료/완료 필터와 함께 적용해줘. 검색어가 비면 기존 필터 목록, 결과가 없으면 안내, 검색 지우기 버튼과 현재 표시 개수를 제공해줘. 검색은 과제를 삭제하거나 전체 집계·저장 데이터를 바꾸지 않는 화면 조건이야. label이 연결된 네이티브 search 입력을 쓰고 검색 조건은 저장하지 마. 추가·수정 저장 뒤 검색과 완료 필터를 초기화해 결과를 보여줘. 모바일 가로 넘침과 키보드 조작을 확인해줘. 변경 파일·diff·검증 결과를 보여주되 commit·push·PR·merge는 아직 하지 마.
학생 확인
검색은 보이는 목록만 바꾸고 데이터와 전체 현황 숫자는 유지한다. git log에 수정 기능과 검색 기능이 서로 다른 커밋으로 보인다.
강사용 설명
2교시 50분은 검색 요구8·구현15·회귀 검증15·짝 검증8·커밋/정리4다. 검색 기능이 쉬워 보여도 저장 데이터와 필터 조합에 영향을 줄 수 있음을 보여준다. 첫 커밋 덕분에 두 변경의 이유를 따로 설명할 수 있다.
현재 과제 체크 앱의 diff를 검토하고 실제 실행한 검사와 미확인을 구분해줘. 전날 데이터가 유지되는지, 완료한 과제를 수정해도 id·done·createdAt·개수가 같은지, 취소 전후 저장 데이터가 같은지, 공백 수정 거절, 제목/과목 검색·대소문자·빈 결과·완료 필터 조합·검색 지우기, 수정 중 삭제/되돌리기, 추가·완료·새로고침·날짜 집계·저장 실패 안내를 확인해줘. 390px 화면과 키보드 초점도 확인해줘. 문제는 재현 절차와 최소 수정으로 보고해줘. 마지막으로 기능별 커밋 메시지와 PR 제목·본문 초안을 제안해줘. 현재 경로·브랜치·remote와 대상 파일을 다시 확인해주고 사용자 데이터·비밀값을 출력하지 마. 원격 push·PR 생성·merge·배포는 내가 해당 단계에서 실행할게.
학생 확인
통과/실패/미확인과 실제 결과를 기록한다. 검사 데이터는 가상 과제만 쓴다. 저장 값 확인은 수업용 가상 데이터에 한해 개발자 도구 Application→Local Storage에서 id·done·createdAt을 비교한다.
강사용 설명
개발자 도구의 localStorage를 지우거나 운영 데이터를 덮어쓰지 않는다. localhost와 Pages는 서로 다른 origin이라 목록이 다른 것은 정상이다. 강사 공개 앱의 루트와 /day2/는 같은 origin·키로 목록을 이어 읽도록 설계했다.
대상 remote와 브랜치를 확인한 뒤 push한다. 본인 GitHub 저장소의 Pull requests→New pull request에서 base:main, compare:feature/edit-tasks를 선택한다. 제목과 아래 PR 본문 예시를 채우고 Files changed에서 변경을 읽은 뒤 PR을 만든다.
한 줄씩 읽고 실행
git push -u origin feature/edit-tasks
학생 확인
커밋 2개가 PR에 보이고 본인 저장소의 main을 대상으로 한다. push만으로 main이 바뀌거나 Pages에 기능이 공개된 것은 아니다. 강사 예제 저장소에 학생 PR을 만들지 않는다.
강사용 설명
3교시 도입8분에 G05 상태 변화를 보여준다. push/PR12·짝 검토10·병합/Pages8·배포 검증8·정리4분이다. PR 제목보다 변경 이유·검증 근거를 읽게 한다. private 토큰·실제 학생 과제는 본문에 넣지 않는다.
짝이 PR의 변경과 검사 결과를 읽고 개선점을 한 가지 적는다. 본인 PR을 Merge pull request로 병합한다. Settings→Pages/Actions에서 배포 완료와 사용한 커밋을 확인하고 본인 github.io 주소에서 수정·검색·새로고침을 다시 검사한다. 로컬 main을 아래 명령으로 갱신한다.
한 줄씩 읽고 실행
git switch main
git pull --ff-only origin main
git log --oneline -3
학생 확인
기록: PR URL, 두 기능 커밋, main 병합 커밋, Pages 배포 커밋, 공개 앱 URL, 확인자와 실제 결과. 배포 대기 중은 미확인으로 기록하고 다음 교시 시작 때 확인한다.
강사용 설명
PR 병합은 소스 반영, Pages built는 배포 결과, 브라우저 검사는 사용자가 보는 동작의 근거다. 서로 구분한다. 원격 main을 병합한 뒤 로컬 main도 갱신한다. 기능 브랜치 삭제·충돌 해결은 필수가 아니다. 4교시는 기존 Docker·로컬 Supabase 준비로 이어가며 하루 시간은 늘리지 않는다.
체크박스는 실제 확인한 항목만 채운다. 아래 공개 PR은 강사가 제공한 참고 사례이며 학생은 자기 저장소에 별도 PR을 만든다.
## 변경 이유
마감일과 과제명을 고치고 많은 과제에서 제목·과목을 빠르게 찾기 위해 개선했습니다.
## 변경 내용
- 과제 수정·저장·취소
- 제목·과목 검색과 기존 완료 필터 조합
## 직접 검증
- [ ] 기존 데이터·완료 상태·id·생성시각·개수 유지
- [ ] 취소·공백 거절·검색/필터·삭제/되돌리기
- [ ] 새로고침·휴대폰·키보드
## 공개 확인 (병합 뒤 작성)
Pages 배포 커밋: / 앱 URL: / 확인자·결과:
tasks: id · user_id · title · subject · due · done · created_at / authenticated 최소 GRANT · anon 접근 차단 / SELECT/DELETE USING · INSERT WITH CHECK / UPDATE USING + WITH CHECK · 소유자/id/생성시각 보존
086
과제 앱의 Supabase DB·RLS 설계
과제 앱의 Supabase DB·RLS 설계를 실제 과제 체크 앱에서 구현·검증한다. / 산출물: tasks 스키마·RLS SQL·로컬 Vite 앱 / 도입8·환경10·DB/RLS20·짝검증8·정리4분
AI Studio는 빠른 시제품 경로로 비교하고 주 실습은 Codex 앱을 이어간다. / 생성된 화면과 실제 PostgreSQL 저장·Auth·권한은 구분한다. / tasks의 user_id와 입력 제약을 설계하고 GRANT·RLS를 함께 확인한다. / 로컬과 클라우드 프로젝트의 URL·DB 대상을 명시한다.
089
u-livet · 이번 실습에 적용할 판단
기록: 데이터 구조·운영 화면을 만든 뒤 로컬 인증과 업무 기능을 검증했다. / 판단: 데이터 저장 구조와 운영 환경을 구분했다. / 적용: 과제 앱은 user_id를 기준으로 계정별 PostgreSQL 행을 설계한다.
090
실습 절차
아래 단계별 실습을 진행한다.
091
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
092
완료 확인과 오류 대응
필수 범위는 이메일 로그인과 회원별 tasks CRUD다. Google·Kakao·Naver·MFA는 4일차에서 이어간다. 브라우저의 전날 목록은 자동 업로드하지 않으며 가상 과제를 새로 만든다. / Node는 설치 버전의 요구사항을 만족해야 한다(제공 예제는22.12이상). .env.local은 Git에서 무시된다. VITE_ 값은 브라우저에 포함되는 공개 설정이며 secret/service_role을 숨겨주는 기능이 아니다. / tasks.user_id는 auth.users.id와 연결된다. SELECT/DELETE는 소유자 USING, INSERT는 WITH CHECK, UPDATE는 USING+WITH CHECK다. anon에는 권한이 없다. 명시적 GRANT와 RLS는 각각 “테이블 접근”과 “행 접근”을 담당한다.
같은 과제 앱의 회원별 DB 실습
K01~K08과 C01~C08을 이어서 사용한다. 전날 화면·검색·수정은 유지하고 Supabase DB·Auth와 Vercel을 연결한다.
Google AI Studio15분 안내는 선택 비교로 유지한다. 모두가 새 프로젝트를 만들도록 요구하지 않고 Supabase DB·RLS 설계 실습으로 이어간다.
강사용 설명
1교시 도입8분 중 AI Studio는 선택 비교 시연5분만 사용한다. 주 실습은 Codex 경로다. 프런트엔드 HTML/CSS/JavaScript, Vite 빌드 도구, Supabase PostgreSQL·Auth·Data API의 역할을 실제 앱으로 비교한다. 별도 서버 언어를 새로 배우는 시간이 아니다. 2일차에 clone한 폴더를 그대로 사용한다. Vite는 기존 HTML·JS를 빌드하는 도구다. ZIP의 solution 안 파일을 저장소 루트에 놓고 .git은 교체하지 않는다. GitHub Pages는 Vite 소스를 그대로 실행하지 못하므로 3일차 공개 주소는 Vercel로 기록한다. 이전 Pages 화면은 2일차 커밋에서 다시 확인할 수 있다. 1교시50분은 도입8·프로젝트/환경10·스키마/RLS20·짝점검8·정리4다. 테이블이 보인다는 이유로 API 접근 성공을 단정하지 않는다. 최신 Supabase의 자동 노출 변화에 맞춰 명시적 GRANT를 사용한다. SQL Editor의 관리자 권한은 일반 사용자 RLS 검사를 대신하지 못한다. 프로젝트 생성·계정 준비는 수업 전에 마친다. C01~C08과 K01~K08의 상세 교재·전날 시작/완성 소스/SQL ZIP을 사용한다.
실습 절차
같은 앱의 저장 위치와 완료 기준 정하기: 전날 수정·검색 앱을 열고 “이 브라우저 저장”과 “계정별 DB 저장”의 차이를 적는다. 오늘의 완료 기준은 회원가입→이메일 확인→로그인→내 과제 저장→새로고침·다른 브라우저 재조회→다른 계정 차단→공개 Vercel URL이다.
기능 브랜치와 Vite·공개 설정 준비: 본인 저장소의 깨끗한 main을 최신화하고 feature/supabase-tasks를 만든다. Codex에 요청1을 입력해 기존 화면을 Vite 프로젝트로 구성한다. ZIP solution을 참고하면 index.html/styles.css/src/·package.json·lockfile·sql/을 저장소 루트에 둔다. .env.example을 .env.local로 복사해 본인 수업 프로젝트 URL과 publishable key를 입력하고 npm ci, npm run dev를 실행한다.
새 수업용 DB에 tasks·GRANT·RLS 적용: 자신의 새 수업용 Supabase 프로젝트에서 SQL Editor를 열어 sql/setup.sql 전체를 한 번 실행한다. 기존 tasks가 있으면 구조와 정책부터 비교하고 임의로 삭제하지 않는다. sql/inspect.sql로 RLS·네 정책·authenticated의 접근 권한과 편집 가능한 열을 확인한다. Data API의 public 노출과 테이블 권한을 함께 확인한다.
Codex 구현 요청
2일차 과제 체크 앱과 현재 Git 브랜치·diff를 먼저 읽어줘. 기존 HTML/CSS·수정·검색·완료·집계를 유지하면서 Vite와 @supabase/supabase-js로 구성해줘. 현재 공식 문서와 설치 버전의 Node 요구를 확인하고 패키지 버전을 고정해 lockfile을 남겨줘. DB는 새 수업용 Supabase의 public.tasks에 id uuid, user_id(auth.users 참조), title(공백만 불가·60자), subject(20자), due(date), done, created_at을 둬. user_id·due 인덱스, RLS, authenticated의 최소 GRANT와 본인 user_id=auth.uid() 조건의 SELECT/INSERT/UPDATE/DELETE 정책을 SQL 파일로 만들어줘. UPDATE는 USING과 WITH CHECK를 모두 쓰고 소유자·id·생성시각은 수정하지 못하게 해줘. anon은 데이터에 접근하지 못하게 해줘. 공개 URL과 publishable key만 VITE_SUPABASE_URL/VITE_SUPABASE_PUBLISHABLE_KEY로 읽고 .env.example과 .gitignore를 만들어줘. secret/service_role·DB 비밀번호는 브라우저나 Git에 넣지 마. 기존 운영 DB에 SQL을 실행하지 말고 새 수업 프로젝트에서 적용할 SQL과 검증 절차를 보여줘.
AI 검증·오류 해결 요청
수업용 Supabase에서 계정 A와 B를 만들어 실제 가입 메일·로그인을 확인해줘. A가 만든 과제를 새로고침과 다른 브라우저의 A 로그인으로 다시 읽고 수정·완료·삭제·되돌리기를 검사해줘. B에서는 A 행 조회0건, A id를 지정한 수정·삭제0건 또는 거절, user_id=A로 위조INSERT 거절, 소유자변경 거절을 Data API 요청으로 검사해줘. 미로그인 요청도 차단되는지 확인해줘. SQL Editor 관리자 조회를 RLS 통과 증거로 쓰지 마. 계정 전환 직후와 늦은 이전 요청 응답에서 A 목록이 B 화면에 나타나지 않는지, 네트워크 실패가 저장 성공으로 표시되지 않는지 확인해줘. 공개 Vercel URL의 배포commit·환경·가입복귀URL도 대조하고 결과를 통과/실패/미확인으로 기록해줘. 이메일·비밀번호·토큰은 보고서에 넣지 마.
완료 확인
필수 범위는 이메일 로그인과 회원별 tasks CRUD다. Google·Kakao·Naver·MFA는 4일차에서 이어간다. 브라우저의 전날 목록은 자동 업로드하지 않으며 가상 과제를 새로 만든다.
Node는 설치 버전의 요구사항을 만족해야 한다(제공 예제는22.12이상). .env.local은 Git에서 무시된다. VITE_ 값은 브라우저에 포함되는 공개 설정이며 secret/service_role을 숨겨주는 기능이 아니다.
tasks.user_id는 auth.users.id와 연결된다. SELECT/DELETE는 소유자 USING, INSERT는 WITH CHECK, UPDATE는 USING+WITH CHECK다. anon에는 권한이 없다. 명시적 GRANT와 RLS는 각각 “테이블 접근”과 “행 접근”을 담당한다.
가입 요청·이메일 확인·세션 생성·비밀번호 복구를 구분한다. / Resend 도메인/DNS→Supabase SMTP→메일 템플릿·복귀 주소 순서로 확인한다. / 발송 요청 성공과 수신함 도착·링크 처리 성공을 따로 기록한다. / Site URL·Redirect URLs·비밀번호 정책과 비밀 보관 위치를 대조한다.
098
u-livet · 이번 실습에 적용할 판단
기록: 로그인 성공 뒤에도 가입 완료 상태와 업무 자격을 따로 확인했다. / 비교: uc-anchor의 Resend 구축 기록은 DNS·SMTP·메일 링크를 구분한다. / 적용: 과제 앱에서 확인 대기·실제 수신·로그인·복구를 각각 검사한다.
099
실습 절차
아래 단계별 실습을 진행한다.
100
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
101
완료 확인과 오류 대응
가입 요청 성공과 메일 도착·확인·로그인 성공을 따로 기록한다. PKCE 확인 링크는 요청한 같은 브라우저에서 연다. 다른 브라우저에서 확인했다면 이메일 확인 완료 후 원래 앱에서 이메일/비밀번호 로그인을 다시 시도한다.
같은 과제 앱의 회원별 DB 실습
K01~K08과 C01~C08을 이어서 사용한다. 전날 화면·검색·수정은 유지하고 Supabase DB·Auth와 Vercel을 연결한다.
DNS·SMTP는 강사가 미리 준비한다. 도메인 설정과 메일 실패 진단표는 별도 원문으로 제공하고 교시에서는 실제 가입·확인 대기·로그인을 완주한다.
강사용 설명
2교시 50분은 흐름8·Auth 구현22·메일/세션8·짝 검증8·정리4다. 기본 SMTP는 발송 대상과 사용량 제한이 있다. 강사는 Resend 검증 도메인·SMTP 또는 로컬 Mailpit를 사전 준비한다. 메일이 막히면 이메일 확인 기능을 끄지 않고 발송 경로를 고친다. 비밀번호 분실 복구·변경은 완성 예제와 F09~F14를 보충으로 사용한다. C01~C08과 K01~K08의 상세 교재·전날 시작/완성 소스/SQL ZIP을 사용한다.
실습 절차
회원가입·메일 확인·로그인·로그아웃 연결: 요청2를 Codex에 입력하고 Authentication의 Email 가입과 Confirm email을 확인한다. 비밀번호 최소12자 정책을 앱과 서버에서 맞춘다. URL Configuration의 Site URL과 Redirect URLs에 실제 로컬 루트(http://localhost:5173/ 등)를 설정한다. 수신 가능한 수업 계정 A로 가입하고 메일 확인 뒤 로그인·새로고침·로그아웃을 각각 시험한다.
Codex 구현 요청
현재 앱에 Supabase 이메일 회원가입·확인 대기·로그인·로그아웃과 회원별 과제 CRUD를 연결해줘. 가입 응답에서 session이 없으면 메일 확인 대기로 표시하고 로그인 성공과 구분해줘. 단일 브라우저 앱은 PKCE·현재 루트 복귀 주소·SDK 세션 복원을 일관되게 사용하고 허용 Redirect URL을 안내해줘. 가입 new-password/로그인 current-password, label·POST form·비밀번호 표시·12자 가입 규칙·중복 제출 방지를 넣어줘. 비밀번호·토큰·계정 존재 여부를 로그나 오류에 노출하지 마. 로그인 전 목록을 숨기고 계정 변경 즉시 이전 목록·편집·되돌리기를 지워줘. 이전 계정의 늦은 요청 응답도 무시해줘. DB select/insert/update/delete의 error와 반환 행을 확인한 뒤만 성공을 표시해줘. 수정은 동일 id·완료·생성시각을 보존하고 삭제 되돌리기도 DB에 반영해줘. 검색·완료 필터·날짜 집계를 유지하며 다른 기기 변경은 새로 불러오기로 조회해줘. localStorage의 전날 과제를 자동 업로드하지 말고 가상 DB 과제로 시작해줘. 네트워크 실패 때 미저장 입력을 유지해줘. 실제 실행 결과와 미실행을 구분해줘.
AI 검증·오류 해결 요청
수업용 Supabase에서 계정 A와 B를 만들어 실제 가입 메일·로그인을 확인해줘. A가 만든 과제를 새로고침과 다른 브라우저의 A 로그인으로 다시 읽고 수정·완료·삭제·되돌리기를 검사해줘. B에서는 A 행 조회0건, A id를 지정한 수정·삭제0건 또는 거절, user_id=A로 위조INSERT 거절, 소유자변경 거절을 Data API 요청으로 검사해줘. 미로그인 요청도 차단되는지 확인해줘. SQL Editor 관리자 조회를 RLS 통과 증거로 쓰지 마. 계정 전환 직후와 늦은 이전 요청 응답에서 A 목록이 B 화면에 나타나지 않는지, 네트워크 실패가 저장 성공으로 표시되지 않는지 확인해줘. 공개 Vercel URL의 배포commit·환경·가입복귀URL도 대조하고 결과를 통과/실패/미확인으로 기록해줘. 이메일·비밀번호·토큰은 보고서에 넣지 마.
완료 확인
가입 요청 성공과 메일 도착·확인·로그인 성공을 따로 기록한다. PKCE 확인 링크는 요청한 같은 브라우저에서 연다. 다른 브라우저에서 확인했다면 이메일 확인 완료 후 원래 앱에서 이메일/비밀번호 로그인을 다시 시도한다.
A가 추가한 과제를 새로고침해 같은 값으로 읽는지 확인한다. / B가 A의 id를 알아도 조회·수정·삭제할 수 없어야 한다. / SQL Editor 관리자 결과와 일반 사용자의 Data API 검사는 다르다. / 소유권 위조·빈 결과·오류를 정상 CRUD와 함께 기록한다.
107
kysong · 이번 실습에 적용할 판단
기록: 임시저장·복구와 최종 제출 판정의 일관성을 보완했다. / 판단: 저장 성공 안내만으로 실제 DB 결과를 판단하지 않는다. / 적용: 과제 앱에서 재조회 값과 사용자별 기록 분리를 함께 확인한다.
108
실습 절차
아래 단계별 실습을 진행한다.
109
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
110
완료 확인과 오류 대응
insert/update/delete의 error와 반환 행을 확인해야 성공이다. 수정 후 id·done·created_at과 개수는 유지된다. 다른 기기에서 같은 계정으로 로그인한 뒤 다시 조회하면 같은 DB 목록을 읽는다. 이 예제의 새로 불러오기는 Realtime 자동 동기화가 아니다. / A는 자기 행만 CRUD, B의 A 조회는0건, 수정·삭제는0건 또는 오류, 위조 INSERT·소유자 변경은거절되어야 한다. 미로그인도 데이터에 접근하지 못한다. 화면이 숨겨졌다는 이유만으로 RLS 통과라 하지 않는다.
같은 과제 앱의 회원별 DB 실습
K01~K08과 C01~C08을 이어서 사용한다. 전날 화면·검색·수정은 유지하고 Supabase DB·Auth와 Vercel을 연결한다.
이 교시의 과제 앱은 아직4일차 MFA를 추가하기 전이다. RLS A/B 기본 검사를 하고 MFA 전후 통합 검사는4~5일차로 이어간다.
강사용 설명
3교시 50분은 읽기/쓰기8·CRUD 구현18·계정/권한12·짝 검증8·정리4다. DB 저장에서는 비동기 오류와 인증 상태를 함께 다룬다. 저장 실패 때 입력값을 남기고 성공 안내를 보내지 않는지 보여준다. 예제는 최대1,000건을 조회하며 대량 데이터는 페이지 나누기를 추후 설계한다. 완성 ZIP의 절차와 Codex 검증 요청을 사용한다. 토큰을 스크린샷이나 과제물에 넣지 않는다. 화면 검색으로 A의 과제를 숨기는 것과 DB가 접근을 거절하는 것은 다르다. 빈 목록이 테이블 권한 오류인지 정상 RLS 차단인지 구분한다. 계정을 바꾸는 동안 도착한 이전 요청의 응답도 다른 계정 화면에 나타나지 않아야 한다. C01~C08과 K01~K08의 상세 교재·전날 시작/완성 소스/SQL ZIP을 사용한다.
실습 절차
내 과제 CRUD를 DB에 저장하고 다시 읽기: A로 로그인해 가상 과제 두 개를 DB에 추가한다. 완료 상태를 바꾸고 제목·과목·마감일을 수정한다. 새로고침과 새로 불러오기로 DB 값을 재조회하고 삭제/되돌리기도 확인한다. 제목·과목 검색과 완료 필터·오늘/기한 지남 집계가 현재 계정 목록에서 작동하는지 확인한다.
A·B 계정과 미로그인으로 권한 검증: 브라우저 일반 창의 A와 별도 프로필/다른 브라우저의 B로 각각 로그인한다. A의 과제 id를 가상 테스트 기록에만 적고, B의 Data API로 A id 조회·수정·삭제와 user_id 위조 INSERT를 검사한다. 로그아웃 직후 목록과 폼이 비워지는지도 확인한다. 상세 검증표의 실제 결과를 기록한다.
Codex 구현 요청
현재 앱에 Supabase 이메일 회원가입·확인 대기·로그인·로그아웃과 회원별 과제 CRUD를 연결해줘. 가입 응답에서 session이 없으면 메일 확인 대기로 표시하고 로그인 성공과 구분해줘. 단일 브라우저 앱은 PKCE·현재 루트 복귀 주소·SDK 세션 복원을 일관되게 사용하고 허용 Redirect URL을 안내해줘. 가입 new-password/로그인 current-password, label·POST form·비밀번호 표시·12자 가입 규칙·중복 제출 방지를 넣어줘. 비밀번호·토큰·계정 존재 여부를 로그나 오류에 노출하지 마. 로그인 전 목록을 숨기고 계정 변경 즉시 이전 목록·편집·되돌리기를 지워줘. 이전 계정의 늦은 요청 응답도 무시해줘. DB select/insert/update/delete의 error와 반환 행을 확인한 뒤만 성공을 표시해줘. 수정은 동일 id·완료·생성시각을 보존하고 삭제 되돌리기도 DB에 반영해줘. 검색·완료 필터·날짜 집계를 유지하며 다른 기기 변경은 새로 불러오기로 조회해줘. localStorage의 전날 과제를 자동 업로드하지 말고 가상 DB 과제로 시작해줘. 네트워크 실패 때 미저장 입력을 유지해줘. 실제 실행 결과와 미실행을 구분해줘.
AI 검증·오류 해결 요청
수업용 Supabase에서 계정 A와 B를 만들어 실제 가입 메일·로그인을 확인해줘. A가 만든 과제를 새로고침과 다른 브라우저의 A 로그인으로 다시 읽고 수정·완료·삭제·되돌리기를 검사해줘. B에서는 A 행 조회0건, A id를 지정한 수정·삭제0건 또는 거절, user_id=A로 위조INSERT 거절, 소유자변경 거절을 Data API 요청으로 검사해줘. 미로그인 요청도 차단되는지 확인해줘. SQL Editor 관리자 조회를 RLS 통과 증거로 쓰지 마. 계정 전환 직후와 늦은 이전 요청 응답에서 A 목록이 B 화면에 나타나지 않는지, 네트워크 실패가 저장 성공으로 표시되지 않는지 확인해줘. 공개 Vercel URL의 배포commit·환경·가입복귀URL도 대조하고 결과를 통과/실패/미확인으로 기록해줘. 이메일·비밀번호·토큰은 보고서에 넣지 마.
완료 확인
insert/update/delete의 error와 반환 행을 확인해야 성공이다. 수정 후 id·done·created_at과 개수는 유지된다. 다른 기기에서 같은 계정으로 로그인한 뒤 다시 조회하면 같은 DB 목록을 읽는다. 이 예제의 새로 불러오기는 Realtime 자동 동기화가 아니다.
A는 자기 행만 CRUD, B의 A 조회는0건, 수정·삭제는0건 또는 오류, 위조 INSERT·소유자 변경은거절되어야 한다. 미로그인도 데이터에 접근하지 못한다. 화면이 숨겨졌다는 이유만으로 RLS 통과라 하지 않는다.
자주 막히는 지점
Invalid API key/설정 전: 변수명·VITE_접두사·재시작/재빌드
permission denied: 명시GRANT와소유자정책확인
목록0건: 빈목록정상인지API오류인지구분
수정/삭제0건: 반환행없음을성공으로표시하지않기
가입메일없음: 강사사전준비SMTP/로컬Mailpit사용
확인후localhost복귀: 실제Production루트로정정
PKCE교환실패: 같은브라우저에서새링크또는확인후로그인
Vercel빌드실패: npm ci/npm run build로재현
이전환경이보임: 환경변경뒤새배포
전날목록이없음: 자동업로드없음·가상DB과제로시작
B가A과제를봄: 공개중단후소유자정책/세션분리수정
확인 질문
B가 A의 과제 id를 알게 되어도 안전한가?
RLS와 GRANT가 올바르면 조회·수정·삭제 및 소유자 위조가 차단된다. 실제 API 요청으로 확인한다.
Supabase Site URL:고정 Production 루트 / Redirect URLs:정확한 로컬/공개/필요 Preview 주소 / 공개 앱에서 가입·로그인·CRUD·기기간·A/B 검증 / 제출:PR · 배포 SHA · Vercel URL · 실제 검사표
113
과제 앱 Vercel 배포와 공개 검증
과제 앱 Vercel 배포와 공개 검증를 실제 과제 체크 앱에서 구현·검증한다. / 산출물: Vercel 공개 URL·PR·배포 근거 / 빌드/Git8·배포12·URL/메일10·공개검증12·정리8분
114
핵심 개념
Vite build·dist·Root Directory / 환경 범위·재빌드·Site URL / 배포 상태와 공개 기능 검증
115
GitHub에서 Vercel·Supabase로 이어지는 설정
GitHub는 소스 이력, Vercel은 앱 배포, Supabase는 DB·Auth를 담당한다. / Root Directory·build·dist와 환경변수 범위를 확인한다. / 공개 URL/key와 서버 비밀을 구분하고 환경 변경 뒤 재빌드한다. / READY 후 공개 URL의 로그인·메일 복귀·저장/재조회를 검사한다.
116
uc-anchor · 이번 실습에 적용할 판단
기술: TypeScript 프런트엔드·Node.js API·PostgreSQL이 다른 역할을 맡는다. / 비교: Git의 코드와 Supabase Auth/SMTP 설정은 별도 기록이다. / 적용: 과제 앱의 배포 commit·앱 URL·DB 대상을 한 표에 적는다.
117
실습 절차
아래 단계별 실습을 진행한다.
118
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
119
완료 확인과 오류 대응
solution 안 파일을 저장소 루트에 넣었다면 Root Directory는 루트다. solution 폴더 자체를 저장소에 넣었다면 실제 경로를 지정한다. Vercel이 Next.js 또는 public/을 빌드하도록 설정되어 있으면 고친다. DB 비밀번호나 service_role은 입력하지 않는다. / 제출: PR URL·main 커밋·배포 커밋·Vercel URL·Supabase 프로젝트 식별자(키 제외)·메일/로그인/DB/권한 체크표. READY는 빌드·배포 상태이며 실제 가입·저장 통과와 다르다. 대기나 미실행은 미확인으로 기록한다.
같은 과제 앱의 회원별 DB 실습
K01~K08과 C01~C08을 이어서 사용한다. 전날 화면·검색·수정은 유지하고 Supabase DB·Auth와 Vercel을 연결한다.
서버 설정이 저장소에 모두 포함돼 있다고 가정하지 않는다. 배포 성공과 회원 서비스의 실제 동작 성공을 구분한다.
강사용 설명
4교시 50분은 빌드/Git8·Vercel12·URL/메일10·공개 A/B 검증12·짝 확인/정리8이다. 전날의 PR 절차를 재사용한다. Framework 자동 감지 결과를 검토한다. 환경값은 빌드에 포함되므로 변경 후 재배포한다. 앱 배포와 DB 스키마 적용은 별개이며 Vercel이 SQL을 자동 실행하지 않는다. Resend SMTP는 Supabase Auth 설정에 보관한다. 앱이 별도 메일을 보내지 않는다면 Resend 키를 Vercel 클라이언트 환경에 넣지 않는다. SNS와 MFA는4일차에 진행하고 오늘은 이메일·DB·배포를 완주한다. 강의자료의 배포 검증과 학생 개인 프로젝트의 실제 통합 검증을 구분한다. C01~C08과 K01~K08의 상세 교재·전날 시작/완성 소스/SQL ZIP을 사용한다.
실습 절차
Git PR → Vercel Vite 배포: 아래처럼 소스·SQL·lockfile·환경 예시만 stage하고 diff를 검토해 commit·push한다. 본인 저장소의 main을 대상으로 PR을 만들고 검토·병합한다. Vercel Add New Project→본인 GitHub 저장소 Import에서 앱이 있는 Root Directory, Vite, npm run build, dist를 확인한다. 두 VITE 공개 변수를 Production과 필요한 Preview에 입력하고 Deploy한다.
공개 URL·인증 복귀·DB 재조회 최종 확인: Vercel의 실제 Production URL을 Supabase Site URL로 넣고 Redirect URLs에 정확한 공개 루트와 필요한 로컬/Preview 루트를 허용한다. 설정 변경 뒤 새 배포를 만들고 가입메일이 공개 앱으로 돌아오는지 확인한다. 공개 앱에서 추가·수정·검색·완료·로그아웃·새로고침·다른 브라우저 A 재조회·B 차단을 다시 검사한다.
Codex 구현 요청
이 과제 앱을 학생 본인 GitHub 저장소에서 Vercel로 배포할 절차를 준비해줘. 현재 경로·브랜치·remote, package.json·lockfile·build를 확인해줘. Vercel Root Directory는 실제 앱 위치, Framework는 Vite, Build는 npm run build, Output은 dist로 맞추고 두 VITE 공개 변수의 Production/필요한 Preview 범위를 설명해줘. .env.local·키·토큰은 commit하지 마. Supabase Site URL은 실제 고정 Production 루트, Redirect URLs는 로컬5173/와 정확한 공개 루트로 맞추고 환경값 변경 뒤 재빌드하도록 안내해줘. PR diff·검증표·병합·배포 SHA를 남겨줘. 가입 요청/메일 도착/확인/로그인/DB 저장/재조회/다른 브라우저 동일계정 복원/A·B 분리/미로그인 차단을 따로 검사해줘. RLS 실패를 UI 숨김·RLS 해제·service_role 노출로 우회하지 마. 원격 변경과 SQL 적용은 수업 단계에서 대상 프로젝트를 확인한 뒤 실행하게 해줘.
AI 검증·오류 해결 요청
수업용 Supabase에서 계정 A와 B를 만들어 실제 가입 메일·로그인을 확인해줘. A가 만든 과제를 새로고침과 다른 브라우저의 A 로그인으로 다시 읽고 수정·완료·삭제·되돌리기를 검사해줘. B에서는 A 행 조회0건, A id를 지정한 수정·삭제0건 또는 거절, user_id=A로 위조INSERT 거절, 소유자변경 거절을 Data API 요청으로 검사해줘. 미로그인 요청도 차단되는지 확인해줘. SQL Editor 관리자 조회를 RLS 통과 증거로 쓰지 마. 계정 전환 직후와 늦은 이전 요청 응답에서 A 목록이 B 화면에 나타나지 않는지, 네트워크 실패가 저장 성공으로 표시되지 않는지 확인해줘. 공개 Vercel URL의 배포commit·환경·가입복귀URL도 대조하고 결과를 통과/실패/미확인으로 기록해줘. 이메일·비밀번호·토큰은 보고서에 넣지 마.
완료 확인
solution 안 파일을 저장소 루트에 넣었다면 Root Directory는 루트다. solution 폴더 자체를 저장소에 넣었다면 실제 경로를 지정한다. Vercel이 Next.js 또는 public/을 빌드하도록 설정되어 있으면 고친다. DB 비밀번호나 service_role은 입력하지 않는다.
제출: PR URL·main 커밋·배포 커밋·Vercel URL·Supabase 프로젝트 식별자(키 제외)·메일/로그인/DB/권한 체크표. READY는 빌드·배포 상태이며 실제 가입·저장 통과와 다르다. 대기나 미실행은 미확인으로 기록한다.
각 학생의 새 수업용 Supabase 프로젝트·GitHub·Vercel 권한, Node/Git/Codex, 로그인/메일 수신을 수업 전에 확인한다. 기본 SMTP는 발송 대상과 한도가 있으므로 일반 학생 메일에 자동 발송된다고 가정하지 않는다. 검증한 Resend 도메인/SMTP 또는 로컬 Mailpit를 준비한다. 비밀번호 최소12자 정책을 앱/서버에서 맞춘다. 계정/도구 한도에 막히면 ZIP 체크포인트로 진행하고 클라우드 검증은 미확인으로 남긴다.
학생은 solution 안 파일들을 본인 clone 저장소 루트에 넣고 .git은 유지한다. starter는2일차 완성 단일HTML이다. 완성 소스는 개인 Supabase 설정이 필요한 프로젝트이며 다운로드만으로 로그인·공유 DB가 연결되지는 않는다. 기존 운영 DB와 실제 개인 과제는 사용하지 않는다.
2일차 과제 체크 앱과 현재 Git 브랜치·diff를 먼저 읽어줘. 기존 HTML/CSS·수정·검색·완료·집계를 유지하면서 Vite와 @supabase/supabase-js로 구성해줘. 현재 공식 문서와 설치 버전의 Node 요구를 확인하고 패키지 버전을 고정해 lockfile을 남겨줘. DB는 새 수업용 Supabase의 public.tasks에 id uuid, user_id(auth.users 참조), title(공백만 불가·60자), subject(20자), due(date), done, created_at을 둬. user_id·due 인덱스, RLS, authenticated의 최소 GRANT와 본인 user_id=auth.uid() 조건의 SELECT/INSERT/UPDATE/DELETE 정책을 SQL 파일로 만들어줘. UPDATE는 USING과 WITH CHECK를 모두 쓰고 소유자·id·생성시각은 수정하지 못하게 해줘. anon은 데이터에 접근하지 못하게 해줘. 공개 URL과 publishable key만 VITE_SUPABASE_URL/VITE_SUPABASE_PUBLISHABLE_KEY로 읽고 .env.example과 .gitignore를 만들어줘. secret/service_role·DB 비밀번호는 브라우저나 Git에 넣지 마. 기존 운영 DB에 SQL을 실행하지 말고 새 수업 프로젝트에서 적용할 SQL과 검증 절차를 보여줘.
2. 회원가입·로그인·DB CRUD
현재 앱에 Supabase 이메일 회원가입·확인 대기·로그인·로그아웃과 회원별 과제 CRUD를 연결해줘. 가입 응답에서 session이 없으면 메일 확인 대기로 표시하고 로그인 성공과 구분해줘. 단일 브라우저 앱은 PKCE·현재 루트 복귀 주소·SDK 세션 복원을 일관되게 사용하고 허용 Redirect URL을 안내해줘. 가입 new-password/로그인 current-password, label·POST form·비밀번호 표시·12자 가입 규칙·중복 제출 방지를 넣어줘. 비밀번호·토큰·계정 존재 여부를 로그나 오류에 노출하지 마. 로그인 전 목록을 숨기고 계정 변경 즉시 이전 목록·편집·되돌리기를 지워줘. 이전 계정의 늦은 요청 응답도 무시해줘. DB select/insert/update/delete의 error와 반환 행을 확인한 뒤만 성공을 표시해줘. 수정은 동일 id·완료·생성시각을 보존하고 삭제 되돌리기도 DB에 반영해줘. 검색·완료 필터·날짜 집계를 유지하며 다른 기기 변경은 새로 불러오기로 조회해줘. localStorage의 전날 과제를 자동 업로드하지 말고 가상 DB 과제로 시작해줘. 네트워크 실패 때 미저장 입력을 유지해줘. 실제 실행 결과와 미실행을 구분해줘.
3. GitHub·Vercel·공개 검증
이 과제 앱을 학생 본인 GitHub 저장소에서 Vercel로 배포할 절차를 준비해줘. 현재 경로·브랜치·remote, package.json·lockfile·build를 확인해줘. Vercel Root Directory는 실제 앱 위치, Framework는 Vite, Build는 npm run build, Output은 dist로 맞추고 두 VITE 공개 변수의 Production/필요한 Preview 범위를 설명해줘. .env.local·키·토큰은 commit하지 마. Supabase Site URL은 실제 고정 Production 루트, Redirect URLs는 로컬5173/와 정확한 공개 루트로 맞추고 환경값 변경 뒤 재빌드하도록 안내해줘. PR diff·검증표·병합·배포 SHA를 남겨줘. 가입 요청/메일 도착/확인/로그인/DB 저장/재조회/다른 브라우저 동일계정 복원/A·B 분리/미로그인 차단을 따로 검사해줘. RLS 실패를 UI 숨김·RLS 해제·service_role 노출로 우회하지 마. 원격 변경과 SQL 적용은 수업 단계에서 대상 프로젝트를 확인한 뒤 실행하게 해줘.
추가: A/B 권한·회귀 검사
수업용 Supabase에서 계정 A와 B를 만들어 실제 가입 메일·로그인을 확인해줘. A가 만든 과제를 새로고침과 다른 브라우저의 A 로그인으로 다시 읽고 수정·완료·삭제·되돌리기를 검사해줘. B에서는 A 행 조회0건, A id를 지정한 수정·삭제0건 또는 거절, user_id=A로 위조INSERT 거절, 소유자변경 거절을 Data API 요청으로 검사해줘. 미로그인 요청도 차단되는지 확인해줘. SQL Editor 관리자 조회를 RLS 통과 증거로 쓰지 마. 계정 전환 직후와 늦은 이전 요청 응답에서 A 목록이 B 화면에 나타나지 않는지, 네트워크 실패가 저장 성공으로 표시되지 않는지 확인해줘. 공개 Vercel URL의 배포commit·환경·가입복귀URL도 대조하고 결과를 통과/실패/미확인으로 기록해줘. 이메일·비밀번호·토큰은 보고서에 넣지 마.
전날 수정·검색 앱을 열고 “이 브라우저 저장”과 “계정별 DB 저장”의 차이를 적는다. 오늘의 완료 기준은 회원가입→이메일 확인→로그인→내 과제 저장→새로고침·다른 브라우저 재조회→다른 계정 차단→공개 Vercel URL이다.
학생 확인
필수 범위는 이메일 로그인과 회원별 tasks CRUD다. Google·Kakao·Naver·MFA는 4일차에서 이어간다. 브라우저의 전날 목록은 자동 업로드하지 않으며 가상 과제를 새로 만든다.
강사용 설명
1교시 도입8분 중 AI Studio는 선택 비교 시연5분만 사용한다. 주 실습은 Codex 경로다. 프런트엔드 HTML/CSS/JavaScript, Vite 빌드 도구, Supabase PostgreSQL·Auth·Data API의 역할을 실제 앱으로 비교한다. 별도 서버 언어를 새로 배우는 시간이 아니다.
본인 저장소의 깨끗한 main을 최신화하고 feature/supabase-tasks를 만든다. Codex에 요청1을 입력해 기존 화면을 Vite 프로젝트로 구성한다. ZIP solution을 참고하면 index.html/styles.css/src/·package.json·lockfile·sql/을 저장소 루트에 둔다. .env.example을 .env.local로 복사해 본인 수업 프로젝트 URL과 publishable key를 입력하고 npm ci, npm run dev를 실행한다.
현재 폴더에서 한 줄씩 실행
git status -sb
git switch main
git pull --ff-only origin main
git switch -c feature/supabase-tasks
node --version
npm ci
npm run dev
이 단계의 Codex 요청
2일차 과제 체크 앱과 현재 Git 브랜치·diff를 먼저 읽어줘. 기존 HTML/CSS·수정·검색·완료·집계를 유지하면서 Vite와 @supabase/supabase-js로 구성해줘. 현재 공식 문서와 설치 버전의 Node 요구를 확인하고 패키지 버전을 고정해 lockfile을 남겨줘. DB는 새 수업용 Supabase의 public.tasks에 id uuid, user_id(auth.users 참조), title(공백만 불가·60자), subject(20자), due(date), done, created_at을 둬. user_id·due 인덱스, RLS, authenticated의 최소 GRANT와 본인 user_id=auth.uid() 조건의 SELECT/INSERT/UPDATE/DELETE 정책을 SQL 파일로 만들어줘. UPDATE는 USING과 WITH CHECK를 모두 쓰고 소유자·id·생성시각은 수정하지 못하게 해줘. anon은 데이터에 접근하지 못하게 해줘. 공개 URL과 publishable key만 VITE_SUPABASE_URL/VITE_SUPABASE_PUBLISHABLE_KEY로 읽고 .env.example과 .gitignore를 만들어줘. secret/service_role·DB 비밀번호는 브라우저나 Git에 넣지 마. 기존 운영 DB에 SQL을 실행하지 말고 새 수업 프로젝트에서 적용할 SQL과 검증 절차를 보여줘.
학생 확인
Node는 설치 버전의 요구사항을 만족해야 한다(제공 예제는22.12이상). .env.local은 Git에서 무시된다. VITE_ 값은 브라우저에 포함되는 공개 설정이며 secret/service_role을 숨겨주는 기능이 아니다.
강사용 설명
2일차에 clone한 폴더를 그대로 사용한다. Vite는 기존 HTML·JS를 빌드하는 도구다. ZIP의 solution 안 파일을 저장소 루트에 놓고 .git은 교체하지 않는다. GitHub Pages는 Vite 소스를 그대로 실행하지 못하므로 3일차 공개 주소는 Vercel로 기록한다. 이전 Pages 화면은 2일차 커밋에서 다시 확인할 수 있다.
자신의 새 수업용 Supabase 프로젝트에서 SQL Editor를 열어 sql/setup.sql 전체를 한 번 실행한다. 기존 tasks가 있으면 구조와 정책부터 비교하고 임의로 삭제하지 않는다. sql/inspect.sql로 RLS·네 정책·authenticated의 접근 권한과 편집 가능한 열을 확인한다. Data API의 public 노출과 테이블 권한을 함께 확인한다.
학생 확인
tasks.user_id는 auth.users.id와 연결된다. SELECT/DELETE는 소유자 USING, INSERT는 WITH CHECK, UPDATE는 USING+WITH CHECK다. anon에는 권한이 없다. 명시적 GRANT와 RLS는 각각 “테이블 접근”과 “행 접근”을 담당한다.
강사용 설명
1교시50분은 도입8·프로젝트/환경10·스키마/RLS20·짝점검8·정리4다. 테이블이 보인다는 이유로 API 접근 성공을 단정하지 않는다. 최신 Supabase의 자동 노출 변화에 맞춰 명시적 GRANT를 사용한다. SQL Editor의 관리자 권한은 일반 사용자 RLS 검사를 대신하지 못한다. 프로젝트 생성·계정 준비는 수업 전에 마친다.
요청2를 Codex에 입력하고 Authentication의 Email 가입과 Confirm email을 확인한다. 비밀번호 최소12자 정책을 앱과 서버에서 맞춘다. URL Configuration의 Site URL과 Redirect URLs에 실제 로컬 루트(http://localhost:5173/ 등)를 설정한다. 수신 가능한 수업 계정 A로 가입하고 메일 확인 뒤 로그인·새로고침·로그아웃을 각각 시험한다.
이 단계의 Codex 요청
현재 앱에 Supabase 이메일 회원가입·확인 대기·로그인·로그아웃과 회원별 과제 CRUD를 연결해줘. 가입 응답에서 session이 없으면 메일 확인 대기로 표시하고 로그인 성공과 구분해줘. 단일 브라우저 앱은 PKCE·현재 루트 복귀 주소·SDK 세션 복원을 일관되게 사용하고 허용 Redirect URL을 안내해줘. 가입 new-password/로그인 current-password, label·POST form·비밀번호 표시·12자 가입 규칙·중복 제출 방지를 넣어줘. 비밀번호·토큰·계정 존재 여부를 로그나 오류에 노출하지 마. 로그인 전 목록을 숨기고 계정 변경 즉시 이전 목록·편집·되돌리기를 지워줘. 이전 계정의 늦은 요청 응답도 무시해줘. DB select/insert/update/delete의 error와 반환 행을 확인한 뒤만 성공을 표시해줘. 수정은 동일 id·완료·생성시각을 보존하고 삭제 되돌리기도 DB에 반영해줘. 검색·완료 필터·날짜 집계를 유지하며 다른 기기 변경은 새로 불러오기로 조회해줘. localStorage의 전날 과제를 자동 업로드하지 말고 가상 DB 과제로 시작해줘. 네트워크 실패 때 미저장 입력을 유지해줘. 실제 실행 결과와 미실행을 구분해줘.
학생 확인
가입 요청 성공과 메일 도착·확인·로그인 성공을 따로 기록한다. PKCE 확인 링크는 요청한 같은 브라우저에서 연다. 다른 브라우저에서 확인했다면 이메일 확인 완료 후 원래 앱에서 이메일/비밀번호 로그인을 다시 시도한다.
강사용 설명
2교시 50분은 흐름8·Auth 구현22·메일/세션8·짝 검증8·정리4다. 기본 SMTP는 발송 대상과 사용량 제한이 있다. 강사는 Resend 검증 도메인·SMTP 또는 로컬 Mailpit를 사전 준비한다. 메일이 막히면 이메일 확인 기능을 끄지 않고 발송 경로를 고친다. 비밀번호 분실 복구·변경은 완성 예제와 F09~F14를 보충으로 사용한다.
A로 로그인해 가상 과제 두 개를 DB에 추가한다. 완료 상태를 바꾸고 제목·과목·마감일을 수정한다. 새로고침과 새로 불러오기로 DB 값을 재조회하고 삭제/되돌리기도 확인한다. 제목·과목 검색과 완료 필터·오늘/기한 지남 집계가 현재 계정 목록에서 작동하는지 확인한다.
학생 확인
insert/update/delete의 error와 반환 행을 확인해야 성공이다. 수정 후 id·done·created_at과 개수는 유지된다. 다른 기기에서 같은 계정으로 로그인한 뒤 다시 조회하면 같은 DB 목록을 읽는다. 이 예제의 새로 불러오기는 Realtime 자동 동기화가 아니다.
강사용 설명
3교시 50분은 읽기/쓰기8·CRUD 구현18·계정/권한12·짝 검증8·정리4다. DB 저장에서는 비동기 오류와 인증 상태를 함께 다룬다. 저장 실패 때 입력값을 남기고 성공 안내를 보내지 않는지 보여준다. 예제는 최대1,000건을 조회하며 대량 데이터는 페이지 나누기를 추후 설계한다.
브라우저 일반 창의 A와 별도 프로필/다른 브라우저의 B로 각각 로그인한다. A의 과제 id를 가상 테스트 기록에만 적고, B의 Data API로 A id 조회·수정·삭제와 user_id 위조 INSERT를 검사한다. 로그아웃 직후 목록과 폼이 비워지는지도 확인한다. 상세 검증표의 실제 결과를 기록한다.
학생 확인
A는 자기 행만 CRUD, B의 A 조회는0건, 수정·삭제는0건 또는 오류, 위조 INSERT·소유자 변경은거절되어야 한다. 미로그인도 데이터에 접근하지 못한다. 화면이 숨겨졌다는 이유만으로 RLS 통과라 하지 않는다.
강사용 설명
완성 ZIP의 절차와 Codex 검증 요청을 사용한다. 토큰을 스크린샷이나 과제물에 넣지 않는다. 화면 검색으로 A의 과제를 숨기는 것과 DB가 접근을 거절하는 것은 다르다. 빈 목록이 테이블 권한 오류인지 정상 RLS 차단인지 구분한다. 계정을 바꾸는 동안 도착한 이전 요청의 응답도 다른 계정 화면에 나타나지 않아야 한다.
아래처럼 소스·SQL·lockfile·환경 예시만 stage하고 diff를 검토해 commit·push한다. 본인 저장소의 main을 대상으로 PR을 만들고 검토·병합한다. Vercel Add New Project→본인 GitHub 저장소 Import에서 앱이 있는 Root Directory, Vite, npm run build, dist를 확인한다. 두 VITE 공개 변수를 Production과 필요한 Preview에 입력하고 Deploy한다.
이 과제 앱을 학생 본인 GitHub 저장소에서 Vercel로 배포할 절차를 준비해줘. 현재 경로·브랜치·remote, package.json·lockfile·build를 확인해줘. Vercel Root Directory는 실제 앱 위치, Framework는 Vite, Build는 npm run build, Output은 dist로 맞추고 두 VITE 공개 변수의 Production/필요한 Preview 범위를 설명해줘. .env.local·키·토큰은 commit하지 마. Supabase Site URL은 실제 고정 Production 루트, Redirect URLs는 로컬5173/와 정확한 공개 루트로 맞추고 환경값 변경 뒤 재빌드하도록 안내해줘. PR diff·검증표·병합·배포 SHA를 남겨줘. 가입 요청/메일 도착/확인/로그인/DB 저장/재조회/다른 브라우저 동일계정 복원/A·B 분리/미로그인 차단을 따로 검사해줘. RLS 실패를 UI 숨김·RLS 해제·service_role 노출로 우회하지 마. 원격 변경과 SQL 적용은 수업 단계에서 대상 프로젝트를 확인한 뒤 실행하게 해줘.
학생 확인
solution 안 파일을 저장소 루트에 넣었다면 Root Directory는 루트다. solution 폴더 자체를 저장소에 넣었다면 실제 경로를 지정한다. Vercel이 Next.js 또는 public/을 빌드하도록 설정되어 있으면 고친다. DB 비밀번호나 service_role은 입력하지 않는다.
강사용 설명
4교시 50분은 빌드/Git8·Vercel12·URL/메일10·공개 A/B 검증12·짝 확인/정리8이다. 전날의 PR 절차를 재사용한다. Framework 자동 감지 결과를 검토한다. 환경값은 빌드에 포함되므로 변경 후 재배포한다. 앱 배포와 DB 스키마 적용은 별개이며 Vercel이 SQL을 자동 실행하지 않는다.
Vercel의 실제 Production URL을 Supabase Site URL로 넣고 Redirect URLs에 정확한 공개 루트와 필요한 로컬/Preview 루트를 허용한다. 설정 변경 뒤 새 배포를 만들고 가입메일이 공개 앱으로 돌아오는지 확인한다. 공개 앱에서 추가·수정·검색·완료·로그아웃·새로고침·다른 브라우저 A 재조회·B 차단을 다시 검사한다.
현재 폴더에서 한 줄씩 실행
git switch main
git pull --ff-only origin main
git log --oneline -3
학생 확인
제출: PR URL·main 커밋·배포 커밋·Vercel URL·Supabase 프로젝트 식별자(키 제외)·메일/로그인/DB/권한 체크표. READY는 빌드·배포 상태이며 실제 가입·저장 통과와 다르다. 대기나 미실행은 미확인으로 기록한다.
강사용 설명
Resend SMTP는 Supabase Auth 설정에 보관한다. 앱이 별도 메일을 보내지 않는다면 Resend 키를 Vercel 클라이언트 환경에 넣지 않는다. SNS와 MFA는4일차에 진행하고 오늘은 이메일·DB·배포를 완주한다. 강의자료의 배포 검증과 학생 개인 프로젝트의 실제 통합 검증을 구분한다.
새 수업용 프로젝트에서 setup.sql을 한 번 실행한다. 기존 tasks가 있으면 먼저 구조를 비교한다. 이 SQL은 Supabase의 auth.users·auth.uid()와 anon/authenticated 역할을 전제로 한다.
-- 새 수업용 프로젝트에서 한 번 실행합니다. 기존 tasks가 있으면 먼저 구조를 확인하세요.
begin;
create table public.tasks (
id uuid primary key default gen_random_uuid(),
user_id uuid not null default auth.uid() references auth.users(id) on delete cascade,
title text not null check (char_length(title) between 1 and 60 and title ~ '[^[:space:]]'),
subject text not null default '' check (char_length(subject) <= 20),
due date not null check (due between date '0001-01-01' and date '9999-12-31'),
done boolean not null default false,
created_at timestamptz not null default now()
);
create index tasks_user_due_idx on public.tasks (user_id, due);
alter table public.tasks enable row level security;
revoke all on table public.tasks from public, anon, authenticated;
grant usage on schema public to authenticated;
grant select, insert, delete on table public.tasks to authenticated;
-- 소유자·id·생성시각은 UPDATE로 바꿀 수 없습니다.
grant update (title, subject, due, done) on public.tasks to authenticated;
create policy tasks_read_own on public.tasks for select to authenticated
using ((select auth.uid()) = user_id);
create policy tasks_insert_own on public.tasks for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy tasks_update_own on public.tasks for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
create policy tasks_delete_own on public.tasks for delete to authenticated
using ((select auth.uid()) = user_id);
commit;
관리자 메타데이터 점검
-- 관리자 메타데이터 점검입니다. 아래 성공만으로 A/B 계정 RLS 검사를 대체하지 않습니다.
select relrowsecurity from pg_class where oid = 'public.tasks'::regclass;
select policyname, cmd, roles, qual, with_check from pg_policies
where schemaname = 'public' and tablename = 'tasks';
select grantee, privilege_type from information_schema.role_table_grants
where table_schema = 'public' and table_name = 'tasks' order by grantee, privilege_type;
select grantee, column_name, privilege_type from information_schema.column_privileges
where table_schema = 'public' and table_name = 'tasks' and privilege_type = 'UPDATE';
GRANT는 테이블·열에 접근할 권한, RLS는 그중 자신의 행에 접근할 조건이다. 공개 publishable key만으로 다른 사람의 행을 읽을 수 없도록 둘을 함께 사용한다. UPDATE에 SELECT 정책도 필요하다. 관리자 SQL 조회는 A/B 권한 검사가 아니다.
수업용 Supabase에서 계정 A와 B를 만들어 실제 가입 메일·로그인을 확인해줘. A가 만든 과제를 새로고침과 다른 브라우저의 A 로그인으로 다시 읽고 수정·완료·삭제·되돌리기를 검사해줘. B에서는 A 행 조회0건, A id를 지정한 수정·삭제0건 또는 거절, user_id=A로 위조INSERT 거절, 소유자변경 거절을 Data API 요청으로 검사해줘. 미로그인 요청도 차단되는지 확인해줘. SQL Editor 관리자 조회를 RLS 통과 증거로 쓰지 마. 계정 전환 직후와 늦은 이전 요청 응답에서 A 목록이 B 화면에 나타나지 않는지, 네트워크 실패가 저장 성공으로 표시되지 않는지 확인해줘. 공개 Vercel URL의 배포commit·환경·가입복귀URL도 대조하고 결과를 통과/실패/미확인으로 기록해줘. 이메일·비밀번호·토큰은 보고서에 넣지 마.
B의 로그인된 클라이언트로 A id를 직접 요청한다. 토큰을 복사해 과제물·스크린샷·로그에 넣지 않는다. 코드의 .eq(user_id)는 조회 조건이며 DB RLS를 대신하지 않는다. 각 항목의 통과/실패/미확인과 실제 결과를 ZIP checks.csv에 남긴다.
완성 예제는 분리된 로컬 Supabase에서 가입 확인 대기·Mailpit 수신·확인 후 로그인·회원별 CRUD·A/B 조회/수정/삭제 차단·소유자 위조·익명 차단을 실제 검사했다. Vite 빌드도 확인했다. 학생 개인 클라우드의 실제 수신함·Vercel 앱 연결은 각자의 수업 계정으로 최종 검증해야 한다. 강의 사이트 배포와 학생 앱 배포는 별개다.
과제 앱 Google 로그인과 callback를 같은 과제 체크 앱에서 구현·검증한다. / 산출물: Google 최초·취소·재로그인과 callback 설정표 / 도입8·공통설정10·Google20·짝검증8·정리4분
125
핵심 개념
외부 제공자 callback과 앱 복귀 주소 / OAuth PKCE·Supabase 세션 / Google Client·대상 사용자
126
Google Cloud와 두 종류의 복귀 주소
제공자 callback은 Supabase, 앱 복귀 주소는 허용된 내 앱 루트다. / Google 웹 OAuth Client의 대상·동의·Redirect URI를 맞춘다. / Client Secret은 Supabase에 보관하고 프런트엔드에 넣지 않는다. / 최초 가입·취소·재로그인을 같은 테스트 계정으로 확인한다.
127
u-livet · 이번 실습에 적용할 판단
기록: 카카오·구글·네이버 인증을 순차 보완했다. / 판단: 제공자 인증과 앱의 가입 완료를 분리했다. / 적용: Google 버튼 성공뿐 아니라 callback·세션·취소를 확인한다.
128
실습 절차
아래 단계별 실습을 진행한다.
129
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
130
완료 확인과 오류 대응
새 수업용 개발자 앱과 테스트 사용자를 준비한다. 제공자 callback은 Supabase 화면에서 복사하고, 앱의 복귀 루트는 Supabase Redirect URLs에 등록한다. 두 주소를 서로 바꾸지 않는다. / 기본 로그인에 필요한 정보만 요청한다. 첫 로그인·동의 취소·재로그인을 확인하고 Supabase user.id/identities와 기존 과제를 비교한다. Google 계정의2단계 인증이 Supabase AAL2를 자동 의미하지는 않는다.
같은 과제 앱의 SNS·MFA 실습
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
Google 계정의 자체2단계 인증과 Supabase 서비스 이용자 AAL2를 혼동하지 않는다. 설정 전체는 연결된 원문에서 확인한다.
강사용 설명
1교시50분은 도입8·공통설정10·Google20·짝검증8·정리4다. 개발자 앱 생성·승인·도메인/개인정보 URL은 강사가 사전 준비한다. 수업시간은 코드 연결과 실제 왕복 검증에 쓴다. 완성본은 MFA까지 요구하므로 SNS만 확인할 때는 중간 코드를 사용한다. Supabase가 외부 code·token 처리를 담당하고 Vite 앱은 PKCE 복귀 후 세션을 읽는다. 이 예제는 앱 루트로 돌아오므로 Next.js용 /auth/callback 서버 경로를 그대로 복사하지 않는다. 사용자 대상·테스트 모드·검증 상태에 따라 계정별 결과가 달라질 수 있다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
실습 절차
전날 앱·세션·callback 왕복 정리: 본인3일차 앱에서 이메일 로그인과 과제 저장을 확인한다. 깨끗한 main에서 feature/social-mfa 브랜치를 만든다. 앱→제공자→Supabase callback→앱 루트→세션→내 과제 순서를 그린다. starter는 전날 코드, checkpoint-sns는 SNS 중간 코드, solution은 MFA까지 포함한 완성 코드다.
Google Cloud와 Google 로그인 연결: Google Cloud의 수업 프로젝트에서 Google Auth Platform의 Branding·Audience·Data Access와 테스트 대상 사용자를 확인한다. 웹 OAuth Client의 Authorized redirect URI에 Supabase Google 설정의 Callback URL을 넣는다. Client ID·Secret은 Supabase Google provider에 등록하고 요청1로 버튼을 연결한다.
Codex 구현 요청
3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.
AI 검증·오류 해결 요청
수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.
완료 확인
새 수업용 개발자 앱과 테스트 사용자를 준비한다. 제공자 callback은 Supabase 화면에서 복사하고, 앱의 복귀 루트는 Supabase Redirect URLs에 등록한다. 두 주소를 서로 바꾸지 않는다.
기본 로그인에 필요한 정보만 요청한다. 첫 로그인·동의 취소·재로그인을 확인하고 Supabase user.id/identities와 기존 과제를 비교한다. Google 계정의2단계 인증이 Supabase AAL2를 자동 의미하지는 않는다.
REST API 키·Login Client Secret / 동의·이메일 없는 계정 / Supabase identities와 과제 소유권
135
Kakao 동의와 기존 계정의 소유권
REST API 키·Login Client Secret·Redirect URI를 구분한다. / 필요한 동의 항목과 이메일 미제공 조건을 확인한다. / 같은 이메일이라는 이유로 앱이 과제·관리자 역할을 합치지 않는다. / Supabase identity와 uid를 기준으로 기존 계정 연결 결과를 확인한다.
136
u-livet · 이번 실습에 적용할 판단
기록: SNS 인증 이후 등록된 역할과 가입 완료 상태를 검사했다. / 판단: 외부 계정 인증이 관리자 권한을 자동 부여하지 않는다. / 적용: Kakao 재로그인 뒤에도 본인 과제 소유권이 유지되는지 확인한다.
137
실습 절차
아래 단계별 실습을 진행한다.
138
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
139
완료 확인과 오류 대응
REST API 키는 client_id, Kakao Login Client Secret은 client_secret이다. JavaScript 키·Admin 키를 대신 쓰지 않는다. 제공자 동의 항목은 앱이 실제 필요한 범위로만 요청한다. / 없는 이메일을 만들거나 확인되지 않은 메일을 verified로 표시하지 않는다. 이메일이 같아도 클라이언트에서 계정/역할/과제를 합치지 않는다. Supabase의 확인된 identity 연결 결과를 확인하며, 별개 uid라면 과제도 별개다.
같은 과제 앱의 SNS·MFA 실습
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
실제 제공자의 동의와 메일 없는 계정 검사를 모의 SDK 인자 검사와 구분한다. 역할 복사는 실습에서 자동화하지 않는다.
강사용 설명
2교시50분은 흐름8·Kakao18·가입/identity12·짝검증8·정리4다. 메뉴명은 제공자 업데이트에 따라 바뀔 수 있어 공식 문서와 실제 화면을 함께 확인한다. 키 이름이 비슷해도 용도가 다르다는 점을 실제 설정표로 보여준다. 이메일 필수 연락 서비스는 별도 확인 단계를 설계한다. 네이버 F19의 고급 가입 흐름과 이번 단순 과제 앱을 구분한다. 수동 identity 연결은 로그인된 본인 계정에서 별도 설계하는 확장 과제다. SNS 공급자를 바꾼다고 관리자 역할이 생기지 않는다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
실습 절차
Kakao Developers와 카카오 로그인 설정: Kakao Developers의 수업 앱에서 웹 서비스 정보와 카카오 로그인 활성화를 확인한다. App Settings→App→Platform Key에서 사용할 REST API 키와 Kakao Login Client Secret을 확인·활성화하고, 해당 키의 Kakao Login Redirect URI에 Supabase callback을 등록한다. Supabase Kakao provider에 두 값을 넣고 버튼을 연결한다.
이메일 누락·취소·기존 회원 연결 검사: 카카오 동의 화면을 취소하거나 선택 정보 제공을 거절해 본다. 이메일 정보가 없을 때도 필요한 서비스라면 Supabase Kakao 설정의 Allow users without an email을 확인한다. 이 수업 앱은 연락 이메일 대신 user.id로 과제 소유권을 관리한다. 이메일 계정과 SNS의 identity 연결 여부를 실제 계정에서 확인한다.
Codex 구현 요청
3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.
AI 검증·오류 해결 요청
수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.
완료 확인
REST API 키는 client_id, Kakao Login Client Secret은 client_secret이다. JavaScript 키·Admin 키를 대신 쓰지 않는다. 제공자 동의 항목은 앱이 실제 필요한 범위로만 요청한다.
없는 이메일을 만들거나 확인되지 않은 메일을 verified로 표시하지 않는다. 이메일이 같아도 클라이언트에서 계정/역할/과제를 합치지 않는다. Supabase의 확인된 identity 연결 결과를 확인하며, 별개 uid라면 과제도 별개다.
자주 막히는 지점
redirect_uri_mismatch: 앱 루트와 혼동하지 않기
취소/로그인 실패: 오류 원문·토큰 노출 없이 재시도 안내
Kakao 이메일 없음: 연락 메일이 필요하면 별도 확인 설계
Naver provider 오류: Custom OAuth2 설정 재확인
UserInfo401/404: 정적 Vite/보호 Preview와 구분
다른 uid로 로그인: 기존 과제/역할을 임의 복사하지 않기
TOTP 코드 오류: 최신 코드로 새 challenge
등록 반복: 기존 factor 확인·미완료 등록만 취소
MFA 후 과제0건: RLS/소유자와 빈 목록 원인 구분
AAL1도 접근됨: UI 검사와 DB 강제 모두 수정
인증앱 분실: 관리자 승인 복구, 자동 우회 금지
확인 질문
SNS 이메일이 같으면 앱이 과제와 역할을 합쳐도 되는가?
서버가 검증한 identity 연결과 uid를 확인한다. 클라이언트에서 임의로 과제·역할을 옮기지 않는다.
과제 앱 Naver Custom OAuth와 어댑터를 같은 과제 체크 앱에서 구현·검증한다. / 산출물: custom:naver와 서버 userinfo 어댑터·설정표 / 흐름8·어댑터20·Custom OAuth10·짝검증8·정리4분
143
핵심 개념
Custom OAuth2·custom:naver / 고정 Naver UserInfo와 sub 매핑 / email_optional과 계정 연결
144
Naver: 검증한 id와 가입 완료 상태
custom:naver의 OAuth2 설정과 고정 서버 UserInfo 어댑터를 연결한다. / 서버가 네이버에서 검증한 id만 sub로 변환한다. / 단순 과제 앱의 이메일 없는 가입과 실제 서비스의 연락 메일 확인을 구분한다. / 토큰 오류·취소·최초 가입·재로그인·동일 identity를 검사한다.
145
u-livet · 이번 실습에 적용할 판단
기록: 네이버 사용자 정보 변환과 별도 이메일 확인을 후속 보완했다. / 교훈: 제공자 인증·이메일 수신·앱 가입 완료는 각각 확인해야 한다. / 적용: 과제 앱은 이메일을 합성하지 않고 검증한 네이버 id를 유지한다.
146
실습 절차
아래 단계별 실습을 진행한다.
147
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
148
완료 확인과 오류 대응
UserInfo URL은 Supabase가 호출 가능한 HTTPS 서버 주소다. localhost·Vite 정적 서버·인증 보호된 Preview 주소를 그대로 넣지 않는다. GET 무토큰 호출이401이면 라우트 응답은 확인한 것이며 실제 네이버 로그인을 확인한 것은 아니다. / 브라우저가 임의로 보낸 사용자 JSON을 믿지 않는다. 네이버 response.id는 해당 개발자 앱의 고유 식별자다. Client ID를 바꾸면 같은 개인이어도 연결 결과가 달라질 수 있다. 이메일 자동 확인을 전역으로 해제하지 않는다.
같은 과제 앱의 SNS·MFA 실습
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
F19는 u-livet의 확장 가입 흐름으로 별도 심화다. 기본 과제 앱의 email_optional 경로에 이메일 OTP를 무조건 추가하지 않는다.
강사용 설명
3교시50분은 흐름8·어댑터20·Custom OAuth10·짝검증8·정리4다. 강사는 어댑터 배포와 Custom Provider 사용 가능 여부·한도·테스트 대상을 미리 확인한다. 로컬 Vite는 api 함수를 실행하지 않으므로 Vercel dev 또는 사전 배포한 수업 어댑터로 시험한다. 보호 기능을 끄는 방식으로 Preview 문제를 우회하지 않는다. 어댑터가 토큰을 읽는 이유는 네이버 서버에 확인하기 위해서다. 토큰을 화면·로그·응답에 되돌려주지 않는다. 공식 문서의 PKCE 지원 조건과 실제 제공자 응답을 확인하고, 실패할 때 state/nonce/검증 절차를 임의로 빼지 않는다. 실계정 왕복이 끝나지 않았다면 “설정 완료/모의 통과/실계정 미확인”으로 기록한다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
실습 절차
네이버 Custom OAuth와 서버 어댑터 배포: Naver Developers에 서비스 URL·callback·테스트 계정을 등록한다. Supabase Custom OAuth Provider를 Manual OAuth2·custom:naver로 구성하고 생성 화면의 Callback URL을 네이버에 복사한다. Authorization/Token URL과 Client ID·Secret을 설정한다. checkpoint-sns의 api/와 server/를 포함해 본인 고정 Vercel 수업 앱에 배포하고 UserInfo URL을 /api/naver-userinfo로 지정한다.
네이버 id→sub와 이메일 없는 계정 검증: 요청2로 서버 어댑터의 고정 네이버 프로필 조회와 응답 검증을 구현한다. resultcode=00과 response.id를 확인한 뒤 sub만 반환한다. 이 제공자에 email_optional=true를 설정하고 custom:naver 버튼을 연결한다. 취소·토큰 오류·이메일 없음·재로그인에서 동일 네이버 identity가 유지되는지 확인한다.
Codex 구현 요청
과제 앱에 네이버 로그인을 custom:naver로 추가해줘. 네이버는 OIDC 자동검색이 아닌 Custom OAuth2로 구성하고 Authorization/Token/UserInfo 설정, 네이버 개발자 앱과 Supabase가 표시한 Callback URL의 관계를 설명해줘. 서버의 GET /api/naver-userinfo는 Supabase가 보낸 Bearer 토큰으로 고정 https://openapi.naver.com/v1/nid/me만 조회해줘. redirect 거절·timeout·응답 크기 제한·resultcode와 response.id 검증을 넣고 검증한 앱별 id만 sub로 반환해줘. 요청 body나 임의 JSON을 신뢰하거나 이메일을 합성·email_verified=true로 만들지 마. 수업 앱은 연락 이메일이 필요 없으므로 이 제공자만 email_optional=true로 구성하고 이메일 없는 SNS 계정도 표시해줘. Naver Client Secret은 Supabase 설정에 보관하고 어댑터는 토큰을 저장/로그/응답하지 않게 해줘. 이 서버 함수는 Vite 정적 미리보기에서 실행되지 않는다고 설명하고, 접근 가능한 고정 Vercel 수업 URL에 배포한 뒤 UserInfo URL에 등록하게 해줘. 실패·이메일 누락·취소·재로그인·같은 identity 검증을 포함하고 실계정 미실행과 모의 검사 결과를 구분해줘.
AI 검증·오류 해결 요청
수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.
완료 확인
UserInfo URL은 Supabase가 호출 가능한 HTTPS 서버 주소다. localhost·Vite 정적 서버·인증 보호된 Preview 주소를 그대로 넣지 않는다. GET 무토큰 호출이401이면 라우트 응답은 확인한 것이며 실제 네이버 로그인을 확인한 것은 아니다.
브라우저가 임의로 보낸 사용자 JSON을 믿지 않는다. 네이버 response.id는 해당 개발자 앱의 고유 식별자다. Client ID를 바꾸면 같은 개인이어도 연결 결과가 달라질 수 있다. 이메일 자동 확인을 전역으로 해제하지 않는다.
자주 막히는 지점
redirect_uri_mismatch: 앱 루트와 혼동하지 않기
취소/로그인 실패: 오류 원문·토큰 노출 없이 재시도 안내
Kakao 이메일 없음: 연락 메일이 필요하면 별도 확인 설계
Naver provider 오류: Custom OAuth2 설정 재확인
UserInfo401/404: 정적 Vite/보호 Preview와 구분
다른 uid로 로그인: 기존 과제/역할을 임의 복사하지 않기
TOTP 코드 오류: 최신 코드로 새 challenge
등록 반복: 기존 factor 확인·미완료 등록만 취소
MFA 후 과제0건: RLS/소유자와 빈 목록 원인 구분
AAL1도 접근됨: UI 검사와 DB 강제 모두 수정
인증앱 분실: 관리자 승인 복구, 자동 우회 금지
확인 질문
네이버 프로필을 브라우저 JSON으로 받아 믿어도 되는가?
서버가 Bearer 토큰으로 고정 네이버 API에서 확인한 앱별 id만 sub로 사용한다.
기존 소유자4정책 유지 + AS RESTRICTIVE 추가 / 본인 AAL1:차단 · 본인 AAL2:CRUD 허용 / 다른 계정은 AAL2여도 내 과제 접근 차단 / 재로그인 challenge · 오답 · 공개 URL 검사
151
과제 앱 TOTP MFA와 DB AAL2 강제
과제 앱 TOTP MFA와 DB AAL2 강제를 같은 과제 체크 앱에서 구현·검증한다. / 산출물: TOTP 등록/재인증과 AAL1/AAL2 DB 검증표 / 흐름8·MFA18·DB강제12·짝검증8·정리4분
152
핵심 개념
enroll→challenge/verify→AAL2 / 재로그인·오답·미완료 등록 취소 / 소유자 RLS AND restrictive AAL2
153
MFA 등록만으로 과제가 보호되는가?
TOTP enroll→코드 검증→AAL2의 각 상태를 구분한다. / 6자리 문자열로 앞자리0을 보존하고 QR·키·코드는 즉시 정리한다. / 소유자 RLS AND restrictive AAL2를 실제 DB에서 강제한다. / 재로그인·오답·미완료 취소·본인/타인 접근을 함께 검사한다.
154
u-livet · 이번 실습에 적용할 판단
기록: SNS 후속 인증과 MFA를 순차 보완했다. / 판단: 추가 인증 강도와 업무 역할·데이터 소유권은 별개다. / 적용: 본인 AAL2만 과제에 접근하고 타인은 AAL2여도 차단되게 한다.
155
실습 절차
아래 단계별 실습을 진행한다.
156
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
157
완료 확인과 오류 대응
TOTP 입력은 문자열로 받아 앞자리0을 보존한다. 등록 성공 뒤 QR·키·입력 코드는 화면에서 제거한다. 코드를 자동 제출하지 않고 확인 버튼으로 검증한다. 이 설정은 Supabase 관리자 대시보드 MFA와 다르다. / AS RESTRICTIVE를 빠뜨리지 않는다. MFA 조건과 소유자 조건을 모두 만족해야 한다. 정책 적용 뒤 A/B 모두 MFA가 필요하다. 결과물에는 PR·배포commit·공개URL·제공자별 최초/취소/재로그인·MFA 전후 검사 결과를 기록하되 키·토큰·QR·코드는 넣지 않는다.
같은 과제 앱의 SNS·MFA 실습
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
분실 복구는 본인 확인을 포함한 정식 절차로 별도 운영한다. 인증앱 등록 화면을 추가했다는 이유로 DB 보호 통과라고 하지 않는다.
강사용 설명
4교시50분은 흐름8·MFA18·DB강제12·짝검증8·정리4다. 등록 버튼을 반복해 factor를 만들지 않도록 한다. 수업 화면 공유에는 실제 QR·수동 키·코드를 띄우지 않는다. 확인된 인증앱의 삭제/기기 교체는 정식 관리·복구 흐름의 확장 과제로 다루고 기본 예제는 미완료 등록 취소만 제공한다. AAL2는 추가 인증 강도이며 관리자 권한이 아니다. 화면 숨김과 실제 DB 거절을 각각 확인한다. 인증앱 분실은 신원 확인을 포함한 정식 복구 절차로 안내하고 RLS나 MFA를 꺼서 통과시키지 않는다. SNS 실계정의 승인·동의·callback과 선택 보충 복구 경로는 수업 계정에서 따로 리허설한다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
실습 절차
인증앱 등록→6자리 검증→AAL2: Supabase의 서비스 이용자 Auth MFA에서 TOTP 등록·검증을 활성화한다. 요청3으로 MFA 화면을 추가한다. 수업용 가상 계정으로 로그인해 등록 시작을 누르고 본인 인증앱에서 QR 또는 수동 키를 등록한다. 먼저 틀린 코드로 거절을 확인한 뒤 최신6자리 코드로 검증한다. 등록만 된 상태와 AAL2 세션을 구분한다.
DB AAL2 강제·재로그인·Vercel 최종 검증: 가상 계정의 AAL2 성공을 먼저 확인한 뒤 본인 수업 DB에서 sql/mfa-required.sql을 실행한다. 기존 user_id 소유자 정책을 유지한다. 로그아웃·재로그인하면 AAL1과 추가 인증 화면이 나오는지 검사한다. Data API에서 AAL1은 차단되고 AAL2 본인만 CRUD가 가능한지 A/B 계정으로 확인한다. Git PR 검토·병합과 Vercel 배포 후 같은 검사를 반복한다.
Codex 구현 요청
현재 앱에 Supabase TOTP MFA를 추가해줘. final 앱은 이메일/SNS 로그인 후 모두 AAL2를 확인해야 tasks를 열게 해줘. getAuthenticatorAssuranceLevel과 listFactors로 미등록·등록대기·기등록 AAL1·AAL2를 구분하고 enroll→QR/수동 키→challengeAndVerify→AAL2 확인을 연결해줘. 등록은 사용자 버튼으로 한 번만 시작하고 TOTP는 text/inputmode numeric/one-time-code/6자리 문자열로 받아 앞자리0을 보존해줘. SDK QR은 img로 표시하고 원본SVG를 innerHTML로 삽입하지 마. QR·비밀키·코드는 로그·URL·영구 저장·과제물에 남기지 않고 완료/취소/계정전환 시 메모리와 DOM에서 지워줘. 같은 세션의 중복 이벤트는 폼을 지우지 않게 하고 새 세션/계정 전환은 이전 요청을 무시해줘. 기존 user_id RLS/GRANT는 보존하고 tasks에 AS RESTRICTIVE FOR ALL TO authenticated, USING과 WITH CHECK로 aal2를 요구하는 SQL을 별도 파일로 만들어줘. 가상 계정의 TOTP 성공을 먼저 확인한 뒤 새 수업 DB에 적용하게 해줘. 틀린/지난 코드·재로그인 challenge·AAL1 차단/AAL2 본인 허용·타인 차단·미완료 등록 취소·분실 시 정식 복구 안내를 포함해줘. 운영 DB/기존 계정은 변경하지 마.
AI 검증·오류 해결 요청
수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.
완료 확인
TOTP 입력은 문자열로 받아 앞자리0을 보존한다. 등록 성공 뒤 QR·키·입력 코드는 화면에서 제거한다. 코드를 자동 제출하지 않고 확인 버튼으로 검증한다. 이 설정은 Supabase 관리자 대시보드 MFA와 다르다.
AS RESTRICTIVE를 빠뜨리지 않는다. MFA 조건과 소유자 조건을 모두 만족해야 한다. 정책 적용 뒤 A/B 모두 MFA가 필요하다. 결과물에는 PR·배포commit·공개URL·제공자별 최초/취소/재로그인·MFA 전후 검사 결과를 기록하되 키·토큰·QR·코드는 넣지 않는다.
자주 막히는 지점
redirect_uri_mismatch: 앱 루트와 혼동하지 않기
취소/로그인 실패: 오류 원문·토큰 노출 없이 재시도 안내
Kakao 이메일 없음: 연락 메일이 필요하면 별도 확인 설계
Naver provider 오류: Custom OAuth2 설정 재확인
UserInfo401/404: 정적 Vite/보호 Preview와 구분
다른 uid로 로그인: 기존 과제/역할을 임의 복사하지 않기
TOTP 코드 오류: 최신 코드로 새 challenge
등록 반복: 기존 factor 확인·미완료 등록만 취소
MFA 후 과제0건: RLS/소유자와 빈 목록 원인 구분
AAL1도 접근됨: UI 검사와 DB 강제 모두 수정
인증앱 분실: 관리자 승인 복구, 자동 우회 금지
확인 질문
MFA 등록 화면만 추가하면 DB도 보호되는가?
TOTP 검증 후 AAL2를 확인하고 소유자 RLS와 restrictive AAL2 정책을 실제 API로 검사해야 한다.
수업용 Google/Kakao/Naver 개발자 앱, 대상 테스트 사용자, 서비스/개인정보 URL·필요 동의·승인 상태와 고정 callback을 미리 준비한다. 네이버 서버 어댑터는 Supabase가 접근할 수 있는 고정 HTTPS 수업 주소에 배포한다. 각 학생은 자기 Supabase·개발자 앱 설정을 사용한다. starter는3일차 DB 앱, checkpoint-sns는SNS까지만 구현한 중간 지점, solution은MFA까지 요구하는 완성본이다. TOTP와 AAL2를 먼저 확인한 뒤 수업 DB에 MFA SQL을 적용한다.
실제 Client Secret·Admin 키·토큰·QR·수동 키·인증코드는 ZIP이나 과제물에 넣지 않는다. 실계정 OAuth를 완료하지 않았다면 설정/모의검사/실계정 미확인을 구분한다. 기존 운영 서비스의 인증 설정은 이 실습에서 바꾸지 않는다.
3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.
2. Naver Custom OAuth·서버 검증
과제 앱에 네이버 로그인을 custom:naver로 추가해줘. 네이버는 OIDC 자동검색이 아닌 Custom OAuth2로 구성하고 Authorization/Token/UserInfo 설정, 네이버 개발자 앱과 Supabase가 표시한 Callback URL의 관계를 설명해줘. 서버의 GET /api/naver-userinfo는 Supabase가 보낸 Bearer 토큰으로 고정 https://openapi.naver.com/v1/nid/me만 조회해줘. redirect 거절·timeout·응답 크기 제한·resultcode와 response.id 검증을 넣고 검증한 앱별 id만 sub로 반환해줘. 요청 body나 임의 JSON을 신뢰하거나 이메일을 합성·email_verified=true로 만들지 마. 수업 앱은 연락 이메일이 필요 없으므로 이 제공자만 email_optional=true로 구성하고 이메일 없는 SNS 계정도 표시해줘. Naver Client Secret은 Supabase 설정에 보관하고 어댑터는 토큰을 저장/로그/응답하지 않게 해줘. 이 서버 함수는 Vite 정적 미리보기에서 실행되지 않는다고 설명하고, 접근 가능한 고정 Vercel 수업 URL에 배포한 뒤 UserInfo URL에 등록하게 해줘. 실패·이메일 누락·취소·재로그인·같은 identity 검증을 포함하고 실계정 미실행과 모의 검사 결과를 구분해줘.
3. TOTP MFA·DB AAL2 강제
현재 앱에 Supabase TOTP MFA를 추가해줘. final 앱은 이메일/SNS 로그인 후 모두 AAL2를 확인해야 tasks를 열게 해줘. getAuthenticatorAssuranceLevel과 listFactors로 미등록·등록대기·기등록 AAL1·AAL2를 구분하고 enroll→QR/수동 키→challengeAndVerify→AAL2 확인을 연결해줘. 등록은 사용자 버튼으로 한 번만 시작하고 TOTP는 text/inputmode numeric/one-time-code/6자리 문자열로 받아 앞자리0을 보존해줘. SDK QR은 img로 표시하고 원본SVG를 innerHTML로 삽입하지 마. QR·비밀키·코드는 로그·URL·영구 저장·과제물에 남기지 않고 완료/취소/계정전환 시 메모리와 DOM에서 지워줘. 같은 세션의 중복 이벤트는 폼을 지우지 않게 하고 새 세션/계정 전환은 이전 요청을 무시해줘. 기존 user_id RLS/GRANT는 보존하고 tasks에 AS RESTRICTIVE FOR ALL TO authenticated, USING과 WITH CHECK로 aal2를 요구하는 SQL을 별도 파일로 만들어줘. 가상 계정의 TOTP 성공을 먼저 확인한 뒤 새 수업 DB에 적용하게 해줘. 틀린/지난 코드·재로그인 challenge·AAL1 차단/AAL2 본인 허용·타인 차단·미완료 등록 취소·분실 시 정식 복구 안내를 포함해줘. 운영 DB/기존 계정은 변경하지 마.
추가: SNS·MFA·권한 통합 검증
수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.
본인3일차 앱에서 이메일 로그인과 과제 저장을 확인한다. 깨끗한 main에서 feature/social-mfa 브랜치를 만든다. 앱→제공자→Supabase callback→앱 루트→세션→내 과제 순서를 그린다. starter는 전날 코드, checkpoint-sns는 SNS 중간 코드, solution은 MFA까지 포함한 완성 코드다.
본인 저장소에서 한 줄씩 실행
git status -sb
git switch main
git pull --ff-only origin main
git switch -c feature/social-mfa
npm ci
npm run dev
학생 확인
새 수업용 개발자 앱과 테스트 사용자를 준비한다. 제공자 callback은 Supabase 화면에서 복사하고, 앱의 복귀 루트는 Supabase Redirect URLs에 등록한다. 두 주소를 서로 바꾸지 않는다.
강사용 설명
1교시50분은 도입8·공통설정10·Google20·짝검증8·정리4다. 개발자 앱 생성·승인·도메인/개인정보 URL은 강사가 사전 준비한다. 수업시간은 코드 연결과 실제 왕복 검증에 쓴다. 완성본은 MFA까지 요구하므로 SNS만 확인할 때는 중간 코드를 사용한다.
Google Cloud의 수업 프로젝트에서 Google Auth Platform의 Branding·Audience·Data Access와 테스트 대상 사용자를 확인한다. 웹 OAuth Client의 Authorized redirect URI에 Supabase Google 설정의 Callback URL을 넣는다. Client ID·Secret은 Supabase Google provider에 등록하고 요청1로 버튼을 연결한다.
이 단계의 Codex 요청
3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.
학생 확인
기본 로그인에 필요한 정보만 요청한다. 첫 로그인·동의 취소·재로그인을 확인하고 Supabase user.id/identities와 기존 과제를 비교한다. Google 계정의2단계 인증이 Supabase AAL2를 자동 의미하지는 않는다.
강사용 설명
Supabase가 외부 code·token 처리를 담당하고 Vite 앱은 PKCE 복귀 후 세션을 읽는다. 이 예제는 앱 루트로 돌아오므로 Next.js용 /auth/callback 서버 경로를 그대로 복사하지 않는다. 사용자 대상·테스트 모드·검증 상태에 따라 계정별 결과가 달라질 수 있다.
Kakao Developers의 수업 앱에서 웹 서비스 정보와 카카오 로그인 활성화를 확인한다. App Settings→App→Platform Key에서 사용할 REST API 키와 Kakao Login Client Secret을 확인·활성화하고, 해당 키의 Kakao Login Redirect URI에 Supabase callback을 등록한다. Supabase Kakao provider에 두 값을 넣고 버튼을 연결한다.
학생 확인
REST API 키는 client_id, Kakao Login Client Secret은 client_secret이다. JavaScript 키·Admin 키를 대신 쓰지 않는다. 제공자 동의 항목은 앱이 실제 필요한 범위로만 요청한다.
강사용 설명
2교시50분은 흐름8·Kakao18·가입/identity12·짝검증8·정리4다. 메뉴명은 제공자 업데이트에 따라 바뀔 수 있어 공식 문서와 실제 화면을 함께 확인한다. 키 이름이 비슷해도 용도가 다르다는 점을 실제 설정표로 보여준다.
카카오 동의 화면을 취소하거나 선택 정보 제공을 거절해 본다. 이메일 정보가 없을 때도 필요한 서비스라면 Supabase Kakao 설정의 Allow users without an email을 확인한다. 이 수업 앱은 연락 이메일 대신 user.id로 과제 소유권을 관리한다. 이메일 계정과 SNS의 identity 연결 여부를 실제 계정에서 확인한다.
학생 확인
없는 이메일을 만들거나 확인되지 않은 메일을 verified로 표시하지 않는다. 이메일이 같아도 클라이언트에서 계정/역할/과제를 합치지 않는다. Supabase의 확인된 identity 연결 결과를 확인하며, 별개 uid라면 과제도 별개다.
강사용 설명
이메일 필수 연락 서비스는 별도 확인 단계를 설계한다. 네이버 F19의 고급 가입 흐름과 이번 단순 과제 앱을 구분한다. 수동 identity 연결은 로그인된 본인 계정에서 별도 설계하는 확장 과제다. SNS 공급자를 바꾼다고 관리자 역할이 생기지 않는다.
Naver Developers에 서비스 URL·callback·테스트 계정을 등록한다. Supabase Custom OAuth Provider를 Manual OAuth2·custom:naver로 구성하고 생성 화면의 Callback URL을 네이버에 복사한다. Authorization/Token URL과 Client ID·Secret을 설정한다. checkpoint-sns의 api/와 server/를 포함해 본인 고정 Vercel 수업 앱에 배포하고 UserInfo URL을 /api/naver-userinfo로 지정한다.
학생 확인
UserInfo URL은 Supabase가 호출 가능한 HTTPS 서버 주소다. localhost·Vite 정적 서버·인증 보호된 Preview 주소를 그대로 넣지 않는다. GET 무토큰 호출이401이면 라우트 응답은 확인한 것이며 실제 네이버 로그인을 확인한 것은 아니다.
강사용 설명
3교시50분은 흐름8·어댑터20·Custom OAuth10·짝검증8·정리4다. 강사는 어댑터 배포와 Custom Provider 사용 가능 여부·한도·테스트 대상을 미리 확인한다. 로컬 Vite는 api 함수를 실행하지 않으므로 Vercel dev 또는 사전 배포한 수업 어댑터로 시험한다. 보호 기능을 끄는 방식으로 Preview 문제를 우회하지 않는다.
요청2로 서버 어댑터의 고정 네이버 프로필 조회와 응답 검증을 구현한다. resultcode=00과 response.id를 확인한 뒤 sub만 반환한다. 이 제공자에 email_optional=true를 설정하고 custom:naver 버튼을 연결한다. 취소·토큰 오류·이메일 없음·재로그인에서 동일 네이버 identity가 유지되는지 확인한다.
이 단계의 Codex 요청
과제 앱에 네이버 로그인을 custom:naver로 추가해줘. 네이버는 OIDC 자동검색이 아닌 Custom OAuth2로 구성하고 Authorization/Token/UserInfo 설정, 네이버 개발자 앱과 Supabase가 표시한 Callback URL의 관계를 설명해줘. 서버의 GET /api/naver-userinfo는 Supabase가 보낸 Bearer 토큰으로 고정 https://openapi.naver.com/v1/nid/me만 조회해줘. redirect 거절·timeout·응답 크기 제한·resultcode와 response.id 검증을 넣고 검증한 앱별 id만 sub로 반환해줘. 요청 body나 임의 JSON을 신뢰하거나 이메일을 합성·email_verified=true로 만들지 마. 수업 앱은 연락 이메일이 필요 없으므로 이 제공자만 email_optional=true로 구성하고 이메일 없는 SNS 계정도 표시해줘. Naver Client Secret은 Supabase 설정에 보관하고 어댑터는 토큰을 저장/로그/응답하지 않게 해줘. 이 서버 함수는 Vite 정적 미리보기에서 실행되지 않는다고 설명하고, 접근 가능한 고정 Vercel 수업 URL에 배포한 뒤 UserInfo URL에 등록하게 해줘. 실패·이메일 누락·취소·재로그인·같은 identity 검증을 포함하고 실계정 미실행과 모의 검사 결과를 구분해줘.
학생 확인
브라우저가 임의로 보낸 사용자 JSON을 믿지 않는다. 네이버 response.id는 해당 개발자 앱의 고유 식별자다. Client ID를 바꾸면 같은 개인이어도 연결 결과가 달라질 수 있다. 이메일 자동 확인을 전역으로 해제하지 않는다.
강사용 설명
어댑터가 토큰을 읽는 이유는 네이버 서버에 확인하기 위해서다. 토큰을 화면·로그·응답에 되돌려주지 않는다. 공식 문서의 PKCE 지원 조건과 실제 제공자 응답을 확인하고, 실패할 때 state/nonce/검증 절차를 임의로 빼지 않는다. 실계정 왕복이 끝나지 않았다면 “설정 완료/모의 통과/실계정 미확인”으로 기록한다.
Supabase의 서비스 이용자 Auth MFA에서 TOTP 등록·검증을 활성화한다. 요청3으로 MFA 화면을 추가한다. 수업용 가상 계정으로 로그인해 등록 시작을 누르고 본인 인증앱에서 QR 또는 수동 키를 등록한다. 먼저 틀린 코드로 거절을 확인한 뒤 최신6자리 코드로 검증한다. 등록만 된 상태와 AAL2 세션을 구분한다.
이 단계의 Codex 요청
현재 앱에 Supabase TOTP MFA를 추가해줘. final 앱은 이메일/SNS 로그인 후 모두 AAL2를 확인해야 tasks를 열게 해줘. getAuthenticatorAssuranceLevel과 listFactors로 미등록·등록대기·기등록 AAL1·AAL2를 구분하고 enroll→QR/수동 키→challengeAndVerify→AAL2 확인을 연결해줘. 등록은 사용자 버튼으로 한 번만 시작하고 TOTP는 text/inputmode numeric/one-time-code/6자리 문자열로 받아 앞자리0을 보존해줘. SDK QR은 img로 표시하고 원본SVG를 innerHTML로 삽입하지 마. QR·비밀키·코드는 로그·URL·영구 저장·과제물에 남기지 않고 완료/취소/계정전환 시 메모리와 DOM에서 지워줘. 같은 세션의 중복 이벤트는 폼을 지우지 않게 하고 새 세션/계정 전환은 이전 요청을 무시해줘. 기존 user_id RLS/GRANT는 보존하고 tasks에 AS RESTRICTIVE FOR ALL TO authenticated, USING과 WITH CHECK로 aal2를 요구하는 SQL을 별도 파일로 만들어줘. 가상 계정의 TOTP 성공을 먼저 확인한 뒤 새 수업 DB에 적용하게 해줘. 틀린/지난 코드·재로그인 challenge·AAL1 차단/AAL2 본인 허용·타인 차단·미완료 등록 취소·분실 시 정식 복구 안내를 포함해줘. 운영 DB/기존 계정은 변경하지 마.
학생 확인
TOTP 입력은 문자열로 받아 앞자리0을 보존한다. 등록 성공 뒤 QR·키·입력 코드는 화면에서 제거한다. 코드를 자동 제출하지 않고 확인 버튼으로 검증한다. 이 설정은 Supabase 관리자 대시보드 MFA와 다르다.
강사용 설명
4교시50분은 흐름8·MFA18·DB강제12·짝검증8·정리4다. 등록 버튼을 반복해 factor를 만들지 않도록 한다. 수업 화면 공유에는 실제 QR·수동 키·코드를 띄우지 않는다. 확인된 인증앱의 삭제/기기 교체는 정식 관리·복구 흐름의 확장 과제로 다루고 기본 예제는 미완료 등록 취소만 제공한다.
가상 계정의 AAL2 성공을 먼저 확인한 뒤 본인 수업 DB에서 sql/mfa-required.sql을 실행한다. 기존 user_id 소유자 정책을 유지한다. 로그아웃·재로그인하면 AAL1과 추가 인증 화면이 나오는지 검사한다. Data API에서 AAL1은 차단되고 AAL2 본인만 CRUD가 가능한지 A/B 계정으로 확인한다. Git PR 검토·병합과 Vercel 배포 후 같은 검사를 반복한다.
본인 저장소에서 한 줄씩 실행
npm run build
git status -sb
git add index.html styles.css src api server sql package.json package-lock.json README.md
git diff --cached
git commit -m "Add social login and TOTP MFA"
git push -u origin feature/social-mfa
학생 확인
AS RESTRICTIVE를 빠뜨리지 않는다. MFA 조건과 소유자 조건을 모두 만족해야 한다. 정책 적용 뒤 A/B 모두 MFA가 필요하다. 결과물에는 PR·배포commit·공개URL·제공자별 최초/취소/재로그인·MFA 전후 검사 결과를 기록하되 키·토큰·QR·코드는 넣지 않는다.
강사용 설명
AAL2는 추가 인증 강도이며 관리자 권한이 아니다. 화면 숨김과 실제 DB 거절을 각각 확인한다. 인증앱 분실은 신원 확인을 포함한 정식 복구 절차로 안내하고 RLS나 MFA를 꺼서 통과시키지 않는다. SNS 실계정의 승인·동의·callback과 선택 보충 복구 경로는 수업 계정에서 따로 리허설한다.
REST API 키 + 활성화된 Login Client Secret → Supabase Kakao
사용할 REST 키의 Redirect URI에 Supabase callback
Naver
custom:naver
Naver Developers + Supabase Custom OAuth2
Client ID + Secret → Supabase Custom Provider
Custom Provider 생성 화면의 Callback URL 원문
주소/설정
등록 위치
제공자 Callback URL
Supabase 각 제공자 화면에 표시된 주소를 원문 복사해 Google/Kakao/Naver에 등록
앱 복귀 루트
Supabase Site URL / Redirect URLs에 실제 로컬·고정 Production 루트 허용
Custom Provider callback
생성 화면의 read-only Callback URL 사용. 다른 제공자 주소를 임의 복사하지 않음
Naver UserInfo URL
Supabase Custom OAuth2에 접근 가능한 고정 HTTPS /api/naver-userinfo 등록
Vercel 프로젝트
기존 Vite / npm run build / dist, 공개환경2개 유지. api/와 server/도 소스에 포함
await supabase.auth.signInWithOAuth({
provider: 'google', // 'kakao' 또는 'custom:naver'
options: { redirectTo: new URL('/', location.origin).href }
})
이 예제는 단일 Vite 앱의 루트 복귀와 SDK의 PKCE 처리를 사용한다. Next.js SSR용 /auth/callback 코드를 그대로 추가하지 않는다. 이메일·SNS 로그인 모두 Supabase 세션으로 과제 DB에 접근한다. 제공자 Secret은 Supabase 설정에만 보관한다.
Supabase는 서버 어댑터에 Bearer 토큰으로 요청한다. 어댑터는 고정 네이버 API에서 받은 정상 응답만 sub로 변환한다. 브라우저의 임의 사용자 JSON이나 이메일 일치만으로 계정을 믿지 않는다. 이 단순 과제 앱은 연락 이메일을 요구하지 않으므로 이메일을 반환하지 않는다. 연락 메일이 필요한 실제 서비스의 별도 확인 흐름은 F19와 구분한다.
서버의 프로필 검증·변환
export class ProfileError extends Error { constructor(status){super('profile verification failed');this.status=status;} }
export function toNaverClaims(body) {
const id=body?.response?.id;
if(body?.resultcode!=='00'||typeof id!=='string'||!id.trim()||id.length>255)throw new ProfileError(502);
// Naver의 검증된 앱별 고유 id만 사용합니다. 확인되지 않은 이메일을 연결 근거로 삼지 않습니다.
return {sub:id};
}
export async function fetchNaverClaims(authorization,fetcher=fetch) {
if(typeof authorization!=='string'||authorization.length>8192||!/^Bearer [A-Za-z0-9._~+\/-]+=*$/i.test(authorization))throw new ProfileError(401);
let response;
try {response=await fetcher('https://openapi.naver.com/v1/nid/me',{
method:'GET',headers:{Authorization:authorization,Accept:'application/json'},redirect:'error',signal:AbortSignal.timeout(5000)
});}catch{throw new ProfileError(502);}
if(!response.ok)throw new ProfileError([401,403].includes(response.status)?401:502);
const reader=response.body?.getReader();if(!reader)throw new ProfileError(502);
const decoder=new TextDecoder();let length=0,text='';
try{while(true){const {done,value}=await reader.read();if(done)break;length+=value.byteLength;if(length>65536){await reader.cancel();throw new ProfileError(502);}text+=decoder.decode(value,{stream:true});}text+=decoder.decode();return toNaverClaims(JSON.parse(text));}
catch{throw new ProfileError(502);}
}
로컬 npm run dev는 Vite 정적 프런트엔드만 실행한다. 서버 함수는 Vercel dev 또는 사전 배포한 고정 수업 URL에서 검사한다. 인증 보호된 Preview 때문에 Supabase 요청이 막히면 보호를 해제해 우회하지 않고, 해당 용도로 준비된 수업 서버 주소를 사용한다. 무토큰401은 라우트 검사 결과이며 실제 SNS 로그인 성공은 별도다.
-- 3일차 tasks 소유자 정책·GRANT를 유지합니다. 새 수업용 DB에서만 실행하세요.
-- 먼저 가상 계정의 TOTP 등록·AAL2를 확인하세요. 적용 후 모든 사용자에게 MFA가 필요합니다.
begin;
create policy tasks_require_aal2 on public.tasks
as restrictive for all to authenticated
using (((select auth.jwt())->>'aal') = 'aal2')
with check (((select auth.jwt())->>'aal') = 'aal2');
commit;
-- 정책 확인 (관리자 메타데이터 점검이며 실제 AAL1/AAL2 API 검사를 대신하지 않습니다.)
select policyname, permissive, cmd, qual, with_check
from pg_policies where schemaname='public' and tablename='tasks';
기존 tasks 소유자 정책4개와 GRANT는 유지한다. permissive 소유자 정책과 restrictive AAL2 조건을 함께 만족해야 한다. AS RESTRICTIVE를 빼고 새 permissive 정책으로 만들면 기존 허용 정책과 OR가 되어 의도대로 MFA를 강제하지 못할 수 있다. 본인 TOTP 등록 성공을 확인한 뒤 새 수업용 DB에만 적용한다.
재로그인·복구·비밀 정리
로그아웃 후 다시 이메일/SNS로 로그인하면 AAL1에서 인증앱 challenge를 거친다.
코드는 type=text, inputmode=numeric, autocomplete=one-time-code와6자리 문자열로 받아 앞자리0을 유지한다.
QR·수동 키·코드는 등록 완료/취소/계정 전환 시 제거한다. 캡처·로그·URL·영구 브라우저 저장에 남기지 않는다.
앱은 미완료 등록 취소만 기본 제공한다. 확인된 인증앱의 교체·분실은 본인 확인을 포함한 정식 기기 관리/복구 절차가 필요하다.
AAL2는 추가 인증 강도다. 관리자 역할이나 다른 사용자 과제 접근 권한을 부여하지 않는다.
수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.
ZIP의 checks.csv에 통과/실패/미확인과 실제 결과·확인자를 남긴다. 공급자별 설정 완료, 로컬/모의 검사, 실제 제공자 동의·callback 성공을 분리한다. JWT·TOTP 비밀·토큰은 보고서에 넣지 않는다.
완성 예제는 격리된 로컬 Supabase에서 실제 TOTP 등록·잘못된/지난 코드 거절·AAL2·재로그인·본인 AAL1 차단/본인 AAL2 허용/타인 거절을 검사했다. 세 SNS 버튼의 SDK 인자와 네이버 어댑터는 모의 입력으로 검증했다. 실제 Google/Kakao/Naver 개발자 앱의 승인·동의·callback은 각 수업 계정에서 최종 확인해야 한다. 강의 사이트 배포와 학생 앱의 실제 SNS 연결은 별개다.
쉼표·큰따옴표·줄바꿈이 있어도4열을 유지 / 위험한 자유 입력 앞 탭 · DB 원본은 유지 / 스프레드시트 열람용 · 원본 복원 백업 아님 / 페이지 실패·상한 초과는 부분 파일을 만들지 않음
162
과제 앱 본인 과제 CSV 내보내기
과제 앱 본인 과제 CSV 내보내기를 같은 앱에서 실행하고 근거를 남긴다. / 산출물: CSV 완성 앱·형식/경계 검사 / 목표5·복원8·구현22·경계검증11·정리4분
163
핵심 개념
본인 전체 DB 재조회·페이지 처리 / CSV 인용·BOM·수식 위험 변환 / 다운로드 취소·상한·동시 편집 한계
164
내보내기는 다운로드 뒤에도 검증한다
현재 검색 배열 대신 본인 전체 과제를 DB에서 새 조회한다. / 한글·인용·줄바꿈·수식 위험 입력과 열 범위를 검사한다. / 빈 목록·페이지 오류·계정 전환에서 파일 생성 결과를 확인한다. / 스프레드시트 열람용 CSV와 원본 복원 백업의 용도를 구분한다.
165
kysong · 이번 실습에 적용할 판단
기록: Excel·HWPX 내보내기를 업무 결과물로 확장했다. / 판단: 파일 생성 성공 이후 실제 열기·수정 가능 여부도 검사했다. / 적용: 과제 CSV도 내려받아 원본 건수·열·문자와 비교한다.
166
실습 절차
아래 단계별 실습을 진행한다.
167
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
168
완료 확인과 오류 대응
미커밋 변경을 먼저 정리하고 잘못된 저장소에서 실행하지 않는다. 환경표에 URL·브랜치·DB 이름만 적는다. 두 환경은 같은 DB를 가리키지 않는다. / 한글·쉼표·큰따옴표·줄바꿈·=1+2·전각=1+2와 빈 목록을 검사한다. 위험한 자유 입력 앞의 탭은 열람용 변환이며 DB 원본은 유지된다. 토큰·이메일·uid가 CSV에 없다.
같은 과제 앱의 최종 릴리스 실습
M01~M08과 E01~E08을 사용한다. CSV·계정별 권한·staging→main·발표를 같은 앱에서 완주한다.
기존 파일 업무 사례를5일차 CSV 검증 기준으로 가져온다. 원본 보존과 열람용 변환의 차이를 실제 예시로 설명한다.
강사용 설명
1교시50분은 목표5·복원8·CSV구현22·경계검증11·정리4다. 두 Supabase 프로젝트/키 준비가 지연되지 않게 강사가 별도 수업 프로젝트를 미리 확보한다. 계정 무료 프로젝트 한도를 수업 전에 확인하고 필요하면 강사 제공 환경으로 진행한다. 실제 프로젝트의 운영 DB를 재사용하지 않는다. CSV 인용은 형식 보존, 수식 위험 변환은 별도 보안 처리다. 탭 접두어는 Excel 열람의 위험을 줄이지만 모든 스프레드시트와 재저장에 대한 보장은 아니다. 코드 검증과 실제 교실 Excel/Sheets에서 텍스트 가져오기 검증을 나눠 기록한다. UI 입력이 trim하는 경우 줄바꿈/선행 제어문자 검사는 코드 테스트로 보충한다. E01~E08과 M01~M08의 교재·시작/완성 코드를 사용한다.
실습 절차
전날 앱 복원과 두 환경 준비: 4일차 완성 앱이 있는 본인 clone 폴더를 연다. ZIP의 starter는 비교용이며 기존 .git과 .env.local을 덮어쓰지 않는다. 강사는 staging 브랜치와 수업용 staging/production Supabase 두 환경을 사전 준비한다. 학생은 main의 최신 이력에서 feature/csv-release를 만들고 로컬 가입·로그인·MFA·과제 CRUD부터 확인한다.
본인 전체 과제를 CSV로 내보내기: 요청1을 Codex에 입력하고 변경 파일을 검토한다. solution의 csv-export.js·task-store.js·main.js·HTML/CSS와 비교한다. 추가 인증 후 내 과제 CSV 내보내기를 누른다. 검색 결과가 1건이어도 본인 전체 DB 과제가 들어가는지 확인한다. 내려받은 파일은 스프레드시트에서 텍스트로 가져오고 한글·열 이름·완료 상태를 확인한다.
Codex 구현 요청
4일차 과제 체크 앱의 이메일/SNS 로그인, TOTP MFA, 소유자 RLS를 유지하고 CSV 내보내기를 추가해줘.
먼저 현재 파일·DB 정책·세션 전환 처리를 읽고 변경할 파일과 검증 기준을 제시해줘.
검색 화면의 배열 대신 로그인한 본인의 과제 전체를 DB에서 새로 조회해. getUser의 uid와 currentLevel=aal2를 확인하고 user_id 필터와 DB의 소유자 RLS AND restrictive AAL2를 모두 유지해.
500행씩 created_at,id 순으로 페이지 조회하고 exact count와 중복/누락을 검사해. 10,000행 초과·중간 오류·건수 변경은 부분 파일 없이 실패시켜. 동시 편집의 일관된 스냅샷을 보장한다고 설명하지 마.
열은 과제명/과목/마감일/완료만. UTF-8 BOM, CRLF, 모든 셀 큰따옴표, 내부 따옴표 두 번 처리. 수식 위험 시작문자(공백 뒤 = + - @와 전각 변형, 탭/개행)에는 quoted 셀 안에 탭을 앞붙여 스프레드시트 열람 위험을 줄이고 원본 DB는 바꾸지 마. 모든 앱/재저장에 안전하다거나 원본 복원 백업이라고 설명하지 마.
조회 중 progress와 aria-busy, 중복 실행 방지, 완료/오류 알림, Blob URL 해제를 구현해. 계정/토큰 변경·로그아웃은 AbortController와 epoch 확인으로 취소하고 늦은 A 응답을 B 화면에서 다운로드하지 않게 해.
빈 목록·한글·쉼표·따옴표·줄바꿈·수식 위험·1,000행 초과·중간 실패·계정 전환을 검증하고 npm run build 결과와 미확인 범위를 알려줘. 비밀값을 출력하거나 RLS/MFA를 끄지 마.
AI 검증·오류 해결 요청
내 과제 앱의 최종 검증을 실행 가능한 범위에서 진행하고 checks.csv에 근거를 남겨줘.
가상 A/B 계정, 익명, AAL1과 AAL2로 직접 Data API를 검사해. 본인 CRUD 허용, 타인 조회/수정/삭제의 0행 또는 거절과 원본 불변, user_id 위조 추가/변경 거절을 확인해. UI가 숨겨졌다는 이유로 권한 검사를 통과시키지 마.
CSV가 본인 전체 DB 과제만 새 조회하는지, 검색 결과와 무관한지, 한글/인용/CRLF/위험 입력/빈 목록/1,203행을 처리하는지 검사해. AAL1 내보내기 거절, 페이지 실패와 계정 전환에서 다운로드 0회를 확인해.
비밀은 값 대신 존재 여부와 변수 이름만 보고해. git diff --cached, .gitignore, 번들에 secret/service_role/SMTP/OAuth secret이 없는지 점검해. publishable key는 공개 식별값이며 RLS를 대신하지 않음을 명시해.
빌드·PR·배포상태와 실제 URL 기능 검사를 분리해. 로컬 통과, 모의 통과, staging 실제, Production 실제, 미실행을 구분해 결과/시각/commit/검증자를 기록해. 실패·미확인 항목이 남으면 출시 완료라고 쓰지 마.
완료 확인
미커밋 변경을 먼저 정리하고 잘못된 저장소에서 실행하지 않는다. 환경표에 URL·브랜치·DB 이름만 적는다. 두 환경은 같은 DB를 가리키지 않는다.
한글·쉼표·큰따옴표·줄바꿈·=1+2·전각=1+2와 빈 목록을 검사한다. 위험한 자유 입력 앞의 탭은 열람용 변환이며 DB 원본은 유지된다. 토큰·이메일·uid가 CSV에 없다.
자주 막히는 지점
CSV 한글 깨짐: UTF-8 텍스트로 가져오기
열/행이 나뉨: 줄바꿈 있는 셀을 파서/스프레드시트로 확인
검색과 건수 다름: 화면 검색과 관계없이 본인 전체 재조회
CSV 수식처럼 보임: 원본 DB를 수정하지 말고 변환/열람 검증
일부 행만 내보냄: 편집 중지 후 재시도·부분파일 금지
타인 요청200: 0행과 원본 불변까지 기록
AAL1 접근됨: 권한SQL과 실제 API 재검사
staging이 운영DB를 봄: 올바른 DB로 수정 후 새 빌드
main만 로그인 실패: 새 운영 URL에서 별도 검증
Naver Preview 실패: 준비된 고정 수업 어댑터에서 검사
롤백했는데 DB는 그대로: 검토된 별도 DB 복구 절차 사용
확인 질문
CSV에 화면 배열을 그대로 쓰면 어떤 문제가 생기는가?
검색·목록 한도·오래된 계정 데이터가 섞일 수 있어 본인 전체 DB를 새로 조회하고 세션을 검증한다.
익명·AAL1 차단 / 본인 AAL2만 허용 / B의 A행 조회·수정·삭제는0행 또는 거절 / user_id 위조 추가·변경 거절을 확인 / A조회 중 계정 전환 → A CSV 다운로드0회
170
Codex: 성공과 거절을 함께 검증
아래 전체 구현 요청을 사용한다.
171
과제 앱 계정별 권한·보안 최종 검증
과제 앱 계정별 권한·보안 최종 검증를 같은 앱에서 실행하고 근거를 남긴다. / 산출물: A/B·익명·AAL·실패 검사표 / 원리7·A/B/API20·실패/취소10·비밀검사9·정리4분
172
핵심 개념
UI 표시와 DB 권한 구분 / 소유자 RLS AND AAL2 / 실패·미확인·비밀 노출 검사
173
권한·비밀·실패를 증거로 검증
익명·AAL1·AAL2·A/B의 직접 API 요청을 대조한다. / HTTP200도0행일 수 있어 영향 행과 원본 불변을 함께 확인한다. / UI 역할·user_metadata를 권한 근거로 사용하지 않는다. / 로컬·모의·실제·미확인을 구분하고 비밀 없는 근거를 남긴다.
174
kysong · 이번 실습에 적용할 판단
기록: 여러 임시저장을 복구할 때 원본 보존과 정합성을 보완했다. / 교훈: 다른 사용자의 기록을 섞어 복구하거나 빈값을 AI가 채우면 안 된다. / 적용: 계정 전환 중 늦은 응답·CSV 취소까지 보안 검사에 포함한다.
175
실습 절차
아래 단계별 실습을 진행한다.
176
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
177
완료 확인과 오류 대응
AAL2는 관리자 권한이 아니다. B의 A행 조회/수정/삭제는0행 또는 거절이며 A원본 불변. 익명은 거절. AAL1은 본인 조회0행·쓰기거절/0행이며 내보내기 불가. 소유권 위조 추가/변경은 거절. / 부분 CSV·이전 계정 CSV·중복 다운로드0회. 예제 상한10,000건을 넘으면 명시 오류. 위 git ls-files 결과는 비어야 한다. 공개 URL/key 외 secret·service_role·SMTP·OAuth 비밀이 소스/번들에 없다.
같은 과제 앱의 최종 릴리스 실습
M01~M08과 E01~E08을 사용한다. CSV·계정별 권한·staging→main·발표를 같은 앱에서 완주한다.
보이는 버튼이나 성공 메시지보다 실제 권한 경계와 원본 불변을 평가한다. 미확인을 통과로 바꾸지 않는다.
강사용 설명
2교시50분은 원리7·A/B/API20·실패/취소10·비밀검사9·정리4다. 표의 기대 결과를 읽는 것과 실제 요청 성공/거절을 구분한다. HTTP200의 빈 배열도 RLS 차단일 수 있으므로 반환 행과 A원본을 함께 확인한다. 이메일이 같음/화면의 관리자 표기/user_metadata.role을 권한 근거로 쓰지 않는다. 1,203건은 기능 테스트가 끝나면 가상 수업 DB에서만 정리한다. 화면 목록의1,000건 한도와 CSV10,000건 한도를 구분한다. 다른 탭의 동시 편집은 잠시 멈춘다. 페이지 조회는 단일 트랜잭션 스냅샷이 아니며 동일 건수의 동시 편집까지 완전 감지하는 구현이 아니다. 실패를 RLS 해제로 해결하지 않는다. E01~E08과 M01~M08의 교재·시작/완성 코드를 사용한다.
실습 절차
A/B·익명·인증 강도별 권한 검사: 가상 A/B 계정을 서로 다른 브라우저 프로필에서 사용한다. 두 계정 모두 TOTP를 준비하고 본인 과제를 하나씩 만든다. A의 id를 검사에만 사용해 B의 실제 Data API로 조회·수정·삭제·user_id 위조를 시도한다. 로그아웃 뒤 재로그인한 AAL1과 추가 인증 후 AAL2를 비교한다. 아래 개발 서버 전용 검사 예시와 checks.csv에 실제 결과를 적는다.
페이지 실패·계정 전환·비밀 노출 점검: 네트워크 차단 또는 모의 테스트로 CSV 조회 중간 실패를 재현한다. 다운로드가 한 번도 발생하지 않아야 한다. 조회 중 로그아웃/다른 계정으로 전환했을 때 이전 계정 파일이 생성되지 않는지도 검사한다. 로컬 가상 데이터1,203건으로 페이지 조회를 확인한다. .gitignore·git diff·빌드 산출물을 검사하고 실패/미확인 사유를 남긴다.
Codex 구현 요청
내 과제 앱의 최종 검증을 실행 가능한 범위에서 진행하고 checks.csv에 근거를 남겨줘.
가상 A/B 계정, 익명, AAL1과 AAL2로 직접 Data API를 검사해. 본인 CRUD 허용, 타인 조회/수정/삭제의 0행 또는 거절과 원본 불변, user_id 위조 추가/변경 거절을 확인해. UI가 숨겨졌다는 이유로 권한 검사를 통과시키지 마.
CSV가 본인 전체 DB 과제만 새 조회하는지, 검색 결과와 무관한지, 한글/인용/CRLF/위험 입력/빈 목록/1,203행을 처리하는지 검사해. AAL1 내보내기 거절, 페이지 실패와 계정 전환에서 다운로드 0회를 확인해.
비밀은 값 대신 존재 여부와 변수 이름만 보고해. git diff --cached, .gitignore, 번들에 secret/service_role/SMTP/OAuth secret이 없는지 점검해. publishable key는 공개 식별값이며 RLS를 대신하지 않음을 명시해.
빌드·PR·배포상태와 실제 URL 기능 검사를 분리해. 로컬 통과, 모의 통과, staging 실제, Production 실제, 미실행을 구분해 결과/시각/commit/검증자를 기록해. 실패·미확인 항목이 남으면 출시 완료라고 쓰지 마.
AI 검증·오류 해결 요청
내 과제 앱의 최종 검증을 실행 가능한 범위에서 진행하고 checks.csv에 근거를 남겨줘.
가상 A/B 계정, 익명, AAL1과 AAL2로 직접 Data API를 검사해. 본인 CRUD 허용, 타인 조회/수정/삭제의 0행 또는 거절과 원본 불변, user_id 위조 추가/변경 거절을 확인해. UI가 숨겨졌다는 이유로 권한 검사를 통과시키지 마.
CSV가 본인 전체 DB 과제만 새 조회하는지, 검색 결과와 무관한지, 한글/인용/CRLF/위험 입력/빈 목록/1,203행을 처리하는지 검사해. AAL1 내보내기 거절, 페이지 실패와 계정 전환에서 다운로드 0회를 확인해.
비밀은 값 대신 존재 여부와 변수 이름만 보고해. git diff --cached, .gitignore, 번들에 secret/service_role/SMTP/OAuth secret이 없는지 점검해. publishable key는 공개 식별값이며 RLS를 대신하지 않음을 명시해.
빌드·PR·배포상태와 실제 URL 기능 검사를 분리해. 로컬 통과, 모의 통과, staging 실제, Production 실제, 미실행을 구분해 결과/시각/commit/검증자를 기록해. 실패·미확인 항목이 남으면 출시 완료라고 쓰지 마.
완료 확인
AAL2는 관리자 권한이 아니다. B의 A행 조회/수정/삭제는0행 또는 거절이며 A원본 불변. 익명은 거절. AAL1은 본인 조회0행·쓰기거절/0행이며 내보내기 불가. 소유권 위조 추가/변경은 거절.
부분 CSV·이전 계정 CSV·중복 다운로드0회. 예제 상한10,000건을 넘으면 명시 오류. 위 git ls-files 결과는 비어야 한다. 공개 URL/key 외 secret·service_role·SMTP·OAuth 비밀이 소스/번들에 없다.
staging 브랜치의 Preview와 main의 Production을 구분한다. / 두 환경의 URL·DB·설정 이름·검증 commit을 기록한다. / PR 검토→병합→새 빌드→실제 URL 재검사를 순서대로 확인한다. / READY·CANCELED·SKIPPED·ERROR는 기능 검증 결과와 다르다.
183
uc-anchor · 이번 실습에 적용할 판단
기록: PR #720·#722의 로컬 Git 병합 이력과 Thomas dot의 staging/Preview 논의를 확인했다. / 범위: 과거 배포 상태는 당시 관찰이며 이번 운영 재검증 결과가 아니다. / 적용: 학생 앱의 검증 commit·배포 commit·URL·DB 대상을 직접 남긴다.
184
실습 절차
아래 단계별 실습을 진행한다.
185
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
186
완료 확인과 오류 대응
환경 변수 변경 뒤 새 빌드를 만든다. 프런트엔드 URL/key 외 비밀값은 VITE_로 만들지 않는다. DB migration은 Vercel 코드 배포와 별도다. 기존 정책이 있으면 SQL을 무작정 재실행하지 않는다. / PR별 base/head·commit·실제 URL·DB 이름·검증자·결과가 남는다. Preview 통과와 Production 재빌드 통과를 별도로 기록한다. 실패하면 배포 완료 표시를 멈추고 원인을 수정한다.
같은 과제 앱의 최종 릴리스 실습
M01~M08과 E01~E08을 사용한다. CSV·계정별 권한·staging→main·발표를 같은 앱에서 완주한다.
과거 사례의 성공을 학생 배포의 성공으로 대신하지 않는다. main은 Production 환경의 새 빌드이며 DB 복구는 앱 롤백과 별도다.
강사용 설명
3교시50분은 환경8·PR/Preview15·검증10·main/운영13·정리4다. Git의 staging area는 커밋 대기 영역, staging 브랜치는 팀이 정한 이름이다. 브랜치 이름만으로 Vercel Production이 되지 않는다. 새 프로젝트의 Auth 사용자는 독립적이므로 같은 사람이 두 DB에서 다른 uid를 가질 수 있다. 운영 데이터 복제 없이 가상 계정으로 검증한다. 수업용 staging 브랜치는 시작 전에 main에서 생성해 원격에 둔다. main 병합은 Production 환경으로 새 빌드하므로 preview와 동일 실행물/설정이라고 단정하지 않는다. 배포 롤백은 앱 버전 복원이며 이미 저장한 DB 데이터나 스키마를 자동 복원하지 않는다. 잘못된 데이터 변경은 별도 검토된 DB 복구 절차가 필요하다. E01~E08과 M01~M08의 교재·시작/완성 코드를 사용한다.
실습 절차
staging Preview와 DB·Auth 설정 대조: 환경표를 채운다. Vercel Production Branch=main을 확인하고 staging은 Preview로 연결한다. Preview의 VITE_ 두 값은 별도 staging Supabase, Production 값은 production Supabase로 지정한다. 각 새 수업 DB를 inspect.sql로 확인하고 검토한 setup.sql→mfa-required.sql을 한 번 적용한다. 환경별 Auth·메일·SNS callback/redirect·Naver 어댑터를 점검한다.
feature→staging 검토 후 main 최종 배포: 변경을 커밋·push하고 GitHub PR base=staging, compare=feature/csv-release를 만든다. 동료가 diff·비밀·검사표를 검토하고 병합한다. Vercel staging Preview의 READY와 commit을 확인한 뒤 실제 URL에서 검증한다. 모두 통과하면 PR base=main, compare=staging을 검토·병합한다. main의 새 Production 배포에서 같은 검사를 다시 하고 release-record.md를 완성한다.
Codex 구현 요청
현재 과제 앱을 staging→main 최종 릴리스할 수 있도록 점검해줘.
git status, remotes, 브랜치, PR base/head와 배포 설정을 먼저 읽어줘. git staging area와 staging 브랜치를 구분해 설명해줘.
feature/csv-release→staging PR에서 검토하고 staging 브랜치는 Preview로 빌드해. 강사가 준비한 별도 수업 staging Supabase와 production Supabase에 같은 검토된 스키마·소유자 RLS·restrictive AAL2가 있는지 먼저 읽기 전용으로 확인해줘. 기존 운영 DB를 초기화하거나 임의 변경하지 마.
VITE_SUPABASE_URL과 VITE_SUPABASE_PUBLISHABLE_KEY의 Preview/Production 매핑, 실제 build commit·URL·DB 프로젝트 이름을 비밀값 없이 기록해. SNS callback/Redirect URL·SMTP·Naver 서버 어댑터도 환경별로 확인해줘.
검증표에 실패나 미확인이 남으면 완료로 표시하지 마. 통과 후 staging→main PR을 준비하고 main Production 재빌드 후 운영 URL에서 로그인/MFA/CRUD/CSV/계정 분리를 다시 확인해.
Preview 검증이 Production의 같은 환경 검증이라고 단정하지 마. 실패 시 이전 앱 배포 복원과 DB 복구가 다름을 설명하고 릴리스 기록 양식을 채워줘. 강제 push·보호 해제·비밀 커밋 없이 진행해줘.
AI 검증·오류 해결 요청
내 과제 앱의 최종 검증을 실행 가능한 범위에서 진행하고 checks.csv에 근거를 남겨줘.
가상 A/B 계정, 익명, AAL1과 AAL2로 직접 Data API를 검사해. 본인 CRUD 허용, 타인 조회/수정/삭제의 0행 또는 거절과 원본 불변, user_id 위조 추가/변경 거절을 확인해. UI가 숨겨졌다는 이유로 권한 검사를 통과시키지 마.
CSV가 본인 전체 DB 과제만 새 조회하는지, 검색 결과와 무관한지, 한글/인용/CRLF/위험 입력/빈 목록/1,203행을 처리하는지 검사해. AAL1 내보내기 거절, 페이지 실패와 계정 전환에서 다운로드 0회를 확인해.
비밀은 값 대신 존재 여부와 변수 이름만 보고해. git diff --cached, .gitignore, 번들에 secret/service_role/SMTP/OAuth secret이 없는지 점검해. publishable key는 공개 식별값이며 RLS를 대신하지 않음을 명시해.
빌드·PR·배포상태와 실제 URL 기능 검사를 분리해. 로컬 통과, 모의 통과, staging 실제, Production 실제, 미실행을 구분해 결과/시각/commit/검증자를 기록해. 실패·미확인 항목이 남으면 출시 완료라고 쓰지 마.
완료 확인
환경 변수 변경 뒤 새 빌드를 만든다. 프런트엔드 URL/key 외 비밀값은 VITE_로 만들지 않는다. DB migration은 Vercel 코드 배포와 별도다. 기존 정책이 있으면 SQL을 무작정 재실행하지 않는다.
PR별 base/head·commit·실제 URL·DB 이름·검증자·결과가 남는다. Preview 통과와 Production 재빌드 통과를 별도로 기록한다. 실패하면 배포 완료 표시를 멈추고 원인을 수정한다.
자주 막히는 지점
CSV 한글 깨짐: UTF-8 텍스트로 가져오기
열/행이 나뉨: 줄바꿈 있는 셀을 파서/스프레드시트로 확인
검색과 건수 다름: 화면 검색과 관계없이 본인 전체 재조회
CSV 수식처럼 보임: 원본 DB를 수정하지 말고 변환/열람 검증
일부 행만 내보냄: 편집 중지 후 재시도·부분파일 금지
타인 요청200: 0행과 원본 불변까지 기록
AAL1 접근됨: 권한SQL과 실제 API 재검사
staging이 운영DB를 봄: 올바른 DB로 수정 후 새 빌드
main만 로그인 실패: 새 운영 URL에서 별도 검증
Naver Preview 실패: 준비된 고정 수업 어댑터에서 검사
롤백했는데 DB는 그대로: 검토된 별도 DB 복구 절차 사용
확인 질문
staging Preview 통과 후 main에서도 다시 검사하는 이유는?
Production 설정으로 새 빌드되므로 DB·Auth·공개 URL을 다시 확인해야 한다.
문제·사용자→실제 동작→검증 근거→배포→한계를3분으로 보여준다. / README에 실행·설정 이름·SQL 순서·검사·복구 담당자를 적는다. / 동료 피드백1개를 다음 이슈로 남기고 미완료 항목을 명시한다. / 발표 자료·CSV·캡처에는 가상 데이터만 사용한다.
192
u-livet · 이번 실습에 적용할 판단
기록: 로컬 실험·운영 선택·SNS·MFA 후속 보완이 함께 남아 있다. / 교훈: 처음 만든 버전과 후속 수정, 실제/모의/미확인을 나눠 설명한다. / 적용: 과제 앱의5일 구축 과정과 다음 운영 작업을 재현 가능하게 전달한다.
193
실습 절차
아래 단계별 실습을 진행한다.
194
Codex에 입력할 요청
아래 전체 구현 요청을 사용한다.
195
완료 확인과 오류 대응
저장소/운영 링크·화면 시연·권한 근거·staging/Production commit·다음 개선1개가 있다. QR·OTP·토큰·실제 학생 정보가 발표 화면/CSV/캡처에 없다. / 본인이 하지 않은 실SNS·운영 검증을 통과로 표시하지 않는다. 발표용 CSV도 본인 가상 자료만 제출한다. 미커밋 작업·열린 PR·실패 검사·복구 담당자가 분명하다.
같은 과제 앱의 최종 릴리스 실습
M01~M08과 E01~E08을 사용한다. CSV·계정별 권한·staging→main·발표를 같은 앱에서 완주한다.
전체 프로젝트 연표와 자세한 기술 결정은 별도 사례 파일로 옮겼다. 발표에서는 이번 과제 앱과 관련된 판단 한 가지를 근거로 설명한다.
강사용 설명
4교시50분은 준비8·발표/짝피드백30·이슈/인수인계8·정리4다. 3분 발표+2분 피드백을6명 소그룹에서 병렬 진행하고 대표 사례만 전체 공유한다. 대규모 수업에서 전원이 전원 앞 발표하면 시간을 넘는다. “AI가 만들었다”보다 문제와 검증 근거를 평가한다. 성공 기준은 공개 링크 하나가 아니라 재현 가능한 실행·권한·배포 근거다. 계정 삭제/보관기간/관리자 대시보드/원본 백업은 후속 기능으로 이슈화한다. 교육용 체크 통과는 전체 보안 감사 완료를 의미하지 않는다. E01~E08과 M01~M08의 교재·시작/완성 코드를 사용한다.
실습 절차
3분 발표·짝 피드백: 요청3으로 발표 원고를 작성하고 발표용 가상 과제3개와 CSV를 준비한다. 청중에게 비밀이 보이지 않게 MFA를 사전 완료한다. 문제/사용자→핵심 동작→권한 검증 근거→두 배포 환경→한계 순서로3분 시연하고 짝에게 개선점1개를 받는다. 실SNS 연결 미완료는 미확인으로 설명한다.
인수인계·릴리스 기록·다음 이슈: README에 실행/환경 변수 이름/SQL 순서/검사 방법/CSV 범위/배포·복구를 정리한다. checks.csv와 release-record.md를 채우고 peer-feedback.md의 개선점 하나를 GitHub 이슈로 남긴다. 최종 main commit·운영URL·발표 원고·가상 CSV·미확인 항목을 제출한다.
Codex 구현 요청
완성한 과제 체크 앱의 3분 시연과 인수인계 자료를 만들어줘.
1일차 문제/사용자와 웹 선택→2일차 Git·개선→3일차 DB·Auth→4일차 SNS·MFA→5일차 CSV·검증·배포를 연결해줘.
0:00~0:30 문제/대상, 0:30~1:30 실제 과제 CRUD·검색·CSV, 1:30~2:15 A/B 권한 검증 근거와 MFA, 2:15~2:45 staging/Production URL·commit, 2:45~3:00 한계/다음 기능으로 구성해줘.
실제 확인한 결과와 모의/미확인을 구분하고 비밀키·토큰·TOTP QR/코드·학생 개인정보를 화면/문서에 넣지 마. 발표용 가상 계정을 사용하고 MFA는 청중에게 비밀을 보이지 않게 사전 완료해.
README에는 실행/환경변수 이름/SQL 적용 순서/CSV 열람 범위/테스트 방법/배포·복구·미확인 항목을 적어줘. 동료 피드백 1건을 이슈로 남길 수 있는 양식과 100점 평가표도 포함해줘.
AI 검증·오류 해결 요청
내 과제 앱의 최종 검증을 실행 가능한 범위에서 진행하고 checks.csv에 근거를 남겨줘.
가상 A/B 계정, 익명, AAL1과 AAL2로 직접 Data API를 검사해. 본인 CRUD 허용, 타인 조회/수정/삭제의 0행 또는 거절과 원본 불변, user_id 위조 추가/변경 거절을 확인해. UI가 숨겨졌다는 이유로 권한 검사를 통과시키지 마.
CSV가 본인 전체 DB 과제만 새 조회하는지, 검색 결과와 무관한지, 한글/인용/CRLF/위험 입력/빈 목록/1,203행을 처리하는지 검사해. AAL1 내보내기 거절, 페이지 실패와 계정 전환에서 다운로드 0회를 확인해.
비밀은 값 대신 존재 여부와 변수 이름만 보고해. git diff --cached, .gitignore, 번들에 secret/service_role/SMTP/OAuth secret이 없는지 점검해. publishable key는 공개 식별값이며 RLS를 대신하지 않음을 명시해.
빌드·PR·배포상태와 실제 URL 기능 검사를 분리해. 로컬 통과, 모의 통과, staging 실제, Production 실제, 미실행을 구분해 결과/시각/commit/검증자를 기록해. 실패·미확인 항목이 남으면 출시 완료라고 쓰지 마.
완료 확인
저장소/운영 링크·화면 시연·권한 근거·staging/Production commit·다음 개선1개가 있다. QR·OTP·토큰·실제 학생 정보가 발표 화면/CSV/캡처에 없다.
본인이 하지 않은 실SNS·운영 검증을 통과로 표시하지 않는다. 발표용 CSV도 본인 가상 자료만 제출한다. 미커밋 작업·열린 PR·실패 검사·복구 담당자가 분명하다.
4일차 과제 체크 앱의 이메일/SNS 로그인, TOTP MFA, 소유자 RLS를 유지하고 CSV 내보내기를 추가해줘.
먼저 현재 파일·DB 정책·세션 전환 처리를 읽고 변경할 파일과 검증 기준을 제시해줘.
검색 화면의 배열 대신 로그인한 본인의 과제 전체를 DB에서 새로 조회해. getUser의 uid와 currentLevel=aal2를 확인하고 user_id 필터와 DB의 소유자 RLS AND restrictive AAL2를 모두 유지해.
500행씩 created_at,id 순으로 페이지 조회하고 exact count와 중복/누락을 검사해. 10,000행 초과·중간 오류·건수 변경은 부분 파일 없이 실패시켜. 동시 편집의 일관된 스냅샷을 보장한다고 설명하지 마.
열은 과제명/과목/마감일/완료만. UTF-8 BOM, CRLF, 모든 셀 큰따옴표, 내부 따옴표 두 번 처리. 수식 위험 시작문자(공백 뒤 = + - @와 전각 변형, 탭/개행)에는 quoted 셀 안에 탭을 앞붙여 스프레드시트 열람 위험을 줄이고 원본 DB는 바꾸지 마. 모든 앱/재저장에 안전하다거나 원본 복원 백업이라고 설명하지 마.
조회 중 progress와 aria-busy, 중복 실행 방지, 완료/오류 알림, Blob URL 해제를 구현해. 계정/토큰 변경·로그아웃은 AbortController와 epoch 확인으로 취소하고 늦은 A 응답을 B 화면에서 다운로드하지 않게 해.
빈 목록·한글·쉼표·따옴표·줄바꿈·수식 위험·1,000행 초과·중간 실패·계정 전환을 검증하고 npm run build 결과와 미확인 범위를 알려줘. 비밀값을 출력하거나 RLS/MFA를 끄지 마.
2. staging→main 최종 릴리스
현재 과제 앱을 staging→main 최종 릴리스할 수 있도록 점검해줘.
git status, remotes, 브랜치, PR base/head와 배포 설정을 먼저 읽어줘. git staging area와 staging 브랜치를 구분해 설명해줘.
feature/csv-release→staging PR에서 검토하고 staging 브랜치는 Preview로 빌드해. 강사가 준비한 별도 수업 staging Supabase와 production Supabase에 같은 검토된 스키마·소유자 RLS·restrictive AAL2가 있는지 먼저 읽기 전용으로 확인해줘. 기존 운영 DB를 초기화하거나 임의 변경하지 마.
VITE_SUPABASE_URL과 VITE_SUPABASE_PUBLISHABLE_KEY의 Preview/Production 매핑, 실제 build commit·URL·DB 프로젝트 이름을 비밀값 없이 기록해. SNS callback/Redirect URL·SMTP·Naver 서버 어댑터도 환경별로 확인해줘.
검증표에 실패나 미확인이 남으면 완료로 표시하지 마. 통과 후 staging→main PR을 준비하고 main Production 재빌드 후 운영 URL에서 로그인/MFA/CRUD/CSV/계정 분리를 다시 확인해.
Preview 검증이 Production의 같은 환경 검증이라고 단정하지 마. 실패 시 이전 앱 배포 복원과 DB 복구가 다름을 설명하고 릴리스 기록 양식을 채워줘. 강제 push·보호 해제·비밀 커밋 없이 진행해줘.
3. 3분 발표·인수인계
완성한 과제 체크 앱의 3분 시연과 인수인계 자료를 만들어줘.
1일차 문제/사용자와 웹 선택→2일차 Git·개선→3일차 DB·Auth→4일차 SNS·MFA→5일차 CSV·검증·배포를 연결해줘.
0:00~0:30 문제/대상, 0:30~1:30 실제 과제 CRUD·검색·CSV, 1:30~2:15 A/B 권한 검증 근거와 MFA, 2:15~2:45 staging/Production URL·commit, 2:45~3:00 한계/다음 기능으로 구성해줘.
실제 확인한 결과와 모의/미확인을 구분하고 비밀키·토큰·TOTP QR/코드·학생 개인정보를 화면/문서에 넣지 마. 발표용 가상 계정을 사용하고 MFA는 청중에게 비밀을 보이지 않게 사전 완료해.
README에는 실행/환경변수 이름/SQL 적용 순서/CSV 열람 범위/테스트 방법/배포·복구·미확인 항목을 적어줘. 동료 피드백 1건을 이슈로 남길 수 있는 양식과 100점 평가표도 포함해줘.
추가: 계정·CSV·배포 통합 검증
내 과제 앱의 최종 검증을 실행 가능한 범위에서 진행하고 checks.csv에 근거를 남겨줘.
가상 A/B 계정, 익명, AAL1과 AAL2로 직접 Data API를 검사해. 본인 CRUD 허용, 타인 조회/수정/삭제의 0행 또는 거절과 원본 불변, user_id 위조 추가/변경 거절을 확인해. UI가 숨겨졌다는 이유로 권한 검사를 통과시키지 마.
CSV가 본인 전체 DB 과제만 새 조회하는지, 검색 결과와 무관한지, 한글/인용/CRLF/위험 입력/빈 목록/1,203행을 처리하는지 검사해. AAL1 내보내기 거절, 페이지 실패와 계정 전환에서 다운로드 0회를 확인해.
비밀은 값 대신 존재 여부와 변수 이름만 보고해. git diff --cached, .gitignore, 번들에 secret/service_role/SMTP/OAuth secret이 없는지 점검해. publishable key는 공개 식별값이며 RLS를 대신하지 않음을 명시해.
빌드·PR·배포상태와 실제 URL 기능 검사를 분리해. 로컬 통과, 모의 통과, staging 실제, Production 실제, 미실행을 구분해 결과/시각/commit/검증자를 기록해. 실패·미확인 항목이 남으면 출시 완료라고 쓰지 마.
4일차 완성 앱이 있는 본인 clone 폴더를 연다. ZIP의 starter는 비교용이며 기존 .git과 .env.local을 덮어쓰지 않는다. 강사는 staging 브랜치와 수업용 staging/production Supabase 두 환경을 사전 준비한다. 학생은 main의 최신 이력에서 feature/csv-release를 만들고 로컬 가입·로그인·MFA·과제 CRUD부터 확인한다.
본인 저장소에서 한 줄씩 실행
git status -sb
git fetch origin
git switch main
git pull --ff-only
git switch -c feature/csv-release
npm ci
npm run dev
학생 확인
미커밋 변경을 먼저 정리하고 잘못된 저장소에서 실행하지 않는다. 환경표에 URL·브랜치·DB 이름만 적는다. 두 환경은 같은 DB를 가리키지 않는다.
강사용 설명
1교시50분은 목표5·복원8·CSV구현22·경계검증11·정리4다. 두 Supabase 프로젝트/키 준비가 지연되지 않게 강사가 별도 수업 프로젝트를 미리 확보한다. 계정 무료 프로젝트 한도를 수업 전에 확인하고 필요하면 강사 제공 환경으로 진행한다. 실제 프로젝트의 운영 DB를 재사용하지 않는다.
요청1을 Codex에 입력하고 변경 파일을 검토한다. solution의 csv-export.js·task-store.js·main.js·HTML/CSS와 비교한다. 추가 인증 후 내 과제 CSV 내보내기를 누른다. 검색 결과가 1건이어도 본인 전체 DB 과제가 들어가는지 확인한다. 내려받은 파일은 스프레드시트에서 텍스트로 가져오고 한글·열 이름·완료 상태를 확인한다.
이 단계의 Codex 요청
4일차 과제 체크 앱의 이메일/SNS 로그인, TOTP MFA, 소유자 RLS를 유지하고 CSV 내보내기를 추가해줘.
먼저 현재 파일·DB 정책·세션 전환 처리를 읽고 변경할 파일과 검증 기준을 제시해줘.
검색 화면의 배열 대신 로그인한 본인의 과제 전체를 DB에서 새로 조회해. getUser의 uid와 currentLevel=aal2를 확인하고 user_id 필터와 DB의 소유자 RLS AND restrictive AAL2를 모두 유지해.
500행씩 created_at,id 순으로 페이지 조회하고 exact count와 중복/누락을 검사해. 10,000행 초과·중간 오류·건수 변경은 부분 파일 없이 실패시켜. 동시 편집의 일관된 스냅샷을 보장한다고 설명하지 마.
열은 과제명/과목/마감일/완료만. UTF-8 BOM, CRLF, 모든 셀 큰따옴표, 내부 따옴표 두 번 처리. 수식 위험 시작문자(공백 뒤 = + - @와 전각 변형, 탭/개행)에는 quoted 셀 안에 탭을 앞붙여 스프레드시트 열람 위험을 줄이고 원본 DB는 바꾸지 마. 모든 앱/재저장에 안전하다거나 원본 복원 백업이라고 설명하지 마.
조회 중 progress와 aria-busy, 중복 실행 방지, 완료/오류 알림, Blob URL 해제를 구현해. 계정/토큰 변경·로그아웃은 AbortController와 epoch 확인으로 취소하고 늦은 A 응답을 B 화면에서 다운로드하지 않게 해.
빈 목록·한글·쉼표·따옴표·줄바꿈·수식 위험·1,000행 초과·중간 실패·계정 전환을 검증하고 npm run build 결과와 미확인 범위를 알려줘. 비밀값을 출력하거나 RLS/MFA를 끄지 마.
학생 확인
한글·쉼표·큰따옴표·줄바꿈·=1+2·전각=1+2와 빈 목록을 검사한다. 위험한 자유 입력 앞의 탭은 열람용 변환이며 DB 원본은 유지된다. 토큰·이메일·uid가 CSV에 없다.
강사용 설명
CSV 인용은 형식 보존, 수식 위험 변환은 별도 보안 처리다. 탭 접두어는 Excel 열람의 위험을 줄이지만 모든 스프레드시트와 재저장에 대한 보장은 아니다. 코드 검증과 실제 교실 Excel/Sheets에서 텍스트 가져오기 검증을 나눠 기록한다. UI 입력이 trim하는 경우 줄바꿈/선행 제어문자 검사는 코드 테스트로 보충한다.
가상 A/B 계정을 서로 다른 브라우저 프로필에서 사용한다. 두 계정 모두 TOTP를 준비하고 본인 과제를 하나씩 만든다. A의 id를 검사에만 사용해 B의 실제 Data API로 조회·수정·삭제·user_id 위조를 시도한다. 로그아웃 뒤 재로그인한 AAL1과 추가 인증 후 AAL2를 비교한다. 아래 개발 서버 전용 검사 예시와 checks.csv에 실제 결과를 적는다.
학생 확인
AAL2는 관리자 권한이 아니다. B의 A행 조회/수정/삭제는0행 또는 거절이며 A원본 불변. 익명은 거절. AAL1은 본인 조회0행·쓰기거절/0행이며 내보내기 불가. 소유권 위조 추가/변경은 거절.
강사용 설명
2교시50분은 원리7·A/B/API20·실패/취소10·비밀검사9·정리4다. 표의 기대 결과를 읽는 것과 실제 요청 성공/거절을 구분한다. HTTP200의 빈 배열도 RLS 차단일 수 있으므로 반환 행과 A원본을 함께 확인한다. 이메일이 같음/화면의 관리자 표기/user_metadata.role을 권한 근거로 쓰지 않는다.
네트워크 차단 또는 모의 테스트로 CSV 조회 중간 실패를 재현한다. 다운로드가 한 번도 발생하지 않아야 한다. 조회 중 로그아웃/다른 계정으로 전환했을 때 이전 계정 파일이 생성되지 않는지도 검사한다. 로컬 가상 데이터1,203건으로 페이지 조회를 확인한다. .gitignore·git diff·빌드 산출물을 검사하고 실패/미확인 사유를 남긴다.
본인 저장소에서 한 줄씩 실행
npm run build
git status --short
git diff --check
git diff -- src index.html styles.css
git ls-files .env.local .env.production
학생 확인
부분 CSV·이전 계정 CSV·중복 다운로드0회. 예제 상한10,000건을 넘으면 명시 오류. 위 git ls-files 결과는 비어야 한다. 공개 URL/key 외 secret·service_role·SMTP·OAuth 비밀이 소스/번들에 없다.
강사용 설명
1,203건은 기능 테스트가 끝나면 가상 수업 DB에서만 정리한다. 화면 목록의1,000건 한도와 CSV10,000건 한도를 구분한다. 다른 탭의 동시 편집은 잠시 멈춘다. 페이지 조회는 단일 트랜잭션 스냅샷이 아니며 동일 건수의 동시 편집까지 완전 감지하는 구현이 아니다. 실패를 RLS 해제로 해결하지 않는다.
환경표를 채운다. Vercel Production Branch=main을 확인하고 staging은 Preview로 연결한다. Preview의 VITE_ 두 값은 별도 staging Supabase, Production 값은 production Supabase로 지정한다. 각 새 수업 DB를 inspect.sql로 확인하고 검토한 setup.sql→mfa-required.sql을 한 번 적용한다. 환경별 Auth·메일·SNS callback/redirect·Naver 어댑터를 점검한다.
이 단계의 Codex 요청
현재 과제 앱을 staging→main 최종 릴리스할 수 있도록 점검해줘.
git status, remotes, 브랜치, PR base/head와 배포 설정을 먼저 읽어줘. git staging area와 staging 브랜치를 구분해 설명해줘.
feature/csv-release→staging PR에서 검토하고 staging 브랜치는 Preview로 빌드해. 강사가 준비한 별도 수업 staging Supabase와 production Supabase에 같은 검토된 스키마·소유자 RLS·restrictive AAL2가 있는지 먼저 읽기 전용으로 확인해줘. 기존 운영 DB를 초기화하거나 임의 변경하지 마.
VITE_SUPABASE_URL과 VITE_SUPABASE_PUBLISHABLE_KEY의 Preview/Production 매핑, 실제 build commit·URL·DB 프로젝트 이름을 비밀값 없이 기록해. SNS callback/Redirect URL·SMTP·Naver 서버 어댑터도 환경별로 확인해줘.
검증표에 실패나 미확인이 남으면 완료로 표시하지 마. 통과 후 staging→main PR을 준비하고 main Production 재빌드 후 운영 URL에서 로그인/MFA/CRUD/CSV/계정 분리를 다시 확인해.
Preview 검증이 Production의 같은 환경 검증이라고 단정하지 마. 실패 시 이전 앱 배포 복원과 DB 복구가 다름을 설명하고 릴리스 기록 양식을 채워줘. 강제 push·보호 해제·비밀 커밋 없이 진행해줘.
학생 확인
환경 변수 변경 뒤 새 빌드를 만든다. 프런트엔드 URL/key 외 비밀값은 VITE_로 만들지 않는다. DB migration은 Vercel 코드 배포와 별도다. 기존 정책이 있으면 SQL을 무작정 재실행하지 않는다.
강사용 설명
3교시50분은 환경8·PR/Preview15·검증10·main/운영13·정리4다. Git의 staging area는 커밋 대기 영역, staging 브랜치는 팀이 정한 이름이다. 브랜치 이름만으로 Vercel Production이 되지 않는다. 새 프로젝트의 Auth 사용자는 독립적이므로 같은 사람이 두 DB에서 다른 uid를 가질 수 있다. 운영 데이터 복제 없이 가상 계정으로 검증한다.
변경을 커밋·push하고 GitHub PR base=staging, compare=feature/csv-release를 만든다. 동료가 diff·비밀·검사표를 검토하고 병합한다. Vercel staging Preview의 READY와 commit을 확인한 뒤 실제 URL에서 검증한다. 모두 통과하면 PR base=main, compare=staging을 검토·병합한다. main의 새 Production 배포에서 같은 검사를 다시 하고 release-record.md를 완성한다.
PR별 base/head·commit·실제 URL·DB 이름·검증자·결과가 남는다. Preview 통과와 Production 재빌드 통과를 별도로 기록한다. 실패하면 배포 완료 표시를 멈추고 원인을 수정한다.
강사용 설명
수업용 staging 브랜치는 시작 전에 main에서 생성해 원격에 둔다. main 병합은 Production 환경으로 새 빌드하므로 preview와 동일 실행물/설정이라고 단정하지 않는다. 배포 롤백은 앱 버전 복원이며 이미 저장한 DB 데이터나 스키마를 자동 복원하지 않는다. 잘못된 데이터 변경은 별도 검토된 DB 복구 절차가 필요하다.
요청3으로 발표 원고를 작성하고 발표용 가상 과제3개와 CSV를 준비한다. 청중에게 비밀이 보이지 않게 MFA를 사전 완료한다. 문제/사용자→핵심 동작→권한 검증 근거→두 배포 환경→한계 순서로3분 시연하고 짝에게 개선점1개를 받는다. 실SNS 연결 미완료는 미확인으로 설명한다.
이 단계의 Codex 요청
완성한 과제 체크 앱의 3분 시연과 인수인계 자료를 만들어줘.
1일차 문제/사용자와 웹 선택→2일차 Git·개선→3일차 DB·Auth→4일차 SNS·MFA→5일차 CSV·검증·배포를 연결해줘.
0:00~0:30 문제/대상, 0:30~1:30 실제 과제 CRUD·검색·CSV, 1:30~2:15 A/B 권한 검증 근거와 MFA, 2:15~2:45 staging/Production URL·commit, 2:45~3:00 한계/다음 기능으로 구성해줘.
실제 확인한 결과와 모의/미확인을 구분하고 비밀키·토큰·TOTP QR/코드·학생 개인정보를 화면/문서에 넣지 마. 발표용 가상 계정을 사용하고 MFA는 청중에게 비밀을 보이지 않게 사전 완료해.
README에는 실행/환경변수 이름/SQL 적용 순서/CSV 열람 범위/테스트 방법/배포·복구·미확인 항목을 적어줘. 동료 피드백 1건을 이슈로 남길 수 있는 양식과 100점 평가표도 포함해줘.
학생 확인
저장소/운영 링크·화면 시연·권한 근거·staging/Production commit·다음 개선1개가 있다. QR·OTP·토큰·실제 학생 정보가 발표 화면/CSV/캡처에 없다.
강사용 설명
4교시50분은 준비8·발표/짝피드백30·이슈/인수인계8·정리4다. 3분 발표+2분 피드백을6명 소그룹에서 병렬 진행하고 대표 사례만 전체 공유한다. 대규모 수업에서 전원이 전원 앞 발표하면 시간을 넘는다. “AI가 만들었다”보다 문제와 검증 근거를 평가한다.
README에 실행/환경 변수 이름/SQL 순서/검사 방법/CSV 범위/배포·복구를 정리한다. checks.csv와 release-record.md를 채우고 peer-feedback.md의 개선점 하나를 GitHub 이슈로 남긴다. 최종 main commit·운영URL·발표 원고·가상 CSV·미확인 항목을 제출한다.
본인 저장소에서 한 줄씩 실행
git status -sb
git log -5 --oneline
git diff --check
학생 확인
본인이 하지 않은 실SNS·운영 검증을 통과로 표시하지 않는다. 발표용 CSV도 본인 가상 자료만 제출한다. 미커밋 작업·열린 PR·실패 검사·복구 담당자가 분명하다.
강사용 설명
성공 기준은 공개 링크 하나가 아니라 재현 가능한 실행·권한·배포 근거다. 계정 삭제/보관기간/관리자 대시보드/원본 백업은 후속 기능으로 이슈화한다. 교육용 체크 통과는 전체 보안 감사 완료를 의미하지 않는다.
수식 위험 변환은 사람의 스프레드시트 열람을 위한 처리다. 탭이 실제 셀 값에 포함되므로 원본 복원/재가져오기용 백업으로 쓰지 않는다. OWASP는 인용/이스케이프만으로 모든 스프레드시트 및 재저장을 보장할 수 없다고 설명한다. 교실의 Excel/Sheets에서 텍스트 가져오기로 실제 열람을 확인한다. DB 원본은 그대로 둔다.
CSV 생성·다운로드·인증/취소 전체 코드
// 사람의 스프레드시트 열람용입니다. 원본 복원/재가져오기 형식이 아닙니다.
function cell(value, userInput = false) {
let text = String(value ?? '');
// 인용만으로 수식이 막히지 않습니다. 전각·선행 공백/제어문자도 검사합니다.
if (userInput && (/^\s*[=+\-@=+-@]/u.test(text) || /^[\t\r\n]/u.test(text))) text = '\t' + text;
return '"' + text.replaceAll('"', '""') + '"';
}
export function toTaskCsv(tasks) {
const rows = [['과제명', '과목', '마감일', '완료'].map(v => cell(v)).join(',')];
for (const task of tasks) rows.push([
cell(task.title, true), cell(task.subject, true), cell(task.due), cell(task.done ? '완료' : '미완료')
].join(','));
return '\uFEFF' + rows.join('\r\n') + '\r\n';
}
export function downloadTaskCsv(csv) {
const url = URL.createObjectURL(new Blob([csv], {type:'text/csv;charset=utf-8'}));
const link = document.createElement('a');
link.href = url;
link.download = `my-tasks-${new Date().toISOString().replaceAll(':','-').slice(0,19)}Z.csv`;
document.body.append(link);
try { link.click(); }
finally { link.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); }
}
export function createCsvExporter(client, store, download = downloadTaskCsv) {
let active = null;
return {
cancel() { active?.abort(); },
async run(userId, isCurrent, onProgress) {
if (active) throw {code:'EXPORT_BUSY'};
const controller = new AbortController();
active = controller;
function check() {
controller.signal.throwIfAborted();
if (!isCurrent()) throw {code:'EXPORT_CANCELLED'};
}
try {
check();
const {data, error} = await client.auth.getUser();
check();
if (error || data.user?.id !== userId) throw {code:'EXPORT_AUTH'};
const aal = await client.auth.mfa.getAuthenticatorAssuranceLevel();
check();
if (aal.error || aal.data.currentLevel !== 'aal2') throw {code:'EXPORT_AUTH'};
const tasks = await store.listForExport(userId, controller.signal, onProgress);
check();
const csv = toTaskCsv(tasks);
check();
download(csv);
return tasks.length;
} finally {
if (active === controller) active = null;
}
}
};
}
아래 코드는 가상 계정·본인 로컬 Vite 개발 서버에서만 DevTools Console로 실행한다. B는 별도 브라우저 프로필에서 로그인·MFA를 마친다. targetId/targetOwner는 A가 만든 가상 과제의 UUID와 A의 uid로 바꾼다. 배포 번들에는 /src 경로가 없으므로 운영 Console에 그대로 붙이지 않는다. JWT·브라우저 세션·전체 client 객체를 출력하지 않는다.
UUID 자리표시자를 그대로 두면 단순 형식 오류만 검사하게 된다. 실제 가상 UUID를 넣고 표와 대조한다. 익명은 로그아웃한 개발 서버에서 동일 SDK로 SELECT/INSERT를 시도한다. AAL1은 등록된 계정의 재로그인 직후, AAL2는 인증앱 코드 확인 후 검사한다. QR/코드는 보고서에 넣지 않는다. 소유자 정책4개 AND AS RESTRICTIVE AAL2를 함께 유지한다.
GitHub에서 기존 main을 기준으로 staging 브랜치를 준비한다. staging area는 git add로 만드는 커밋 대기 영역이며 브랜치와 다르다.
Vercel Settings에서 Production Branch=main을 확인한다. staging push는 보통 Preview다. Preview 두 VITE_ 값은 staging DB, Production 두 값은 별도 production DB로 설정한다. 다른 Preview도 쓰면 staging branch override를 확인한다.
본인 수업용 새 DB를 sql/inspect.sql로 먼저 확인한 뒤 setup.sql→mfa-required.sql을 검토·적용한다. 이미 존재하면 중복 실행하지 말고 변경 migration을 검토한다. Auth 가입·이메일/비밀번호·TOTP 설정은 DB SQL과 별도다.
staging/production Supabase의 Site URL·Redirect URLs, 제공자마다 표시하는 Supabase callback, SMTP/Resend 발신·이메일 확인/복구, Naver 고정 서버 어댑터를 환경별 점검한다. 비밀은 각 서비스 설정에 보관한다.
feature/csv-release→staging PR을 검토·병합한다. Preview READY·commit·URL·DB 이름을 대조하고 로그인→MFA→과제 추가/완료/재조회→CSV→B분리 검사를 한다.
모두 통과하면 staging→main PR을 검토·병합한다. main은 Production 환경으로 새 빌드한다. 운영 URL의 동일 smoke 검사를 반복하고 기록한다.
실패/미확인이 있으면 출시 완료 표시를 멈춘다. 필요하면 이전 정상 앱 배포를 복원하고, DB 스키마/데이터 복구는 별도 검토한다.
이 수업은 main의 Git 자동 배포를 사용한다. Preview 검증만으로 Production의 환경·메일·SNS가 검증되지는 않는다. Vercel에서 Preview를 promote하는 별도 기능도 Production 설정으로 다시 빌드될 수 있으므로 Preview URL을 그대로 운영 검증 결과로 적지 않는다. Vercel 배포는 Supabase SQL을 자동 적용하지 않는다.
# 과제 체크 앱 최종 릴리스 기록
검증 일시/확인자:
저장소 / feature PR → staging:
staging commit / Preview URL / Supabase 프로젝트 이름:
staging 빌드 상태 / 실제 검사 근거:
staging PR → main:
main commit / Production URL / Supabase 프로젝트 이름:
Production 빌드 상태 / 실제 검사 근거:
DB SQL 검토·적용 순서/기록 (키/연결문자열 제외):
이메일·각 SNS·MFA 실제/모의/미확인:
실패·미확인 / 출시 판단:
이전 정상 앱 배포 / 복원 담당자:
DB 변경 유무 / 별도 DB 복구 절차:
다음 이슈 / 동료 피드백:
ZIP의 environments.csv와 release-record.md에 실제 값을 기록하되 키·비밀번호·연결 문자열은 넣지 않는다. URL/commit/프로젝트 이름과 검사 시각이면 근거를 대조할 수 있다.
내 과제 앱의 최종 검증을 실행 가능한 범위에서 진행하고 checks.csv에 근거를 남겨줘.
가상 A/B 계정, 익명, AAL1과 AAL2로 직접 Data API를 검사해. 본인 CRUD 허용, 타인 조회/수정/삭제의 0행 또는 거절과 원본 불변, user_id 위조 추가/변경 거절을 확인해. UI가 숨겨졌다는 이유로 권한 검사를 통과시키지 마.
CSV가 본인 전체 DB 과제만 새 조회하는지, 검색 결과와 무관한지, 한글/인용/CRLF/위험 입력/빈 목록/1,203행을 처리하는지 검사해. AAL1 내보내기 거절, 페이지 실패와 계정 전환에서 다운로드 0회를 확인해.
비밀은 값 대신 존재 여부와 변수 이름만 보고해. git diff --cached, .gitignore, 번들에 secret/service_role/SMTP/OAuth secret이 없는지 점검해. publishable key는 공개 식별값이며 RLS를 대신하지 않음을 명시해.
빌드·PR·배포상태와 실제 URL 기능 검사를 분리해. 로컬 통과, 모의 통과, staging 실제, Production 실제, 미실행을 구분해 결과/시각/commit/검증자를 기록해. 실패·미확인 항목이 남으면 출시 완료라고 쓰지 마.
통과/실패/미확인과 실제 결과·시각·확인자를 CSV 양식에 적는다. 테스트 계정/키/QR/코드/토큰을 근거로 첨부하지 않는다.
완성 소스는 격리된 로컬 Supabase의 실제 TOTP/AAL/RLS와1,203행 CSV 페이지 조회, 타인 제외·소유자 위조·계정 전환·실패 처리를 검사한다. ZIP 클린 빌드와 HTML 발표·교재를 검수한다. 학생의 실제 cloud staging/Production DB 연결, 이메일 발송, 세 SNS 제공자 승인/동의 왕복, 실제 교실 스프레드시트 열람은 각 수업 계정에서 최종 확인해야 한다. 강의 사이트 게시와 학생 앱의 두 환경 릴리스는 별개다.