google-oauth.js
2.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { google } from 'googleapis'
/**
* @param {{ clientId: string, clientSecret: string|null }} config
* @returns {import('@drama/auth').OauthPort}
*/
export function createGoogleOauthPort({ clientId, clientSecret }) {
if (!clientId) throw new Error('createGoogleOauthPort: clientId is required')
const client = new google.auth.OAuth2(clientId, clientSecret || undefined)
return {
async exchange({ provider, code, oauth_flow, redirect_uri }) {
if (provider !== 'google') throw new Error(`Unsupported OAuth provider: ${provider}`)
if (oauth_flow === 'authorization_code') {
if (!clientSecret) throw new Error('Google OAuth authorization code flow requires GOOGLE_OAUTH_CLIENT_SECRET')
if (!redirect_uri) throw new Error('Google OAuth authorization code flow requires redirect_uri')
const { tokens } = await client.getToken({ code, redirect_uri })
if (!tokens.id_token) throw new Error('Google OAuth token response is missing id_token')
const ticket = await client.verifyIdToken({ idToken: tokens.id_token, audience: clientId })
const payload = ticket.getPayload()
if (!payload?.sub || !payload.email) throw new Error('Google ID token is missing account identity')
return {
provider_user_id: payload.sub,
email: payload.email,
email_verified: payload.email_verified === true,
}
}
if (oauth_flow !== 'access_token') throw new Error(`Unsupported Google OAuth flow: ${oauth_flow}`)
const tokenInfo = await client.getTokenInfo(code)
if (tokenInfo.aud !== clientId) throw new Error('Google access token audience does not match client ID')
const response = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
headers: { authorization: `Bearer ${code}` },
})
if (!response.ok) throw new Error(`Google userinfo request failed: HTTP ${response.status}`)
const payload = await response.json()
if (!payload.sub || !payload.email) throw new Error('Google userinfo is missing account identity')
return {
provider_user_id: payload.sub,
email: payload.email,
email_verified: payload.email_verified === true,
}
},
}
}