Skip to content

Commit

Permalink
Merge pull request #48 from VirgilSecurity/fix-jwt-generator
Browse files Browse the repository at this point in the history
Require identity in JwtGenerator#generateToken
  • Loading branch information
vadimavdeev authored Feb 5, 2019
2 parents 48f4408 + 01778d5 commit fb861a1
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 15 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2017 Virgil Security
Copyright 2015-2019 Virgil Security

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "virgil-sdk",
"version": "5.2.1",
"version": "5.2.2",
"description": "Virgil Security Services SDK",
"contributors": [
"Eugene Baranov <ebaranov.dev@gmail.com> (https://github.com/ebaranov/)",
Expand Down
20 changes: 13 additions & 7 deletions src/Auth/JwtGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,29 @@ export class JwtGenerator {
/**
* @see {@link IJwtGeneratorOptions.appId}
*/
public readonly appId: string;
readonly appId: string;

/**
* @see {@link IJwtGeneratorOptions.apiKey}
*/
public readonly apiKey: IPrivateKey;
readonly apiKey: IPrivateKey;

/**
* @see {@link IJwtGeneratorOptions.apiKeyId}
*/
public readonly apiKeyId: string;
readonly apiKeyId: string;

/**
* @see {@link IJwtGeneratorOptions.accessTokenSigner}
*/
public readonly accessTokenSigner: IAccessTokenSigner;
readonly accessTokenSigner: IAccessTokenSigner;

/**
* @see {@link IJwtGeneratorOptions.millisecondsToLive}
*/
public readonly millisecondsToLive: number;
readonly millisecondsToLive: number;

public constructor (options: IJwtGeneratorOptions) {
constructor (options: IJwtGeneratorOptions) {
validateOptions(options);

this.appId = options.appId;
Expand All @@ -99,7 +99,13 @@ export class JwtGenerator {
* @param {IExtraData} ada - Additional data to be encoded in the JWT.
* @returns {Jwt}
*/
public generateToken(identity: string, ada?: IExtraData) {
generateToken(identity: string, ada?: IExtraData) {
if (!identity) {
throw new TypeError(
'Illegal arguments for function `generateToken`. Argument `identity` is required.'
);
}

const iat = getUnixTimestamp(new Date());
const exp = getUnixTimestamp(new Date().getTime() + this.millisecondsToLive);

Expand Down
1 change: 1 addition & 0 deletions src/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import './unit/StorageAdapter.test';
import './unit/KeyStorage.test';
import './unit/KeyEntryStorage.test';
import './unit/PrivateKeyStorage.test';
import './unit/JwtGenerator.test';
import './integration/CardManager.test';
import './integration/CardVerifier.test';
import './integration/Jwt.test';
Expand Down
12 changes: 6 additions & 6 deletions src/__tests__/integration/CardManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { ICard } from '../../Cards/ICard';

import { compatData } from './data';

const WELL_KNOWN_IDENTITY = `js_sdk_well_known_identity${Date.now()}@virgil.com`;
let WELL_KNOWN_CARD_ID:string;

const init = () => {
const crypto = new VirgilCrypto();
const accessTokenSigner = new VirgilAccessTokenSigner(crypto);
Expand All @@ -36,7 +39,7 @@ const init = () => {
millisecondsToLive: 1000
});

const accessTokenProvider = new GeneratorJwtProvider(jwtGenerator);
const accessTokenProvider = new GeneratorJwtProvider(jwtGenerator, undefined, WELL_KNOWN_IDENTITY);

const cardVerifier = new VirgilCardVerifier(cardCrypto);

Expand All @@ -56,9 +59,6 @@ const init = () => {
};
};

const WELL_KNOWN_IDENTITY = `js_sdk_well_known_identity${Date.now()}@virgil.com`;
let WELL_KNOWN_CARD_ID:string;

describe('CardManager', function () {

this.timeout(10000);
Expand Down Expand Up @@ -460,10 +460,10 @@ describe('CardManager', function () {
const getTokenFn = (context: ITokenContext) => {
if (context.forceReload) {
// generating good token
return Promise.resolve(fixture.jwtGenerator.generateToken(context.identity!));
return Promise.resolve(fixture.jwtGenerator.generateToken(WELL_KNOWN_IDENTITY));
} else {
// generating expired token
const expiredToken = fixture.expiredTokenGenerator.generateToken(context.identity!);
const expiredToken = fixture.expiredTokenGenerator.generateToken(WELL_KNOWN_IDENTITY);
const sleep = new Promise(resolve => setTimeout(resolve, 2000));
// sleeping is needed because minimum token lifetime is 1 second
return sleep.then(() => expiredToken);
Expand Down
137 changes: 137 additions & 0 deletions src/__tests__/unit/JwtGenerator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/// <reference path="../declarations.d.ts" />

import { VirgilCrypto, VirgilAccessTokenSigner } from 'virgil-crypto';
import { JwtGenerator } from '../../Auth/JwtGenerator';
import { JwtContentType, VirgilContentType, IssuerPrefix, SubjectPrefix } from '../../Auth/jwt-constants';
import { getUnixTimestamp } from '../../Lib/timestamp';

const virgilCrypto = new VirgilCrypto();
const accessTokenSigner = new VirgilAccessTokenSigner(virgilCrypto);

describe('JwtGenerator', () => {
describe('constructor', () => {
it('throws when options are not provided', () => {
assert.throws(() => {
new JwtGenerator(null as any);
}, 'JwtGenerator options must be provided');
});

it('throws when API Key ID is not provided', () => {
assert.throws(() => {
new JwtGenerator({
apiKey: virgilCrypto.generateKeys().privateKey,
appId: 'irrelevant',
accessTokenSigner
} as any);
}, '`apiKeyId` is required');
});

it('throws when API Key is not provided', () => {
assert.throws(() => {
new JwtGenerator({
apiKeyId: 'irrelevant',
appId: 'irrelevant',
accessTokenSigner
} as any);
}, '`apiKey` is required');
});

it('throws when App ID is not provided', () => {
assert.throws(() => {
new JwtGenerator({
apiKey: virgilCrypto.generateKeys().privateKey,
apiKeyId: 'irrelevant',
accessTokenSigner
} as any);
}, '`appId` is required');
});

it('throws when accessTokenSigner is not provided', () => {
assert.throws(() => {
new JwtGenerator({
apiKey: virgilCrypto.generateKeys().privateKey,
apiKeyId: 'irrelevant',
appId: 'irrelevant'
} as any);
}, '`accessTokenSigner` is required');
});

it('uses "millisecondsToLive" value provided in options', () => {
const expectedMillisecondsToLive = 99999;
const generator = new JwtGenerator({
millisecondsToLive: expectedMillisecondsToLive,
apiKey: virgilCrypto.generateKeys().privateKey,
apiKeyId: 'irrelevant',
appId: 'irrelevant',
accessTokenSigner
});

assert.equal(generator.millisecondsToLive, expectedMillisecondsToLive);
});

it('uses 20 minutes by default for "millisecondsToLive"', () => {
const expectedMillisecondsToLive = 20 * 60 * 1000;
const generator = new JwtGenerator({
apiKey: virgilCrypto.generateKeys().privateKey,
apiKeyId: 'irrelevant',
appId: 'irrelevant',
accessTokenSigner
});

assert.equal(generator.millisecondsToLive, expectedMillisecondsToLive);
});
});

describe('generateToken', () => {
it('throws when identity is not provided', () => {
const generator = new JwtGenerator({
apiKey: virgilCrypto.generateKeys().privateKey,
apiKeyId: 'irrelevant',
appId: 'irrelevant',
accessTokenSigner
});

assert.throws(() => {
generator.generateToken(undefined as any);
}, '`identity` is required');
});

it('generates a valid JWT', () => {
const keypair = virgilCrypto.generateKeys();
const generator = new JwtGenerator({
apiKey: keypair.privateKey,
apiKeyId: keypair.privateKey.identifier.toString('base64'),
appId: 'my_app_id',
accessTokenSigner
});

const expectedIdentity = 'test_identity';
const expectedAda = { additional: 'data' };
const token = generator.generateToken(expectedIdentity, expectedAda);

assert.isDefined(token.header, 'JWT has header');
assert.isDefined(token.body, 'JWT has body');
assert.isDefined(token.signature, 'JWT has signature');

assert.equal(token.header.alg, accessTokenSigner.getAlgorithm(), 'algorithm is correct');
assert.equal(token.header.kid, keypair.privateKey.identifier.toString('base64'), 'key id is correct');
assert.equal(token.header.typ, JwtContentType, 'token type is correct');
assert.equal(token.header.cty, VirgilContentType, 'content type is correct');

assert.equal(token.body.iss, IssuerPrefix + 'my_app_id', 'issuer is correct');
assert.equal(token.body.sub, SubjectPrefix + 'test_identity', 'subject is correct');
assert.equal(token.body.iat, getUnixTimestamp(Date.now()), 'issued at is correct');
assert.equal(
token.body.exp,
getUnixTimestamp(Date.now() + generator.millisecondsToLive),
'expires at is correct'
);
assert.deepEqual(token.body.ada, expectedAda, 'additional data is correct');

assert.isTrue(
virgilCrypto.verifySignature(token.unsignedData, token.signature!, keypair.publicKey),
'signature is correct'
);
});
});
});

0 comments on commit fb861a1

Please sign in to comment.