Files
notesnook/packages/core/api/token-manager.js

140 lines
3.8 KiB
JavaScript
Raw Normal View History

/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2022 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2022-08-30 16:13:11 +05:00
2020-12-16 12:06:25 +05:00
import http from "../utils/http";
import constants from "../utils/constants";
import { EV, EVENTS } from "../common";
import { withTimeout, Mutex } from "async-mutex";
import { logger } from "../logger";
2020-12-16 12:06:25 +05:00
const ENDPOINTS = {
token: "/connect/token",
revoke: "/connect/revocation",
temporaryToken: "/account/token",
logout: "/account/logout"
2020-12-16 12:06:25 +05:00
};
class TokenManager {
/**
*
* @param {import("../database/storage").default} storage
2020-12-16 12:06:25 +05:00
*/
constructor(storage) {
this._storage = storage;
this._refreshTokenMutex = withTimeout(new Mutex(), 10 * 1000);
this.logger = logger.scope("TokenManager");
2020-12-16 12:06:25 +05:00
}
2021-05-25 16:19:58 +05:00
async getToken(renew = true, forceRenew = false) {
let token = await this._storage.read("token");
2020-12-16 12:06:25 +05:00
if (!token) return;
this.logger.info("Access token requested", {
accessToken: token.access_token.slice(0, 10)
});
2021-05-25 16:19:58 +05:00
if (forceRenew || (renew && this._isTokenExpired(token))) {
await this._refreshToken(forceRenew);
2020-12-16 12:06:25 +05:00
return await this.getToken();
}
2020-12-16 12:06:25 +05:00
return token;
}
_isTokenExpired(token) {
const { t, expires_in } = token;
const expiryMs = t + expires_in * 1000;
2020-12-16 12:06:25 +05:00
return Date.now() >= expiryMs;
}
2021-05-25 16:19:58 +05:00
async getAccessToken(forceRenew = false) {
return await getSafeToken(async () => {
2021-05-25 16:19:58 +05:00
const token = await this.getToken(true, forceRenew);
if (!token) return;
return token.access_token;
}, "Error getting access token:");
2020-12-16 12:06:25 +05:00
}
async _refreshToken(forceRenew = false) {
await this._refreshTokenMutex.runExclusive(async () => {
this.logger.info("Refreshing access token");
const token = await this.getToken(false, false);
if (!forceRenew && !this._isTokenExpired(token)) {
return;
}
const { refresh_token, scope } = token;
if (!refresh_token || !scope) return;
const refreshTokenResponse = await await http.post(
`${constants.AUTH_HOST}${ENDPOINTS.token}`,
{
refresh_token,
grant_type: "refresh_token",
scope: scope,
client_id: "notesnook"
}
);
await this.saveToken(refreshTokenResponse);
EV.publish(EVENTS.tokenRefreshed);
});
2020-12-16 12:06:25 +05:00
}
async revokeToken() {
const token = await this.getToken();
if (!token) return;
const { access_token } = token;
2020-12-16 12:06:25 +05:00
await http.post(
`${constants.AUTH_HOST}${ENDPOINTS.logout}`,
null,
access_token
);
2020-12-16 12:06:25 +05:00
}
saveToken(tokenResponse) {
2021-05-28 23:10:10 +05:00
let token = { ...tokenResponse, t: Date.now() };
return this._storage.write("token", token);
2021-04-05 11:32:40 +05:00
}
async getAccessTokenFromAuthorizationCode(userId, authCode) {
return await this.saveToken(
await http.post(`${constants.AUTH_HOST}${ENDPOINTS.temporaryToken}`, {
authorization_code: authCode,
user_id: userId,
client_id: "notesnook"
})
);
}
2020-12-16 12:06:25 +05:00
}
export default TokenManager;
async function getSafeToken(action, errorMessage) {
try {
return await action();
} catch (e) {
console.error(errorMessage, e);
if (e.message === "invalid_grant" || e.message === "invalid_client") {
EV.publish(EVENTS.userSessionExpired);
}
throw e;
}
}