Skip to content

Commit

Permalink
Add token cache support in alfred (#15612)
Browse files Browse the repository at this point in the history
This PR includes changes below:
1. Add token cache in Alfred. It reuse the single token cache as the
cache for all tokens. And there is no need to have a separate single
token use cache if token cache is enabled
2. Add a new function verifyToken with taken cache and refactor
verifyStorageToken to use this new function. It should be used in other
places where we need to verify token.

---------

Co-authored-by: Yunpeng Hou <[email protected]>
  • Loading branch information
yunho-microsoft and Yunpeng Hou authored May 17, 2023
1 parent deaf8b9 commit eac5a06
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
IThrottleMiddlewareOptions,
getParam,
getCorrelationId,
getBooleanFromConfig,
verifyToken,
} from "@fluidframework/server-services-utils";
import { validateRequestParams, handleResponse } from "@fluidframework/server-services";
import { Request, Router } from "express";
Expand All @@ -40,6 +42,7 @@ export function create(
tenantManager: core.ITenantManager,
storage: core.IDocumentStorage,
tenantThrottlers: Map<string, core.IThrottler>,
jwtTokenCache?: core.ICache,
tokenManager?: core.ITokenRevocationManager,
): Router {
const router: Router = Router();
Expand All @@ -50,6 +53,12 @@ export function create(
};
const generalTenantThrottler = tenantThrottlers.get(Constants.generalRestCallThrottleIdPrefix);

// Jwt token cache
const enableJwtTokenCache: boolean = getBooleanFromConfig(
"alfred:jwtTokenCache:enable",
config,
);

function handlePatchRootSuccess(request: Request, opBuilder: (request: Request) => any[]) {
const tenantId = getParam(request.params, "tenantId");
const documentId = getParam(request.params, "id");
Expand Down Expand Up @@ -83,6 +92,8 @@ export function create(
storage,
maxTokenLifetimeSec,
isTokenExpiryEnabled,
enableJwtTokenCache,
jwtTokenCache,
tokenManager,
);
handleResponse(
Expand Down Expand Up @@ -200,32 +211,44 @@ const verifyRequest = async (
storage: core.IDocumentStorage,
maxTokenLifetimeSec: number,
isTokenExpiryEnabled: boolean,
tokenCacheEnabled: boolean,
tokenCache?: core.ICache,
tokenManager?: core.ITokenRevocationManager,
) =>
Promise.all([
verifyToken(
verifyTokenWrapper(
request,
tenantManager,
maxTokenLifetimeSec,
isTokenExpiryEnabled,
tokenCacheEnabled,
tokenCache,
tokenManager,
),
checkDocumentExistence(request, storage),
]);

async function verifyToken(
async function verifyTokenWrapper(
request: Request,
tenantManager: core.ITenantManager,
maxTokenLifetimeSec: number,
isTokenExpiryEnabled: boolean,
tokenCacheEnabled: boolean,
tokenCache?: core.ICache,
tokenManager?: core.ITokenRevocationManager,
): Promise<void> {
const token = request.headers["access-token"] as string;
if (!token) {
return Promise.reject(new Error("Missing access token"));
return Promise.reject(new Error("Missing access token in request header."));
}
const tenantId = getParam(request.params, "tenantId");
if (!tenantId) {
return Promise.reject(new Error("Missing tenantId in request."));
}
const documentId = getParam(request.params, "id");
if (!documentId) {
return Promise.reject(new Error("Missing documentId in request."));
}
const claims = validateTokenClaims(token, documentId, tenantId);
if (isTokenExpiryEnabled) {
validateTokenClaimsExpiration(claims, maxTokenLifetimeSec);
Expand All @@ -238,6 +261,19 @@ async function verifyToken(
}
}

if (tokenCacheEnabled && tokenCache) {
const options = {
requireDocumentId: true,
requireTokenExpiryCheck: isTokenExpiryEnabled,
maxTokenLifetimeSec,
ensureSingleUseToken: false,
singleUseTokenCache: undefined,
enableTokenCache: true,
tokenCache,
};
return verifyToken(tenantId, documentId, token, tenantManager, options);
}

return tenantManager.verifyToken(claims.tenantId, token);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import {
ICache,
IDeltaService,
ITenantManager,
IThrottler,
Expand All @@ -14,6 +15,7 @@ import {
throttle,
IThrottleMiddlewareOptions,
getParam,
getBooleanFromConfig,
} from "@fluidframework/server-services-utils";
import { validateRequestParams, handleResponse } from "@fluidframework/server-services";
import { Router } from "express";
Expand All @@ -29,6 +31,7 @@ export function create(
appTenants: IAlfredTenant[],
tenantThrottlers: Map<string, IThrottler>,
clusterThrottlers: Map<string, IThrottler>,
jwtTokenCache?: ICache,
tokenManager?: ITokenRevocationManager,
): Router {
const deltasCollectionName = config.get("mongo:collectionNames:deltas");
Expand All @@ -51,6 +54,20 @@ export function create(
throttleIdSuffix: Constants.alfredRestThrottleIdSuffix,
};

// Jwt token cache
const enableJwtTokenCache: boolean = getBooleanFromConfig(
"alfred:jwtTokenCache:enable",
config,
);

const defaultTokenValidationOptions = {
requireDocumentId: true,
ensureSingleUseToken: false,
singleUseTokenCache: undefined,
enableTokenCache: enableJwtTokenCache,
tokenCache: jwtTokenCache,
};

function stringToSequenceNumber(value: any): number {
if (typeof value !== "string") {
return undefined;
Expand All @@ -66,7 +83,7 @@ export function create(
router.get(
["/v1/:tenantId/:id", "/:tenantId/:id/v1"],
validateRequestParams("tenantId", "id"),
verifyStorageToken(tenantManager, config, tokenManager),
verifyStorageToken(tenantManager, config, tokenManager, defaultTokenValidationOptions),
throttle(generalTenantThrottler, winston, tenantThrottleOptions),
(request, response, next) => {
const from = stringToSequenceNumber(request.query.from);
Expand All @@ -92,7 +109,7 @@ export function create(
router.get(
"/raw/:tenantId/:id",
validateRequestParams("tenantId", "id"),
verifyStorageToken(tenantManager, config, tokenManager),
verifyStorageToken(tenantManager, config, tokenManager, defaultTokenValidationOptions),
throttle(generalTenantThrottler, winston, tenantThrottleOptions),
(request, response, next) => {
const tenantId = getParam(request.params, "tenantId") || appTenants[0].id;
Expand Down Expand Up @@ -124,7 +141,7 @@ export function create(
winston,
getDeltasTenantThrottleOptions,
),
verifyStorageToken(tenantManager, config, tokenManager),
verifyStorageToken(tenantManager, config, tokenManager, defaultTokenValidationOptions),
(request, response, next) => {
const from = stringToSequenceNumber(request.query.from);
const to = stringToSequenceNumber(request.query.to);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
IThrottleMiddlewareOptions,
getParam,
validateTokenScopeClaims,
getBooleanFromConfig,
} from "@fluidframework/server-services-utils";
import { validateRequestParams, handleResponse } from "@fluidframework/server-services";
import { Router } from "express";
Expand Down Expand Up @@ -86,10 +87,24 @@ export function create(
throttleIdSuffix: Constants.alfredRestThrottleIdSuffix,
};

// Jwt token cache
const enableJwtTokenCache: boolean = getBooleanFromConfig(
"alfred:jwtTokenCache:enable",
config,
);

const defaultTokenValidationOptions = {
requireDocumentId: true,
ensureSingleUseToken: false,
singleUseTokenCache: undefined,
enableTokenCache: enableJwtTokenCache,
tokenCache: singleUseTokenCache,
};

router.get(
"/:tenantId/:id",
validateRequestParams("tenantId", "id"),
verifyStorageToken(tenantManager, config, tokenManager),
verifyStorageToken(tenantManager, config, tokenManager, defaultTokenValidationOptions),
throttle(generalTenantThrottler, winston, tenantThrottleOptions),
(request, response, next) => {
const documentP = storage.getDocument(
Expand Down Expand Up @@ -120,6 +135,8 @@ export function create(
requireDocumentId: false,
ensureSingleUseToken: true,
singleUseTokenCache,
enableTokenCache: enableJwtTokenCache,
tokenCache: singleUseTokenCache,
}),
throttle(
clusterThrottlers.get(Constants.createDocThrottleIdPrefix),
Expand Down Expand Up @@ -210,7 +227,7 @@ export function create(
*/
router.get(
"/:tenantId/session/:id",
verifyStorageToken(tenantManager, config, tokenManager),
verifyStorageToken(tenantManager, config, tokenManager, defaultTokenValidationOptions),
throttle(
clusterThrottlers.get(Constants.getSessionThrottleIdPrefix),
winston,
Expand Down Expand Up @@ -244,7 +261,7 @@ export function create(
"/:tenantId/document/:id",
validateRequestParams("tenantId", "id"),
validateTokenScopeClaims(DocDeleteScopeType),
verifyStorageToken(tenantManager, config, tokenManager),
verifyStorageToken(tenantManager, config, tokenManager, defaultTokenValidationOptions),
async (request, response, next) => {
const documentId = getParam(request.params, "id");
const tenantId = getParam(request.params, "tenantId");
Expand All @@ -263,7 +280,7 @@ export function create(
"/:tenantId/document/:id/revokeToken",
validateRequestParams("tenantId", "id"),
validateTokenScopeClaims(TokenRevokeScopeType),
verifyStorageToken(tenantManager, config, tokenManager),
verifyStorageToken(tenantManager, config, tokenManager, defaultTokenValidationOptions),
throttle(generalTenantThrottler, winston, tenantThrottleOptions),
async (request, response, next) => {
const documentId = getParam(request.params, "id");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function create(
appTenants,
tenantThrottlers,
clusterThrottlers,
singleUseTokenCache,
tokenManager,
);
const documentsRoute = documents.create(
Expand All @@ -64,6 +65,7 @@ export function create(
tenantManager,
storage,
tenantThrottlers,
singleUseTokenCache,
tokenManager,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
"enforceServerGeneratedDocumentId": false,
"socketIo": {
"perMessageDeflate": true
},
"jwtTokenCache": {
"enable": true
}
},
"client": {
Expand Down
Loading

0 comments on commit eac5a06

Please sign in to comment.