Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not refresh token on every request and cache verified ones #3014

Draft
wants to merge 3 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 55 additions & 26 deletions site/gatsby-site/server/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@ import { MongoClient } from "mongodb";
import config from "./config";
import * as reporter from "./reporter";


function extractToken(header: string) {

if (header && header!.startsWith('Bearer ')) {

if (header && header.startsWith('Bearer ')) {
return header.substring(7);
}

return null;
}

export const verifyToken = async (token: string) => {

const loginResponse = await fetch(
`https://realm.mongodb.com/api/admin/v3.0/auth/providers/mongodb-cloud/login`,
{
Expand All @@ -31,7 +26,7 @@ export const verifyToken = async (token: string) => {
);

if (!loginResponse.ok) {
throw new Error(`Error login into admin api! \n\n ${await loginResponse.text()}`);
throw new Error(`Error logging into admin API! \n\n ${await loginResponse.text()}`);
}

const loginData = await loginResponse.json();
Expand All @@ -56,35 +51,75 @@ export const verifyToken = async (token: string) => {
}

async function getUser(userId: string, client: MongoClient) {

const db = client.db('customData');

const collection = db.collection('users');

const userData = await collection.findOne({ userId });

return {
id: userId,
roles: userData?.roles,
}
};
}

async function getUserFromHeader(header: string, client: MongoClient) {
async function getTokenCache(token: string, client: MongoClient) {
const db = client.db('customData');
const collection = db.collection('tokenCache');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by: let's discuss how to cache tokens before creating a DB collection for this.


return collection.findOne({ token });
}

async function deleteTokenCache(token: string, client: MongoClient) {
const db = client.db('customData');
const collection = db.collection('tokenCache');

await collection.deleteMany({ token });
}

async function setTokenCache(token: string, userId: string, client: MongoClient) {
const db = client.db('customData');
const collection = db.collection('tokenCache');

await collection.deleteMany({ userId });

await collection.insertOne({ token, userId });
}

async function getUserFromHeader(header: string, client: MongoClient) {
const token = extractToken(header);

console.log('checking token', token);

if (token) {

const data = await verifyToken(token);
const cachedToken = await getTokenCache(token, client);

if (data == 'token expired') {

throw new Error('Token expired');
let userId = null;

if (cachedToken) {

userId = cachedToken.userId;
}
else {

const data = await verifyToken(token);

if (data === 'token expired') {

await deleteTokenCache(token, client);

throw new Error('Token expired');
}

if (data.sub) {
userId = data.sub;
}
}

if (data.sub) {
if (userId) {

const userData = await getUser(userId, client);

const userData = await getUser(data.sub, client);
await setTokenCache(token, userId, client);

return userData;
}
Expand All @@ -94,17 +129,11 @@ async function getUserFromHeader(header: string, client: MongoClient) {
}

export const context = async ({ req, client }: { req: IncomingMessage, client: MongoClient }) => {

try {

const user = await getUserFromHeader(req.headers.authorization!, client);

return { user, req, client };
}
catch (e) {

} catch (e) {
reporter.error(e as Error);

throw e;
}
}
};
21 changes: 20 additions & 1 deletion site/gatsby-site/src/contexts/userContext/UserContextProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ import useLocalizePath from '../../components/i18n/useLocalizePath';
import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename';
import CustomButton from '../../elements/Button';

function decodeAccessToken(accessToken) {
const parts = accessToken.split('.');

const encodedPayload = parts[1];

const decodedPayload = atob(encodedPayload);

const parsedPayload = JSON.parse(decodedPayload);

const { exp: expires, iat: issuedAt, sub: subject, user_data: userData = {} } = parsedPayload;

return { expires, issuedAt, subject, userData };
}

function isTokenExpired(accessToken) {
const { expires } = decodeAccessToken(accessToken);

return Date.now() >= expires * 1000;
}
// https://github.com/mongodb-university/realm-graphql-apollo-react/blob/master/src/index.js

const getApolloCLient = (getValidAccessToken) =>
Expand Down Expand Up @@ -173,7 +192,7 @@ export const UserContextProvider = ({ children }) => {
const getValidAccessToken = async () => {
if (!realmApp.currentUser) {
await login();
} else {
} else if (isTokenExpired(realmApp.currentUser.accessToken)) {
await realmApp.currentUser.refreshCustomData();
}

Expand Down
Loading