gpt4 book ai didi

javascript - 在 Google AppS 脚本用户属性中存储 API key 和 secret

转载 作者:行者123 更新时间:2023-12-02 20:53:36 28 4
gpt4 key购买 nike

我对 Google AppScript 还很陌生,正在尝试编写自定义 REST API 的连接器。对于该 API,我需要一个 API key (或 secret ),即每个用户的 key 。由于在脚本中以纯文本形式存储 secret 并不是最好的主意,因此我想将其存储在 Google PropertyService 中并从那里检索它。像这样:

var userProperties = PropertiesService.getUserProperties();
var apiKey = userProperties.getProperty('MY_SECRET')

但我不明白的是,用户如何才能首先存储 key ?我没有找到用户(在本例中为我)可以查看或编辑属性的任何位置。然后我发现这个不错introduction to user properties在脚本容器中创建一个菜单,允许用户手动输入密码。

const API_KEY = 'API_KEY';

var ui = SpreadsheetApp.getUi();
var userProperties = PropertiesService.getUserProperties();


function onOpen(){
ui.createMenu('API Keys')
.addItem('Set API Key', 'userPromptApiKey')
.addItem('Delete API Key', 'deleteApiKey')
.addToUi();
}


function userPromptApiKey(){
var userValue = ui.prompt('API Key ', ui.ButtonSet.OK);
// ToDo: add current key to the prompt
userProperties.setProperty(API_KEY, userValue.getResponseText());
}


function deleteApiKey(){
userProperties.deleteProperty(API_KEY)
}

问题是,我的脚本没有绑定(bind)到任何容器(没有电子表格,没有文档)。相反,我想稍后在 Google DataStudio 中使用它。这就是为什么

SpreadsheetApp.getUi();

不起作用。关于如何处理这个问题有什么想法或建议吗?还有其他推荐的方法来处理这些 secret 吗?

最佳答案

现在,几周后我学到了很多东西。首先,您需要区分 UI 和逻辑脚本。其次,是容器绑定(bind)的脚本还是独立的脚本。

容器绑定(bind)脚本绑定(bind)到 Google 电子表格、Google 文档或允许用户交互的任何其他 UI。在这种情况下,您可以访问代码中的 UI 并向 UI 添加自定义菜单,一旦用户单击该菜单,该菜单将调用脚本中的方法。缺点是您需要知道它是电子表格还是文档,因为 UI 类不同。您还需要指示用户使用自定义菜单输入他或她的凭据。有一个very nice instruction在线的。下面的代码是受指令启发而截取的。确保为 onOpen 创建触发器。

var ui = SpreadsheetApp.getUi();
var userProperties = PropertiesService.getUserProperties();

const API_KEY = 'api.key';

function onOpen(){
ui.createMenu('Credentials & Authentication')
.addItem('Set API key', 'setKey')
.addItem('Delete API key', 'resetKey')
.addItem('Delete all credentials', 'deleteAll')
.addToUi();
}

function setKey(){
var scriptValue = ui.prompt('Please provide your API key.' , ui.ButtonSet.OK);
userProperties.setProperty(API_KEY, scriptValue.getResponseText());
}

function resetKey(){
userProperties.deleteProperty(API_KEY);
}

function deleteAll(){
userProperties.deleteAllProperties();
}

对于独立脚本,您需要找到任何其他方式来连接到 UI。在我的情况下,我正在实现 custom connector for Google Data Studio其中有 a very nice example也在线。有一个相当详细的instruction on authentication和一个API reference on authentication以及。这个custom connector for Kaggle也非常有帮助。它在 Google Data Studio GitHub 上开源。 。以下演示代码的灵感来自于这些示例。查看 getCredentialsvalidateCredentialsgetAuthTyperesetAuthisAuthValidsetCredentials

var cc = DataStudioApp.createCommunityConnector();

const URL_DATA = 'https://www.myverysecretdomain.com/api';
const URL_PING = 'https://www.myverysecretdomain.com/ping';
const AUTH_USER = 'auth.user'
const AUTH_KEY = 'auth.key';
const JSON_TAG = 'user';

String.prototype.format = function() {
// https://coderwall.com/p/flonoa/simple-string-format-in-javascript
a = this;
for (k in arguments) {
a = a.replace("{" + k + "}", arguments[k])
}
return a
}

function httpGet(user, token, url, params) {
try {
// this depends on the URL you are connecting to
var headers = {
'ApiUser': user,
'ApiToken': token,
'User-Agent': 'my super freaky Google Data Studio connector'
};

var options = {
headers: headers
};

if (params && Object.keys(params).length > 0) {
var params_ = [];
for (const [key, value] of Object.entries(params)) {
var value_ = value;
if (Array.isArray(value))
value_ = value.join(',');

params_.push('{0}={1}'.format(key, encodeURIComponent(value_)))
}

var query = params_.join('&');
url = '{0}?{1}'.format(url, query);
}

var response = UrlFetchApp.fetch(url, options);

return {
code: response.getResponseCode(),
json: JSON.parse(response.getContentText())
}
} catch (e) {
throwConnectorError(e);
}
}

function getCredentials() {
var userProperties = PropertiesService.getUserProperties();
return {
username: userProperties.getProperty(AUTH_USER),
token: userProperties.getProperty(AUTH_KEY)
}
}

function validateCredentials(user, token) {
if (!user || !token)
return false;

var response = httpGet(user, token, URL_PING);

if (response.code == 200)
console.log('API key for the user %s successfully validated', user);
else
console.error('API key for the user %s is invalid. Code: %s', user, response.code);

return response;
}

function getAuthType() {
var cc = DataStudioApp.createCommunityConnector();
return cc.newAuthTypeResponse()
.setAuthType(cc.AuthType.USER_TOKEN)
.setHelpUrl('https://www.myverysecretdomain.com/index.html#authentication')
.build();
}

function resetAuth() {
var userProperties = PropertiesService.getUserProperties();
userProperties.deleteProperty(AUTH_USER);
userProperties.deleteProperty(AUTH_KEY);

console.info('Credentials have been reset.');
}

function isAuthValid() {
var credentials = getCredentials()
if (credentials == null) {
console.info('No credentials found.');
return false;
}

var response = validateCredentials(credentials.username, credentials.token);
return (response != null && response.code == 200);
}

function setCredentials(request) {
var credentials = request.userToken;
var response = validateCredentials(credentials.username, credentials.token);

if (response == null || response.code != 200) return { errorCode: 'INVALID_CREDENTIALS' };

var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty(AUTH_USER, credentials.username);
userProperties.setProperty(AUTH_KEY, credentials.token);

console.info('Credentials have been stored');

return {
errorCode: 'NONE'
};
}

function throwConnectorError(text) {
DataStudioApp.createCommunityConnector()
.newUserError()
.setDebugText(text)
.setText(text)
.throwException();
}

function getConfig(request) {
// ToDo: handle request.languageCode for different languages being displayed
console.log(request)

var params = request.configParams;
var config = cc.getConfig();

// ToDo: add your config if necessary

config.setDateRangeRequired(true);
return config.build();
}

function getDimensions() {
var types = cc.FieldType;

return [
{
id:'id',
name:'ID',
type:types.NUMBER
},
{
id:'name',
name:'Name',
isDefault:true,
type:types.TEXT
},
{
id:'email',
name:'Email',
type:types.TEXT
}
];
}

function getMetrics() {
return [];
}

function getFields(request) {
Logger.log(request)

var fields = cc.getFields();

var dimensions = this.getDimensions();
var metrics = this.getMetrics();
dimensions.forEach(dimension => fields.newDimension().setId(dimension.id).setName(dimension.name).setType(dimension.type));
metrics.forEach(metric => fields.newMetric().setId(metric.id).setName(metric.name).setType(metric.type).setAggregation(metric.aggregations));

var defaultDimension = dimensions.find(field => field.hasOwnProperty('isDefault') && field.isDefault == true);
var defaultMetric = metrics.find(field => field.hasOwnProperty('isDefault') && field.isDefault == true);

if (defaultDimension)
fields.setDefaultDimension(defaultDimension.id);
if (defaultMetric)
fields.setDefaultMetric(defaultMetric.id);

return fields;
}

function getSchema(request) {
var fields = getFields(request).build();
return { schema: fields };
}

function convertValue(value, id) {
// ToDo: add special conversion if necessary
switch(id) {
default:
// value will be converted automatically
return value[id];
}
}

function entriesToDicts(schema, data, converter, tag) {

return data.map(function(element) {

var entry = element[tag];
var row = {};
schema.forEach(function(field) {

// field has same name in connector and original data source
var id = field.id;
var value = converter(entry, id);

// use UI field ID
row[field.id] = value;
});

return row;
});
}

function dictsToRows(requestedFields, rows) {
return rows.reduce((result, row) => ([...result, {'values': requestedFields.reduce((values, field) => ([...values, row[field]]), [])}]), []);
}

function getParams (request) {
var schema = this.getSchema();
var params;

if (request) {
params = {};

// ToDo: handle pagination={startRow=1.0, rowCount=100.0}
} else {
// preview only
params = {
limit: 20
}
}

return params;
}

function getData(request) {
Logger.log(request)

var credentials = getCredentials()
var schema = getSchema();
var params = getParams(request);

var requestedFields; // fields structured as I want them (see above)
var requestedSchema; // fields structured as Google expects them
if (request) {
// make sure the ordering of the requested fields is kept correct in the resulting data
requestedFields = request.fields.filter(field => !field.forFilterOnly).map(field => field.name);
requestedSchema = getFields(request).forIds(requestedFields);
} else {
// use all fields from schema
requestedFields = schema.map(field => field.id);
requestedSchema = api.getFields(request);
}

var filterPresent = request && request.dimensionsFilters;
//var filter = ...
if (filterPresent) {
// ToDo: apply request filters on API level (before the API call) to minimize data retrieval from API (number of rows) and increase speed
// see https://developers.google.com/datastudio/connector/filters

// filter = ... // initialize filter
// filter.preFilter(params); // low-level API filtering if possible
}

// get HTTP response; e.g. check for HTTT RETURN CODE on response.code if necessary
var response = httpGet(credentials.username, credentials.token, URL_DATA, params);

// get JSON data from HTTP response
var data = response.json;

// convert the full dataset including all fields (the full schema). non-requested fields will be filtered later on
var rows = entriesToDicts(schema, data, convertValue, JSON_TAG);

// match rows against filter (high-level filtering)
//if (filter)
// rows = rows.filter(row => filter.match(row) == true);

// remove non-requested fields
var result = dictsToRows(requestedFields, rows);

console.log('{0} rows received'.format(result.length));
//console.log(result);

return {
schema: requestedSchema.build(),
rows: result,
filtersApplied: filter ? true : false
};
}

如果这些都不符合您的要求,请选择 WebApp正如@kessy 在另一个答案中所建议的。

关于javascript - 在 Google AppS 脚本用户属性中存储 API key 和 secret ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61540618/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com