Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 8 additions & 5 deletions src/utils/stateless-jwt/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,15 @@ async function createRefreshToken(service, payload, audience) {

async function createAccessToken(service, refreshToken, payload, audience) {
const exp = toSeconds(Date.now() + service.config.jwt.stateless.accessTTL);

return createToken(service, audience, {
const finalPayload = {
...payload,
// should not exceed refreshToken exp
exp: exp > refreshToken.exp ? refreshToken.exp : exp,
// refresh token id
rt: refreshToken.cs,
});
};

return createToken(service, audience, finalPayload);
}

async function login(service, userId, audience, metadata) {
Expand Down Expand Up @@ -142,7 +143,9 @@ async function refreshTokenStrategy({
}) {
const { refreshRotation: { enabled, always, interval } } = service.config.jwt.stateless;
const now = toSeconds(Date.now());
if (enabled && (always || (now > refreshToken.exp - interval))) {
const expireInterval = refreshToken.exp - toSeconds(interval);

if (enabled && (always || (now > expireInterval))) {
const refreshTkn = await createRefreshToken(service, refreshPayload, audience);
const access = await createAccessToken(service, refreshTkn.payload, accessPayload, audience);

Expand Down Expand Up @@ -188,7 +191,7 @@ async function refreshTokenPair(service, encodedRefreshToken, refreshToken, audi
rule: {
expireAt: refreshToken.exp,
rt: refreshToken.cs,
iat: { lte: access.payload.iat },
iat: { lt: access.payload.iat },
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/stateless-jwt/to-seconds.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
toSeconds: (epochTime) => Math.round(epochTime / 1000),
toSeconds: (timestamp) => Math.floor(timestamp / 1000),
};
7 changes: 6 additions & 1 deletion test/suites/actions/login-out-refresh.jwt.stateless.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ describe('#stateless-jwt', function loginSuite() {
it('#refresh should accept refresh token', async () => {
const { jwt: oldJwt, jwtRefresh } = await loginUser();

await delay(1000);

const response = await this.users.dispatch('refresh', { params: { token: jwtRefresh, audience: user.audience } });

assert.ok(response.jwt);
Expand All @@ -132,7 +134,7 @@ describe('#stateless-jwt', function loginSuite() {
assert.strictEqual(rules.length, 1);

assert.strictEqual(rules[0].rule.rt, oldTokenDecoded.rt);
assert.deepStrictEqual(rules[0].rule.iat, { lte: newTokenDecoded.iat });
assert.deepStrictEqual(rules[0].rule.iat, { lt: newTokenDecoded.iat });
assert.strictEqual(rules[0].params.expireAt, refreshTokenDecoded.exp);
assert.deepStrictEqual(oldTokenDecoded.audience, newTokenDecoded.audience);

Expand Down Expand Up @@ -176,6 +178,9 @@ describe('#stateless-jwt', function loginSuite() {
assert.strictEqual(rules[0].params.expireAt, refreshTokenDecoded.exp);
assert.ok(rules[0].rule._or);

// requires rule propagation
await delay(200);

// try again with same access token
await assert.rejects(
this.users.dispatch('verify', { params: { token: jwt, audience: user.audience } }),
Expand Down
2 changes: 1 addition & 1 deletion test/suites/actions/revoke-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe('#revoke-rule.* actions and RevocationRulesManager', () => {
},
});

const expireTime = Math.round(Date.now() / 1000);
const expireTime = Math.floor(Date.now() / 1000);
await createRule({ iss: 'ms-users-global' });
await createRule({ expireAt: expireTime });

Expand Down