diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 6b85bdfe1c..fe82e544e7 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -25,10 +25,16 @@ "morgan": "^1.10.0", "winston": "^3.2.1", "yn": "^4.0.0", - "passport": "0.4.1", - "passport-google-oauth20": "2.0.0", - "@types/passport": "1.0.3", - "@types/passport-google-oauth20": "2.0.3" + "passport": "^0.4.1", + "passport-google-oauth20": "^2.0.0", + "passport-oauth2-refresh": "^2.0.0", + "passport-oauth2": "^1.5.0", + "cookie-parser": "^1.4.5", + "@types/passport-oauth2-refresh": "^1.1.1", + "@types/passport": "^1.0.3", + "@types/passport-google-oauth20": "^2.0.3", + "@types/cookie-parser": "^1.4.2", + "@types/passport-oauth2": "^1.4.9" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.6", diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 0ef7e19d3e..03f6424296 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -15,8 +15,9 @@ */ import passport from 'passport'; -import express from 'express'; +import express, { CookieOptions } from 'express'; import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; +import refresh from 'passport-oauth2-refresh'; import { AuthProvider, AuthProviderRouteHandlers, @@ -25,6 +26,7 @@ import { import { postMessageResponse } from './../utils'; import { InputError } from '@backstage/backend-common'; +const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000; export class GoogleAuthProvider implements AuthProvider, AuthProviderRouteHandlers { private readonly providerConfig: AuthProviderConfig; @@ -54,6 +56,18 @@ export class GoogleAuthProvider next: express.NextFunction, ) { return passport.authenticate('google', (_, user) => { + const { refreshToken } = user; + delete user.refreshToken; + + const options: CookieOptions = { + maxAge: THOUSAND_DAYS_MS, + secure: false, + sameSite: 'none', + path: 'localhost:3000/auth/google', + httpOnly: true, + }; + + res.cookie('grtoken', refreshToken, options); postMessageResponse(res, { type: 'auth-result', payload: user, @@ -62,7 +76,33 @@ export class GoogleAuthProvider } async logout(_req: express.Request, res: express.Response) { - res.send('logout!'); + return res.send('logout!'); + } + + async refresh(req: express.Request, res: express.Response) { + const refreshToken = req.cookies['grtoken']; + const scope = req.query.scope?.toString() ?? ''; + const params = scope ? { scope } : {}; + + if (!refreshToken) { + return res.status(401).send('Missing session cookie'); + } + + refresh.requestNewAccessToken( + 'google', + refreshToken, + params, + (err, accessToken) => { + if (err || !accessToken) { + return res.status(401).send('Failed to refresh access token'); + } + + return res.send({ + accessToken, + expiresInSeconds: 36000, + }); + }, + ); } strategy(): passport.Strategy { diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 6658299033..68fe61c7a6 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -24,7 +24,7 @@ export const defaultRouter = (provider: AuthProviderRouteHandlers) => { router.get('/handler/frame', provider.frameHandler); router.get('/logout', provider.logout); if (provider.refresh) { - router.get('/refreshToken', provider.refresh); + router.get('/refresh', provider.refresh); } return router; }; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index cdde95d9d0..a2e3b3e1db 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -17,6 +17,9 @@ import express from 'express'; import Router from 'express-promise-router'; import passport from 'passport'; +import cookieParser from 'cookie-parser'; +import refresh from 'passport-oauth2-refresh'; +import OAuth2Strategy from 'passport-oauth2'; import { Logger } from 'winston'; import { providers } from './../providers/config'; import { makeProvider } from '../providers'; @@ -39,6 +42,9 @@ export async function createRouter( ); logger.info(`Configuring provider: ${providerId}`); passport.use(strategy); + if (strategy instanceof OAuth2Strategy) { + refresh.use(strategy); + } providerRouters[providerId] = providerRouter; } @@ -52,6 +58,7 @@ export async function createRouter( router.use(passport.initialize()); router.use(passport.session()); + router.use(cookieParser()); for (const providerId in providerRouters) { if (providerRouters.hasOwnProperty(providerId)) { diff --git a/yarn.lock b/yarn.lock index 074279db2b..0d49abe417 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3861,6 +3861,13 @@ dependencies: "@types/node" "*" +"@types/cookie-parser@^1.4.2": + version "1.4.2" + resolved "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz#e4d5c5ffda82b80672a88a4281aaceefb1bd9df5" + integrity sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg== + dependencies: + "@types/express" "*" + "@types/cookiejar@*": version "2.1.1" resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" @@ -4181,7 +4188,7 @@ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/passport-google-oauth20@2.0.3": +"@types/passport-google-oauth20@^2.0.3": version "2.0.3" resolved "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.3.tgz#f554ff6d39f395acff3f1d762e54462194dac8da" integrity sha512-6EUEGzEg4acwowvgR/yVZIj8S2Kkwc6JmlY2/wnM1wJHNz20o7s1TIGrxnah8ymLgJasYDpy95P3TMMqlmetPw== @@ -4190,7 +4197,15 @@ "@types/passport" "*" "@types/passport-oauth2" "*" -"@types/passport-oauth2@*": +"@types/passport-oauth2-refresh@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@types/passport-oauth2-refresh/-/passport-oauth2-refresh-1.1.1.tgz#cbe466d4fcac36182fd75bf55279c0b1e953c382" + integrity sha512-Tw0JvfDPv9asgFPACd9oOGCaD/0/Uyi+QF7fmrJC74cJKC6I8N8wwhJJHyfd1N2E/qaLgTh431lhOa9jicpNdg== + dependencies: + "@types/oauth" "*" + "@types/passport-oauth2" "*" + +"@types/passport-oauth2@*", "@types/passport-oauth2@^1.4.9": version "1.4.9" resolved "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.9.tgz#134007c4b505a82548c9cb19094c5baeb2205c92" integrity sha512-QP0q+NVQOaIu2r0e10QWkiUA0Ya5mOBHRJN0UrI+LolMLOP1/VN4EVIpJ3xVwFo+xqNFRoFvFwJhBvKnk7kpUA== @@ -4199,7 +4214,7 @@ "@types/oauth" "*" "@types/passport" "*" -"@types/passport@*", "@types/passport@1.0.3": +"@types/passport@*", "@types/passport@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@types/passport/-/passport-1.0.3.tgz#e459ed6c262bf0686684d1b05901be0d0b192a9c" integrity sha512-nyztuxtDPQv9utCzU0qW7Gl8BY2Dn8BKlYAFFyxKipFxjaVd96celbkLCV/tRqqBUZ+JB8If3UfgV8347DTo3Q== @@ -7241,6 +7256,14 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, dependencies: safe-buffer "~5.1.1" +cookie-parser@^1.4.5: + version "1.4.5" + resolved "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz#3e572d4b7c0c80f9c61daf604e4336831b5d1d49" + integrity sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -15987,14 +16010,19 @@ pascalcase@^0.1.1: resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -passport-google-oauth20@2.0.0: +passport-google-oauth20@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef" integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ== dependencies: passport-oauth2 "1.x.x" -passport-oauth2@1.x.x: +passport-oauth2-refresh@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/passport-oauth2-refresh/-/passport-oauth2-refresh-2.0.0.tgz#7b19c77ff3cc000819c69f6ad9e318450f57b85e" + integrity sha512-yXvCB6nem/O+WThhiyI3TlPXpzSGY+9+hy9OTx9QF8e9GInplyRHxHaaOhFylKvnof9UmWHAufQFZk8cO1Fb2g== + +passport-oauth2@1.x.x, passport-oauth2@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108" integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ== @@ -16010,7 +16038,7 @@ passport-strategy@1.x.x: resolved "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= -passport@0.4.1: +passport@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==