Skip to content

Commit

Permalink
Expire refreshed tokens within the same time as initial token.
Browse files Browse the repository at this point in the history
Change-type: major
Signed-off-by: fisehara <[email protected]>
  • Loading branch information
fisehara committed May 30, 2024
1 parent b6988c2 commit 04bb9bd
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 3 deletions.
1 change: 1 addition & 0 deletions config/confd/templates/env.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ IMAGE_STORAGE_PREFIX={{if getenv "IMAGE_STORAGE_PREFIX"}}{{getenv "IMAGE_STORAGE
IMAGE_STORAGE_SECRET_KEY={{getenv "IMAGE_STORAGE_SECRET_KEY"}}
JSON_WEB_TOKEN_EXPIRY_MINUTES={{getenv "JSON_WEB_TOKEN_EXPIRY_MINUTES"}}
JSON_WEB_TOKEN_SECRET='{{getenv "JSON_WEB_TOKEN_SECRET"}}'
JSON_WEB_TOKEN_LIMIT_EXPIRY_REFRESH='{{getenv "JSON_WEB_TOKEN_LIMIT_EXPIRY_REFRESH"}}'
{{if getenv "LOGS_HOST"}}LOGS_HOST={{getenv "LOGS_HOST"}}{{end}}
MAX_CONNECTIONS=75
MIXPANEL_TOKEN={{getenv "MIXPANEL_TOKEN"}}
Expand Down
3 changes: 2 additions & 1 deletion docker-compose.test-custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ services:
IMAGE_STORAGE_PREFIX: images
IMAGE_STORAGE_ACCESS_KEY: ACCESS_KEY
IMAGE_STORAGE_SECRET_KEY: SECRET_KEY
JSON_WEB_TOKEN_EXPIRY_MINUTES: 10080
JSON_WEB_TOKEN_EXPIRY_MINUTES: 720
JSON_WEB_TOKEN_SECRET: purple
JSON_WEB_TOKEN_LIMIT_EXPIRY_REFRESH: true
LOGS_HOST: logs.balenadev.io
METRICS_MAX_REPORT_INTERVAL_SECONDS: 3
MIXPANEL_TOKEN: mixpanel_token
Expand Down
18 changes: 16 additions & 2 deletions src/infra/auth/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getUser } from './auth.js';
import {
JSON_WEB_TOKEN_EXPIRY_MINUTES,
JSON_WEB_TOKEN_SECRET,
JSON_WEB_TOKEN_LIMIT_EXPIRY_REFRESH,
} from '../../lib/config.js';

const { InternalRequestError } = errors;
Expand Down Expand Up @@ -177,12 +178,25 @@ export function createScopedAccessToken(
}

const EXPIRY_SECONDS = JSON_WEB_TOKEN_EXPIRY_MINUTES * 60;
// if the new jwt should be created from an existing one,
// the expiration date of the existing token is taken over
// If a new token is issued the input value or default is used.
export const createJwt = (
payload: AnyObject,
jwtOptions: jsonwebtoken.SignOptions = {},
): string => {
_.defaults(jwtOptions, { expiresIn: EXPIRY_SECONDS });
if (JSON_WEB_TOKEN_LIMIT_EXPIRY_REFRESH === true) {
if (payload.exp == null) {
jwtOptions.expiresIn ??= EXPIRY_SECONDS;
} else {
// jsonwebtoken will throw an error if the expiresIn and exp are both set.
// So we need to delete the expiresIn to tke over the old expiration date.
delete jwtOptions.expiresIn;
}
} else {
jwtOptions.expiresIn ??= EXPIRY_SECONDS;
delete payload.exp;
}
delete payload.iat;
delete payload.exp;
return jsonwebtoken.sign(payload, JSON_WEB_TOKEN_SECRET, jwtOptions);
};
4 changes: 4 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ export const JSON_WEB_TOKEN_EXPIRY_MINUTES = intVar(
'JSON_WEB_TOKEN_EXPIRY_MINUTES',
);
export const JSON_WEB_TOKEN_SECRET = requiredVar('JSON_WEB_TOKEN_SECRET');
export const JSON_WEB_TOKEN_LIMIT_EXPIRY_REFRESH = boolVar(
'JSON_WEB_TOKEN_LIMIT_EXPIRY_REFRESH',
false,
);
export const LOGS_HOST = optionalVar('LOGS_HOST');
export const MIXPANEL_TOKEN = requiredVar('MIXPANEL_TOKEN');
export const NODE_ENV = optionalVar('NODE_ENV');
Expand Down
14 changes: 14 additions & 0 deletions test/04_session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { supertest } from './test-lib/supertest.js';
import * as versions from './test-lib/versions.js';
import type { Device } from './test-lib/fake-device.js';
import type { Application } from '../src/balena-model.js';
import { decode } from 'jsonwebtoken';

const atob = (x: string) => Buffer.from(x, 'base64').toString('binary');
const parseJwt = (t: string) => JSON.parse(atob(t.split('.')[1]));
Expand Down Expand Up @@ -118,10 +119,23 @@ export default () => {
});

it('should be refreshable with /user/v1/refresh-token', async function () {
// wait 2 seconds to make sure the token is already starting to expire
await new Promise((resolve) => setTimeout(resolve, 2000));

const res = await supertest({ token })
.get('/user/v1/refresh-token')
.expect(200);

const oldDecodedToken = decode(token);
const newDecodedToken = decode(res.text);
token = res.text;

expect(oldDecodedToken).to.be.an('object').to.have.property('exp');
expect(newDecodedToken).to.be.an('object').to.have.property('exp');
expect(oldDecodedToken.exp).to.equal(newDecodedToken.exp);

Check failure on line 135 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

'oldDecodedToken' is possibly 'null'.

Check failure on line 135 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

Property 'exp' does not exist on type 'string | JwtPayload'.

Check failure on line 135 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

'newDecodedToken' is possibly 'null'.

Check failure on line 135 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

Property 'exp' does not exist on type 'string | JwtPayload'.
expect(oldDecodedToken.iat).to.be.lt(newDecodedToken.iat);

Check failure on line 136 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

'oldDecodedToken' is possibly 'null'.

Check failure on line 136 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

Property 'iat' does not exist on type 'string | JwtPayload'.

Check failure on line 136 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

'newDecodedToken' is possibly 'null'.

Check failure on line 136 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

Property 'iat' does not exist on type 'string | JwtPayload'.
expect(newDecodedToken.iat - oldDecodedToken.iat).to.be.gt(2);

Check failure on line 137 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

'newDecodedToken' is possibly 'null'.

Check failure on line 137 in test/04_session.ts

View workflow job for this annotation

GitHub Actions / Flowzone / Test npm (22.x)

Property 'iat' does not exist on type 'string | JwtPayload'.

const tokenParts = token.split('.');
expect(tokenParts).to.be.an('array');
expect(tokenParts).to.have.property('length', 3);
Expand Down

0 comments on commit 04bb9bd

Please sign in to comment.