(core) Google auth endpoint has not responded with auth code

Summary:
Google Auth popup wasn't able to resolve origin from gristConfig.
Moving this reponsability to server side, where it gets calculated from initial request.

Test Plan: n/a

Reviewers: dsagal, paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2935
This commit is contained in:
Jarosław Sadziński 2021-07-21 18:38:57 +02:00
parent 08295a696b
commit 291bcd17ff
5 changed files with 20 additions and 18 deletions

View File

@ -11,7 +11,7 @@ export async function makeXLSX(
activeDoc: ActiveDoc, activeDoc: ActiveDoc,
req: express.Request): Promise<ArrayBuffer> { req: express.Request): Promise<ArrayBuffer> {
const content = await exportDoc(activeDoc, req); const content = await exportDoc(activeDoc, req);
const data = await convertToExcel(content, req.host === 'localhost'); const data = await convertToExcel(content, req.hostname === 'localhost');
return data; return data;
} }

View File

@ -1232,7 +1232,7 @@ export class FlexServer implements GristServer {
public addGoogleAuthEndpoint() { public addGoogleAuthEndpoint() {
if (this._check('google-auth')) { return; } if (this._check('google-auth')) { return; }
const messagePage = makeMessagePage(this, getAppPathTo(this.appRoot, 'static')); const messagePage = makeMessagePage(getAppPathTo(this.appRoot, 'static'));
addGoogleAuthEndpoint(this.app, messagePage); addGoogleAuthEndpoint(this.app, messagePage);
} }

View File

@ -1,10 +1,10 @@
import {auth} from '@googleapis/oauth2'; import { auth } from '@googleapis/oauth2';
import {ApiError} from 'app/common/ApiError'; import { ApiError } from 'app/common/ApiError';
import {parseSubdomain} from 'app/common/gristUrls'; import { parseSubdomain } from 'app/common/gristUrls';
import {expressWrap} from 'app/server/lib/expressWrap'; import { expressWrap } from 'app/server/lib/expressWrap';
import * as log from 'app/server/lib/log'; import * as log from 'app/server/lib/log';
import * as express from 'express'; import * as express from 'express';
import {URL} from 'url'; import { URL } from 'url';
/** /**
* Google Auth Endpoint for performing server side authentication. More information can be found * Google Auth Endpoint for performing server side authentication. More information can be found
@ -130,15 +130,18 @@ export function addGoogleAuthEndpoint(
log.info(`GoogleAuth - auth handler at ${getFullAuthEndpointUrl()}`); log.info(`GoogleAuth - auth handler at ${getFullAuthEndpointUrl()}`);
expressApp.get(authHandlerPath, expressWrap(async (req: express.Request, res: express.Response) => { expressApp.get(authHandlerPath, expressWrap(async (req: express.Request, res: express.Response) => {
// Test if the code is in a query string. Google sends it back after user has given a concent for // Test if the code is in a query string. Google sends it back after user has given a concent for
// our request. It is encrypted (with CLIENT_SECRET) and signed with redirect url. // our request. It is encrypted (with CLIENT_SECRET) and signed with redirect url.
// In state query parameter we will receive an url that was send as part of the request to Google.
if (req.query.code) { if (req.query.code) {
log.debug("GoogleAuth - response from Google with valid code"); log.debug("GoogleAuth - response from Google with valid code");
messagePage(req, res, { code: req.query.code }); messagePage(req, res, { code: req.query.code, origin: req.query.state });
} else if (req.query.error) { } else if (req.query.error) {
log.debug("GoogleAuth - response from Google with error code", req.query.error); log.debug("GoogleAuth - response from Google with error code", req.query.error);
if (req.query.error === "access_denied") { if (req.query.error === "access_denied") {
messagePage(req, res, { error: req.query.error }); messagePage(req, res, { error: req.query.error, origin: req.query.state });
} else { } else {
// This should not happen, either code or error is a mandatory query parameter. // This should not happen, either code or error is a mandatory query parameter.
throw new ApiError("Error authenticating with Google", 500); throw new ApiError("Error authenticating with Google", 500);
@ -146,13 +149,17 @@ export function addGoogleAuthEndpoint(
} else { } else {
const oAuth2Client = _googleAuthClient(); const oAuth2Client = _googleAuthClient();
const scope = req.query.scope || DRIVE_SCOPE; const scope = req.query.scope || DRIVE_SCOPE;
// Create url for origin parameter for a popup window.
const origin = `${req.protocol}://${req.headers.host}`;
const authUrl = oAuth2Client.generateAuthUrl({ const authUrl = oAuth2Client.generateAuthUrl({
scope, scope,
prompt: 'select_account' prompt: 'select_account',
state: origin
}); });
log.debug(`GoogleAuth - redirecting to Google consent screen`, { log.debug(`GoogleAuth - redirecting to Google consent screen`, {
authUrl, authUrl,
scope scope,
state: origin
}); });
res.redirect(authUrl); res.redirect(authUrl);
} }

View File

@ -51,12 +51,10 @@ export function makeGristConfig(homeUrl: string|null, extra: Partial<GristLoadCo
* Primary used for Google Auth Grist's endpoint, but can be used in future in any other server side * Primary used for Google Auth Grist's endpoint, but can be used in future in any other server side
* authentication flow. * authentication flow.
*/ */
export function makeMessagePage(server: GristServer, staticDir: string) { export function makeMessagePage(staticDir: string) {
return async (req: express.Request, resp: express.Response, message: any) => { return async (req: express.Request, resp: express.Response, message: any) => {
const config = server.getGristConfig();
const fileContent = await fse.readFile(path.join(staticDir, "message.html"), 'utf8'); const fileContent = await fse.readFile(path.join(staticDir, "message.html"), 'utf8');
const content = fileContent const content = fileContent
.replace("<!-- INSERT CONFIG -->", `<script>window.gristConfig = ${JSON.stringify(config)};</script>`)
.replace("<!-- INSERT MESSAGE -->", `<script>window.message = ${JSON.stringify(message)};</script>`); .replace("<!-- INSERT MESSAGE -->", `<script>window.message = ${JSON.stringify(message)};</script>`);
resp.status(200).type('html').send(content); resp.status(200).type('html').send(content);
}; };

View File

@ -4,12 +4,9 @@
<meta charset="utf8"> <meta charset="utf8">
</head> </head>
<body> <body>
<!-- INSERT CONFIG -->
<!-- INSERT MESSAGE --> <!-- INSERT MESSAGE -->
<script> <script>
// Determine proper home url for origin parameter window.opener.postMessage(message, message.origin);
const origin = gristConfig.pathOnly ? gristConfig.homeUrl : `https://${gristConfig.org}${gristConfig.baseDomain}`;
window.opener.postMessage(message, origin);
</script> </script>
</body> </body>
</html> </html>