diff --git a/.changeset/rare-fishes-sort.md b/.changeset/rare-fishes-sort.md new file mode 100644 index 00000000..1c8119a7 --- /dev/null +++ b/.changeset/rare-fishes-sort.md @@ -0,0 +1,5 @@ +--- +"@hono/oauth-providers": patch +--- + +fix(@hono/oauth-providers): google provider attach custom parameters diff --git a/packages/oauth-providers/src/providers/google/authFlow.ts b/packages/oauth-providers/src/providers/google/authFlow.ts index a5fe6d01..9435dd7a 100644 --- a/packages/oauth-providers/src/providers/google/authFlow.ts +++ b/packages/oauth-providers/src/providers/google/authFlow.ts @@ -14,6 +14,7 @@ type GoogleAuthFlow = { state?: string login_hint?: string prompt?: 'none' | 'consent' | 'select_account' + access_type?: 'offline' | 'online' } export class AuthFlow { @@ -28,6 +29,7 @@ export class AuthFlow { prompt: 'none' | 'consent' | 'select_account' | undefined user: Partial | undefined granted_scopes: string[] | undefined + access_type: 'offline' | 'online' | undefined constructor({ client_id, @@ -39,6 +41,7 @@ export class AuthFlow { state, code, token, + access_type, }: GoogleAuthFlow) { this.client_id = client_id this.client_secret = client_secret @@ -51,6 +54,7 @@ export class AuthFlow { this.token = token this.user = undefined this.granted_scopes = undefined + this.access_type = access_type if ( this.client_id === undefined || @@ -71,6 +75,9 @@ export class AuthFlow { include_granted_scopes: true, scope: this.scope.join(' '), state: this.state, + prompt: this.prompt, + login_hint: this.login_hint, + access_type: this.access_type, }) return `https://accounts.google.com/o/oauth2/v2/auth?${parsedOptions}` } diff --git a/packages/oauth-providers/src/providers/google/googleAuth.ts b/packages/oauth-providers/src/providers/google/googleAuth.ts index eac4edd8..1bf4b5ee 100644 --- a/packages/oauth-providers/src/providers/google/googleAuth.ts +++ b/packages/oauth-providers/src/providers/google/googleAuth.ts @@ -1,6 +1,6 @@ import type { MiddlewareHandler } from 'hono' -import { getCookie, setCookie } from 'hono/cookie' import { env } from 'hono/adapter' +import { getCookie, setCookie } from 'hono/cookie' import { HTTPException } from 'hono/http-exception' import { getRandomState } from '../../utils/getRandomState' @@ -10,6 +10,7 @@ export function googleAuth(options: { scope: string[] login_hint?: string prompt?: 'none' | 'consent' | 'select_account' + access_type?: 'online' | 'offline' client_id?: string client_secret?: string state?: string @@ -24,6 +25,7 @@ export function googleAuth(options: { redirect_uri: options.redirect_uri || c.req.url.split('?')[0], login_hint: options.login_hint, prompt: options.prompt, + access_type: options.access_type, scope: options.scope, state: newState, code: c.req.query('code'), diff --git a/packages/oauth-providers/test/index.test.ts b/packages/oauth-providers/test/index.test.ts index ac151843..d8f58dbe 100644 --- a/packages/oauth-providers/test/index.test.ts +++ b/packages/oauth-providers/test/index.test.ts @@ -70,6 +70,18 @@ describe('OAuth Middleware', () => { redirect_uri: 'http://localhost:3000/google', })(c, next) }) + app.use('/google-custom-params', (c, next) => { + return googleAuth({ + client_id, + client_secret, + scope: ['openid', 'email', 'profile'], + redirect_uri: 'http://localhost:3000/google', + login_hint: 'test-login-hint', + prompt: 'select_account', + state: 'test-state', + access_type: 'offline', + })(c, next) + }) app.get('/google', (c) => { const user = c.get('user-google') const token = c.get('token') @@ -356,6 +368,21 @@ describe('OAuth Middleware', () => { expect(redirectUrl.searchParams.get('redirect_uri')).toBe('http://localhost:3000/google') }) + it('Should attach custom parameters', async () => { + const res = await app.request('/google-custom-params') + expect(res).not.toBeNull() + expect(res.status).toBe(302) + + const redirectLocation = res.headers.get('location')! + const redirectUrl = new URL(redirectLocation) + expect(redirectUrl.searchParams.get('redirect_uri')).toBe('http://localhost:3000/google') + expect(redirectUrl.searchParams.get('scope')).toBe('openid email profile') + expect(redirectUrl.searchParams.get('login_hint')).toBe('test-login-hint') + expect(redirectUrl.searchParams.get('prompt')).toBe('select_account') + expect(redirectUrl.searchParams.get('state')).toBe('test-state') + expect(redirectUrl.searchParams.get('access_type')).toBe('offline') + }) + it('Prevent CSRF attack', async () => { const res = await app.request(`/google?code=${dummyCode}&state=malware-state`)