Как встроить автономное веб-приложение Google Apps Script, требующее авторизации, в новые Сайты Google? - PullRequest
2 голосов
/ 12 июня 2019

Я создал автономное веб-приложение Google Apps Script, которое я пытаюсь встроить в новые Сайты Google.Он работает правильно, когда я вошел в учетную запись, используемую для создания проекта Apps Script.Однако, если я вошел в другую учетную запись, которая еще не авторизовала веб-приложение, страница Сайтов Google загружается, но iFrame с проектом встроенного скрипта приложений загружается неправильно.

Вместо этого отображается iFrame "account.google.com отказался подключиться », и на консоли отображается« Отказано в отображении »https://accounts.google.com/ServiceLogin?passive=1209600&continue=https%3A%2F%2Fscript.google.com%2Fmacros%2Fs%2FAKfycbzizTNkflSXZbKSF8TTxTR5QoF4LAhPPuSq-1juFdIOdL_IlFM%2Fexec&followup=https%3A%2F%2Fscript.google.com%2Fmacros%2Fs%2FAKfycbzizTNkflSXZbKSF8TTxTR5QoF4LAhPPuSq-1juFdIOdL_IlFM%2Fexec' в кадре, поскольку для параметра« X-Frame-Options »установлено значение« deny »."

КакЯ понимаю, что новые пользователи не авторизованы для моего приложения Apps Script Web, что вызывает поток авторизации.Однако, когда процесс авторизации начинается с загрузки страницы входа в Google (https://accounts.google.com/ServiceLogin?... сверху), он прерывается, поскольку для заголовка X-Frame-Options для страницы входа установлено значение Запретить.

Я экспериментировал с HTMLoutput.setXFrameOptionsMode (HtmlService.XFrameOptionsMode.ALLOWALL) (см. https://developers.google.com/apps-script/reference/html/html-output#setxframeoptionsmodemode),, но я почти уверен, что проблема, приводящая к неправильной загрузке iFrame Сайтов Google, не является моим приложением, ноЗнак Google на странице.

Ссылка на сайт Google: https://sites.google.com/view/create-user-filter-views/home

Ссылка на веб-приложение Script Apps: https://script.google.com/macros/s/AKfycbzizTNkflSXZbKSF8TTxTR5QoF4LAhPPuSq-1juFdIOdL_IlFM/exec

Документация от Google по внедрению скрипта Apps.на новых сайтах: https://developers.google.com/apps-script/guides/web#embedding_a_web_app_in_new_sites

Как авторизовать новых пользователей в моем веб-приложении с Сайтов Google?

Нужно ли сначала направлять их на сайт сценариев опубликованных приложений, чтобы пройти черезпоток авторизации, а затем направить их на мой сайт Google (очевидно, это был бы ужасный вариант)?

1 Ответ

1 голос
/ 16 июня 2019

Прежде всего, вы правы в своем анализе.На странице входа Google (и на самом деле это большой процент размещенного в Google контента) X-Frame-Options отключено, а перенаправление заблокировано для загрузки внутри iframe из-за этого параметра.Если пользователь уже вошел в Google, но не авторизовал приложение, я считаю, что большую часть времени они должны видеть диалог авторизации в iframe без ошибок (о чем Алан Уэллс сообщал).Тем не менее, я не проверил полностью, и это может быть для пользователей с несколькими одновременными входами (например, вошли в несколько Gmail), он выгонит вас на страницу входа и вызовет блок X-Frame-Options.

* 1002В любом случае, после некоторого раскопок, я нашел рабочее решение для этого.Это немного глупо из-за всех различных ограничений, которые Apps Script накладывает на то, что можно использовать.Например, я сначала хотел использовать postMessage для передачи сообщения от встроенного iframe на родительскую страницу, и если родитель не получил сообщение в X # секунд, он бы предположил, что iframe не удалось загрузить, и будетперенаправить пользователя для входа / авторизации приложения.Увы, postMessage не очень хорошо работает со скриптом приложений из-за того, что они дважды встраивают фреймы.

решение:

Решение, которое я получил, заключалось в использовании подхода JSONP.Это кратко упомянуто Google здесь .Во-первых, поместите оверлей над iframe, который предлагает пользователю аутентифицировать приложение, со ссылкой для этого.Затем вы дважды загружаете скрипт приложения, один раз как iframe, а затем снова как тег <script></script>.Если тег <script> загружается успешно, он вызывает функцию обратного вызова, которая скрывает наложение подсказки, так что нижний iframe может стать видимым.

Вот мой код, урезанный, чтобы вы могли увидеть, как это работает:

Emedded HTML:

<style>
.appsWidgetWrapper {
    position: fixed;
}
.appsWidget {
    width: 100%;
    height: 100%;
    border: none !important;
}
.loggedOut {
    top: 0px;
    left: 0px;
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: darksalmon;
    text-align: center;
}
</style>

<!-- Script loaded as iframe widget with fallback -->
<div class="appsWidgetWrapper">
    <iframe class="appsWidget" src="https://script.google.com/macros/s/SCRIPT_ID/exec?embedIframe"></iframe>
    <div class="loggedOut">
        <div class="loggedOutContent">
            <div class="loggedOutText">You need to "authorize" this widget.</div>
            <button class="authButton">Log In / Authorize</button>
        </div>
    </div>
</div>

<!-- Define JSONP callback and authbutton redirect-->
<script>
    function authSuccess(email){
        console.log(email);
        // Hide auth prompt overlay
        document.querySelector('.loggedOut').style.display = 'none';
    }
    document.querySelectorAll('.authButton').forEach(function(elem){
        elem.addEventListener('click',function(evt){
            var currentUrl = document.location.href;
            var authPage = 'https://script.google.com/macros/s/SCRIPT_ID/exec?auth=true&redirect=' + encodeURIComponent(currentUrl);
            window.open(authPage,'_blank');
        });
    });
</script>

<!-- Fetch script as JSONP with callback -->
<script src="https://script.google.com/macros/s/SCRIPT_ID/exec?jsonpCallback=authSuccess"></script>

И Code.gs (Apps Script)

function doGet(e) {
    var email = Session.getActiveUser().getEmail();

    if (e.queryString && 'jsonpCallback' in e.parameter){
        // JSONP callback
        // Get the string name of the callback function
        var cbFnName = e.parameter['jsonpCallback'];
        // Prepare stringified JS that will get evaluated when called from <script></script> tag
        var scriptText = "window." + cbFnName + "('" + email + "');";
        // Return proper MIME type for JS
        return ContentService.createTextOutput(scriptText).setMimeType(ContentService.MimeType.JAVASCRIPT);
    }

    else if (e.queryString && ('auth' in e.parameter || 'redirect' in e.parameter)){
        // Script was opened in order to auth in new tab
        var rawHtml = '<p>You have successfully authorized the widget. You can now close this tab and refresh the page you were previously on.</p>';
        if ('redirect' in e.parameter){
            rawHtml += '<br/><a href="' + e.parameter['redirect'] + '">Previous Page</a>';
        }
        return HtmlService.createHtmlOutput(rawHtml);
    }
    else {
        // Display HTML in iframe
        var rawHtml = "<h1>App Script successfully loaded in iframe!</h1>"
            + "\n"
            + "<h2>User's email used to authorize: <?= authedEmail ?></h2>";
        var template = HtmlService.createTemplate(rawHtml);
        template.authedEmail = email;
        return template.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
    }
}

В этом примере «authSuccess» - это моя функция обратного вызова JSONP, которая должна получитьвызывается по электронной почте авторизованного пользователя, если сценарий выполнен успешно.В противном случае, если пользователю необходимо войти в систему или авторизоваться, это не произойдет, и наложение останется видимым и заблокирует отображение ошибки iframe для пользователя.

Я также разместил полный код вGithub , где структура может быть немного легче увидеть.

...