mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) add an access token mechanism to help with attachments in custom widgets
Summary:
With this, a custom widget can render an attachment by doing:
```
const tokenInfo = await grist.docApi.getAccessToken({readOnly: true});
const img = document.getElementById('the_image');
const id = record.C[0]; // get an id of an attachment
const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`;
img.setAttribute('src', src)
```
The access token expires after a few mins, so if a user right-clicks on an image
to save it, they may get access denied unless they refresh the page. A little awkward,
but s3 pre-authorized links behave similarly and it generally isn't a deal-breaker.
Test Plan: added tests
Reviewers: dsagal
Reviewed By: dsagal
Subscribers: dsagal
Differential Revision: https://phab.getgrist.com/D3488
This commit is contained in:
@@ -59,6 +59,7 @@ import {FormulaProperties, getFormulaProperties} from 'app/common/GranularAccess
|
||||
import {parseUrlId} from 'app/common/gristUrls';
|
||||
import {byteString, countIf, retryOnce, safeJsonParse} from 'app/common/gutil';
|
||||
import {InactivityTimer} from 'app/common/InactivityTimer';
|
||||
import * as roles from 'app/common/roles';
|
||||
import {schema, SCHEMA_VERSION} from 'app/common/schema';
|
||||
import {MetaRowRecord, SingleCell} from 'app/common/TableData';
|
||||
import {FetchUrlOptions, UploadResult} from 'app/common/uploads';
|
||||
@@ -67,7 +68,7 @@ import {convertFromColumn} from 'app/common/ValueConverter';
|
||||
import {guessColInfoWithDocData} from 'app/common/ValueGuesser';
|
||||
import {parseUserAction} from 'app/common/ValueParser';
|
||||
import {ParseOptions} from 'app/plugin/FileParserAPI';
|
||||
import {GristDocAPI} from 'app/plugin/GristAPI';
|
||||
import {AccessTokenOptions, AccessTokenResult, GristDocAPI} from 'app/plugin/GristAPI';
|
||||
import {compileAclFormula} from 'app/server/lib/ACLFormula';
|
||||
import {Authorizer} from 'app/server/lib/Authorizer';
|
||||
import {checksumFile} from 'app/server/lib/checksumFile';
|
||||
@@ -102,6 +103,7 @@ import {DocClients} from './DocClients';
|
||||
import {DocPluginManager} from './DocPluginManager';
|
||||
import {
|
||||
DocSession,
|
||||
getDocSessionAccess,
|
||||
getDocSessionUser,
|
||||
getDocSessionUserId,
|
||||
makeExceptionalDocSession,
|
||||
@@ -1350,6 +1352,33 @@ export class ActiveDoc extends EventEmitter {
|
||||
return forkIds;
|
||||
}
|
||||
|
||||
public async getAccessToken(docSession: OptDocSession, options: AccessTokenOptions): Promise<AccessTokenResult> {
|
||||
const tokens = this._docManager.gristServer.getAccessTokens();
|
||||
const userId = getDocSessionUserId(docSession);
|
||||
const docId = this.docName;
|
||||
const access = getDocSessionAccess(docSession);
|
||||
// If we happen to be using a "readOnly" connection, max out at "readOnly"
|
||||
// even if user could do more.
|
||||
if (roles.getStrongestRole('viewers', access) === 'viewers') {
|
||||
options.readOnly = true;
|
||||
}
|
||||
// Return a token that can be used to authorize as the given user.
|
||||
if (!userId) { throw new Error('creating access token requires a user'); }
|
||||
const token = await tokens.sign({
|
||||
readOnly: options.readOnly,
|
||||
userId, // definitely do not want userId overridable by options.
|
||||
docId, // likewise for docId.
|
||||
});
|
||||
const ttlMsecs = tokens.getNominalTTLInMsec();
|
||||
const baseUrl = this._options?.docApiUrl;
|
||||
if (!baseUrl) { throw new Error('cannot create token without URLs'); }
|
||||
return {
|
||||
token,
|
||||
baseUrl,
|
||||
ttlMsecs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an ACL formula is valid. If not, will throw an error with an explanation.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user