fix(desktop+supabase): OAuth 로그인 완주 + RLS 재귀 픽스 (빅뱅 Phase 5 Part 1)
빅뱅 Phase 5 실증 중 발견한 3개 버그 해결:
1. implicit flow 응답 무시:
- supabase-js가 persistSession:false 일 때 PKCE code_verifier 저장 못해
implicit flow로 fallback, fragment(#access_token=...&refresh_token=...)로
토큰 전달. handleDeepLink는 ?code=만 파싱하고 있어 무시됨.
- handleDeepLink에 fragment parser 추가 (access_token + refresh_token)
- CloudSyncService.handleAuthTokens() 신규 — setSession() 후
기존 _onAuthenticated SSOT로 수렴
- 에러 query/fragment(error, error_description) 상세 로깅
- 토큰 값은 로그에 안 찍히도록 (query=yes/no, fragment=yes/no 만)
2. Supabase RLS 무한 재귀:
- team_members_read_same_team 정책이 자기 테이블 재조회 →
Postgres RLS 엔진 무한 재귀 탐지 에러
- meetings / meeting_memos / meeting_documents / team_invites 정책이
team_members 서브쿼리 경유해서 전부 같이 터짐 + Realtime TIMED_OUT
- migration 20260411000001: SECURITY DEFINER 함수 2개
(user_team_ids, user_admin_team_ids) 신규 — Supabase 권장 패턴
- 영향 정책: team_members(4) + meetings(2) + meeting_memos(1) +
meeting_documents(2) + team_invites(1) 전부 함수 기반으로 재작성
- supabase db push 완료
3. LoginScreen stale 번들:
- Phase 1.5에서 import 제거했는데도 vite HMR/cache 어딘가에서
stale state 유지해서 렌더러에 계속 뜸
- LoginScreen.tsx 파일 자체 삭제 (vite 컴파일 대상 제거)
로그인 성공 확인: yunchan8804@gmail.com 으로 Google OAuth 완주 →
users/7da3dd02-9f2f-4ee9-a9b3-1c2c24875a93/d3ro.db 생성 → Initial sync 시작.
This commit is contained in:
parent
817b4a2580
commit
9498380f7f
5 changed files with 305 additions and 125 deletions
|
|
@ -1,113 +0,0 @@
|
|||
// src/renderer/components/LoginScreen.tsx
|
||||
// SaaS 첫 실행 게이트 — OAuth 로그인 안 됐으면 메인 UI 진입 차단.
|
||||
// 사용자는 Google/GitHub 버튼만 클릭하면 됨. URL/Key 입력 필드 노출 금지.
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Box, Button, Stack, Alert, CircularProgress } from '@mui/material'
|
||||
import GoogleIcon from '@mui/icons-material/Google'
|
||||
import GitHubIcon from '@mui/icons-material/GitHub'
|
||||
import { MetalCard, PhosphorText } from '@d3ro/ui/components/ds'
|
||||
import { d3roPalette, d3roFontMono, typoSx } from '@d3ro/ui/theme'
|
||||
import { useI18n } from '@d3ro/i18n'
|
||||
|
||||
export function LoginScreen(): React.ReactElement {
|
||||
const { t } = useI18n()
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [info, setInfo] = useState<string | null>(null)
|
||||
|
||||
async function handleSignIn(provider: 'google' | 'github'): Promise<void> {
|
||||
setError(null)
|
||||
setInfo(null)
|
||||
setBusy(true)
|
||||
try {
|
||||
const r = await window.electronAPI.cloudSync.signIn({ provider })
|
||||
if (!r.success) {
|
||||
setError(r.error.message)
|
||||
setBusy(false)
|
||||
return
|
||||
}
|
||||
setInfo(t('login.browserPrompt') ?? '브라우저에서 로그인을 완료해주세요...')
|
||||
// 성공 시 auth-changed 이벤트가 AuthGate로 전달되어 자동 전환됨.
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: d3roPalette.bg.app,
|
||||
p: 4
|
||||
}}
|
||||
>
|
||||
<MetalCard sx={{ p: 5, maxWidth: 440, width: '100%' }}>
|
||||
<Stack spacing={3} alignItems="center">
|
||||
<PhosphorText variant="title">D3RO VOICE</PhosphorText>
|
||||
<Box
|
||||
sx={{
|
||||
...typoSx('label'),
|
||||
color: d3roPalette.text.label,
|
||||
textAlign: 'center',
|
||||
fontFamily: d3roFontMono
|
||||
}}
|
||||
>
|
||||
{t('login.subtitle') ?? 'AI 음성 어시스턴트'}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ width: '100%', borderTop: `1px solid ${d3roPalette.border.subtle}`, pt: 3 }}>
|
||||
<Stack spacing={1.5}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
startIcon={busy ? <CircularProgress size={16} /> : <GoogleIcon />}
|
||||
onClick={() => void handleSignIn('google')}
|
||||
disabled={busy}
|
||||
>
|
||||
{t('login.google') ?? 'Google로 계속하기'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
startIcon={<GitHubIcon />}
|
||||
onClick={() => void handleSignIn('github')}
|
||||
disabled={busy}
|
||||
>
|
||||
{t('login.github') ?? 'GitHub로 계속하기'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" variant="outlined" sx={{ width: '100%' }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
{info && !error && (
|
||||
<Alert severity="info" variant="outlined" sx={{ width: '100%' }}>
|
||||
{info}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
...typoSx('label'),
|
||||
color: d3roPalette.text.muted,
|
||||
fontSize: 11,
|
||||
textAlign: 'center',
|
||||
maxWidth: 320
|
||||
}}
|
||||
>
|
||||
{t('login.terms') ?? '계속 진행하면 이용약관 및 개인정보 처리방침에 동의합니다.'}
|
||||
</Box>
|
||||
</Stack>
|
||||
</MetalCard>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue