Layout fix + API proxy + Google auth + managed grammar #4012
2 changed files+55−0
Modifiedapi/.env.example+4−0View fileUnifiedSplit
@@ -7,5 +7,9 @@ CLAUDE_API_KEY=sk-ant-...
77# OpenAI API key (from platform.openai.com)
88OPENAI_API_KEY=sk-...
99
10# Google OAuth Client ID (from console.cloud.google.com)
11# Create at: APIs & Services → Credentials → OAuth 2.0 Client ID
12GOOGLE_CLIENT_ID=xxxxx.apps.googleusercontent.com
13
1014# Server port
1115PORT=3001
Modifiedapi/server.js+51−0View fileUnifiedSplit
@@ -73,6 +73,7 @@ db.exec(`
7373// ── Config ───────────────────────────────────────────────
7474const CLAUDE_API_KEY = process.env.CLAUDE_API_KEY || ''
7575const OPENAI_API_KEY = process.env.OPENAI_API_KEY || ''
76const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || ''
7677const PORT = process.env.PORT || 3001
7778
7879const LIMITS = {
@@ -175,6 +176,56 @@ app.post('/auth/login', async (req, res) => {
175176 }
176177})
177178
179// ── Google Sign-In (one click, no password) ──────────────
180
181app.post('/auth/google', async (req, res) => {
182 try {
183 const { credential } = req.body
184 if (!credential) return res.status(400).json({ error: 'No Google credential provided' })
185
186 // Verify the Google ID token
187 // Google's tokeninfo endpoint validates the JWT without needing a library
188 const verifyResponse = await fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${credential}`)
189 if (!verifyResponse.ok) return res.status(401).json({ error: 'Invalid Google credential' })
190
191 const googleUser = await verifyResponse.json()
192
193 // Verify the token was issued for our app
194 if (GOOGLE_CLIENT_ID && googleUser.aud !== GOOGLE_CLIENT_ID) {
195 return res.status(401).json({ error: 'Google token not issued for this app' })
196 }
197
198 const email = googleUser.email?.toLowerCase()
199 if (!email) return res.status(400).json({ error: 'No email in Google account' })
200
201 // Find or create user
202 let user = db.prepare('SELECT * FROM users WHERE email = ?').get(email)
203
204 if (!user) {
205 // New user — create account (no password needed for Google auth)
206 const id = nanoid()
207 const hash = await bcrypt.hash(nanoid(32), 10) // random password placeholder
208 db.prepare('INSERT INTO users (id, email, password_hash) VALUES (?, ?, ?)').run(id, email, hash)
209 user = { id, email, plan: 'free' }
210 }
211
212 // Create session
213 const token = nanoid(32)
214 const expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
215 db.prepare('INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)').run(token, user.id, expires)
216
217 res.json({
218 token,
219 plan: user.plan || 'free',
220 email: user.email,
221 name: googleUser.name || '',
222 })
223 } catch (err) {
224 console.error('Google auth error:', err)
225 res.status(500).json({ error: 'Google sign-in failed. Try again.' })
226 }
227})
228
178229// ── Grammar Check (Claude Haiku — fast + cheap) ─────────
179230
180231app.post('/grammar', async (req, res) => {
181232
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts