diff --git a/ghost/members-api/index.js b/ghost/members-api/index.js index be51dde0f7..c9e2a7abca 100644 --- a/ghost/members-api/index.js +++ b/ghost/members-api/index.js @@ -69,6 +69,10 @@ module.exports = function MembersApi({ .then(member => encodeToken({ sub: member.id, plans: member.subscriptions.map(sub => sub.plan), + exp: member.subscriptions + .map(sub => sub.validUntil) + .reduce((a, b) => Math.min(a, b), + Math.floor((Date.now() / 1000) + (60 * 60 * 24 * 30))), aud: audience })) .then(token => res.end(token)) diff --git a/ghost/members-api/static/auth/components/MembersProvider.js b/ghost/members-api/static/auth/components/MembersProvider.js index ff8ee58701..ff922e823a 100644 --- a/ghost/members-api/static/auth/components/MembersProvider.js +++ b/ghost/members-api/static/auth/components/MembersProvider.js @@ -48,7 +48,6 @@ export default class MembersProvider extends Component { return this.ready.then(() => { return new Promise((resolve, reject) => { this.gateway.call(method, options, (err, successful) => { - console.log({method, options, err, successful}); if (err || !successful) { reject(err || !successful); } diff --git a/ghost/members-api/static/gateway/bundle.js b/ghost/members-api/static/gateway/bundle.js index 1605546092..f097a8f8d6 100644 --- a/ghost/members-api/static/gateway/bundle.js +++ b/ghost/members-api/static/gateway/bundle.js @@ -24,23 +24,33 @@ } function isTokenExpired(token) { + const claims = getClaims(token); + + if (!claims) { + return true; + } + + const expiry = claims.exp * 1000; + const now = Date.now(); + + const nearFuture = now + (30 * 1000); + + if (expiry < nearFuture) { + return true; + } + + return false; + } + + function getClaims(token) { try { const [header, claims, signature] = token.split('.'); // eslint-disable-line no-unused-vars const parsedClaims = JSON.parse(atob(claims.replace('+', '-').replace('/', '_'))); - const expiry = parsedClaims.exp * 1000; - const now = Date.now(); - - const nearFuture = now + (30 * 1000); - - if (expiry > nearFuture) { - return true; - } - - return false; + return parsedClaims; } catch (e) { - return true; + return null; } } @@ -48,6 +58,8 @@ const tokenKey = 'members:token:aud:' + audience; const storedToken = storage.getItem(tokenKey); if (isTokenExpired(storedToken)) { + const storedTokenKeys = getStoredTokenKeys(); + storage.setItem('members:tokens', JSON.stringify(storedTokenKeys.filter(key => key !== tokenKey))); storage.removeItem(tokenKey); return null; } @@ -86,10 +98,10 @@ // @TODO this needs to be configurable const membersApi = location.pathname.replace(/\/members\/gateway\/?$/, '/ghost/api/v2/members'); - function getToken({audience}) { + function getToken({audience, fresh}) { const storedToken = getStoredToken(audience); - if (storedToken) { + if (storedToken && !fresh) { return Promise.resolve(storedToken); } @@ -123,10 +135,9 @@ if (storage.getItem('signedin')) { window.parent.postMessage({event: 'signedin'}, origin); } else { - window.parent.postMessage({event: 'signedout'}, origin); + getToken({audience: origin, fresh: true}); } - getToken({audience: origin}); return Promise.resolve(); }); diff --git a/ghost/members-api/tokens.js b/ghost/members-api/tokens.js index b72ae533a3..49758fb360 100644 --- a/ghost/members-api/tokens.js +++ b/ghost/members-api/tokens.js @@ -9,15 +9,15 @@ module.exports = function ({ const keyStore = jose.JWK.createKeyStore(); const keyStoreReady = keyStore.add(privateKey, 'pem'); - function encodeToken({sub, aud = issuer, plans}) { + function encodeToken({sub, aud = issuer, plans, exp}) { return keyStoreReady.then(jwk => jwt.sign({ sub, + exp, plans, kid: jwk.kid }, privateKey, { algorithm: 'RS512', audience: aud, - expiresIn: '30m', issuer })); }