New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

limes

Package Overview
Dependencies
Maintainers
4
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

limes - npm Package Compare versions

Comparing version 1.1.0 to 2.0.0

dist/IdentityProvider.js

2

.eslintrc.json
{
"extends": "eslint-config-es/2015/server"
"extends": "es/2015/server"
}

@@ -5,2 +5,4 @@ 'use strict';

var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

@@ -14,40 +16,55 @@

var expressJwt = require('express-jwt'),
flow = require('middleware-flow'),
jwt = require('jsonwebtoken');
var jwt = require('jsonwebtoken');
var IdentityProvider = require('./IdentityProvider');
var Limes =
/*#__PURE__*/
function () {
function Limes(options) {
function Limes(_ref) {
var identityProviders = _ref.identityProviders;
(0, _classCallCheck2.default)(this, Limes);
if (!options) {
throw new Error('Options are missing.');
if (!identityProviders) {
throw new Error('Identity providers are missing.');
}
if (!options.identityProviderName) {
throw new Error('Identity provider name is missing.');
if (identityProviders.length === 0) {
throw new Error('Identity providers are missing.');
}
if (!options.privateKey && !options.certificate) {
throw new Error('Specify private key and / or certificate.');
}
var identityProviderName = options.identityProviderName,
privateKey = options.privateKey,
certificate = options.certificate,
_options$expiresInMin = options.expiresInMinutes,
expiresInMinutes = _options$expiresInMin === void 0 ? 24 * 60 : _options$expiresInMin;
this.identityProviderName = identityProviderName;
this.privateKey = privateKey;
this.certificate = certificate;
this.expiresInMinutes = expiresInMinutes;
this.identityProviders = identityProviders;
}
(0, _createClass2.default)(Limes, [{
key: "issueTokenFor",
value: function issueTokenFor(subject) {
var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
key: "getIdentityProviderByIssuer",
value: function getIdentityProviderByIssuer(_ref2) {
var issuer = _ref2.issuer;
if (!issuer) {
throw new Error('Issuer is missing.');
}
var requestedIdentityProvider = this.identityProviders.find(function (identityProvider) {
return identityProvider.issuer === issuer;
});
if (!requestedIdentityProvider) {
throw new Error("Issuer '".concat(issuer, "' not found."));
}
return requestedIdentityProvider;
}
}, {
key: "issueToken",
value: function issueToken(_ref3) {
var issuer = _ref3.issuer,
subject = _ref3.subject,
_ref3$payload = _ref3.payload,
payload = _ref3$payload === void 0 ? {} : _ref3$payload;
if (!issuer) {
throw new Error('Issuer is missing.');
}
if (!subject) {

@@ -57,25 +74,11 @@ throw new Error('Subject is missing.');

return jwt.sign(payload, this.privateKey, {
var identityProvider = this.getIdentityProviderByIssuer({
issuer: issuer
});
var token = jwt.sign(payload, identityProvider.privateKey, {
algorithm: 'RS256',
expiresIn: this.expiresInMinutes * 60,
subject: subject,
issuer: this.identityProviderName
issuer: identityProvider.issuer,
expiresIn: identityProvider.expiresInMinutes * 60
});
}
}, {
key: "issueTokenForAnonymous",
value: function issueTokenForAnonymous(payload) {
return this.issueTokenFor('anonymous', payload);
}
}, {
key: "issueDecodedTokenForAnonymous",
value: function issueDecodedTokenForAnonymous(options) {
var payloadWhenAnonymous = options.payloadWhenAnonymous;
var issuedAt = Math.floor(Date.now() / 1000);
var expiresAt = issuedAt + this.expiresInMinutes * 60;
var token = payloadWhenAnonymous;
token.iat = issuedAt;
token.exp = expiresAt;
token.iss = this.identityProviderName;
token.sub = 'anonymous';
return token;

@@ -88,5 +91,4 @@ }

/*#__PURE__*/
_regenerator.default.mark(function _callee(token) {
var _this = this;
_regenerator.default.mark(function _callee(_ref4) {
var token, untrustedDecodedToken, identityProvider, decodedToken;
return _regenerator.default.wrap(function _callee$(_context) {

@@ -96,15 +98,57 @@ while (1) {

case 0:
return _context.abrupt("return", new Promise(function (resolve, reject) {
jwt.verify(token, _this.certificate, {
issuer: _this.identityProviderName
}, function (err, decodedToken) {
if (err) {
return reject(err);
}
token = _ref4.token;
resolve(decodedToken);
});
}));
if (token) {
_context.next = 3;
break;
}
case 1:
throw new Error('Token is missing.');
case 3:
_context.prev = 3;
untrustedDecodedToken = jwt.decode(token);
_context.next = 10;
break;
case 7:
_context.prev = 7;
_context.t0 = _context["catch"](3);
throw new Error('Failed to verify token.');
case 10:
if (untrustedDecodedToken) {
_context.next = 12;
break;
}
throw new Error('Failed to verify token.');
case 12:
identityProvider = this.getIdentityProviderByIssuer({
issuer: untrustedDecodedToken.iss
});
_context.next = 15;
return new Promise(function (resolve, reject) {
try {
jwt.verify(token, identityProvider.certificate, {
algorithms: ['RS256'],
issuer: identityProvider.issuer
}, function (err, verifiedToken) {
if (err) {
return reject(new Error('Failed to verify token.'));
}
resolve(verifiedToken);
});
} catch (ex) {
reject(ex);
}
});
case 15:
decodedToken = _context.sent;
return _context.abrupt("return", decodedToken);
case 17:
case "end":

@@ -114,39 +158,120 @@ return _context.stop();

}
}, _callee, this);
}, _callee, this, [[3, 7]]);
}));
return function verifyToken(_x) {
function verifyToken(_x) {
return _verifyToken.apply(this, arguments);
};
}
return verifyToken;
}()
}, {
key: "verifyTokenMiddlewareExpress",
value: function verifyTokenMiddlewareExpress() {
var _this2 = this;
key: "verifyTokenMiddleware",
value: function verifyTokenMiddleware(_ref5) {
var _this = this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _options$payloadWhenA = options.payloadWhenAnonymous,
payloadWhenAnonymous = _options$payloadWhenA === void 0 ? {} : _options$payloadWhenA;
return flow.try(expressJwt({
secret: this.certificate,
issuer: this.identityProviderName,
getToken: function getToken(req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
var issuerForAnonymousTokens = _ref5.issuerForAnonymousTokens;
return null;
}
})).catch(function (err, req, res, next) {
if (err.code === 'invalid_token') {
return res.status(401).end();
}
if (!issuerForAnonymousTokens) {
throw new Error('Issuer for anonymous tokens is missing.');
}
req.user = _this2.issueDecodedTokenForAnonymous({
payloadWhenAnonymous: payloadWhenAnonymous
});
next();
return (
/*#__PURE__*/
function () {
var _ref6 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee2(req, res, next) {
var token, authorizationHeader, authorizationQuery, _authorizationHeader$, _authorizationHeader$2, authorizationType, authorizationValue, decodedToken;
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
authorizationHeader = req.headers.authorization, authorizationQuery = req.query.token;
if (authorizationHeader) {
_authorizationHeader$ = authorizationHeader.split(' '), _authorizationHeader$2 = (0, _slicedToArray2.default)(_authorizationHeader$, 2), authorizationType = _authorizationHeader$2[0], authorizationValue = _authorizationHeader$2[1];
if (authorizationType === 'Bearer') {
token = authorizationValue;
}
} else if (authorizationQuery) {
token = authorizationQuery;
}
if (!token) {
_context2.next = 14;
break;
}
_context2.prev = 3;
_context2.next = 6;
return _this.verifyToken({
token: token
});
case 6:
decodedToken = _context2.sent;
_context2.next = 12;
break;
case 9:
_context2.prev = 9;
_context2.t0 = _context2["catch"](3);
return _context2.abrupt("return", res.status(401).end());
case 12:
_context2.next = 15;
break;
case 14:
decodedToken = Limes.issueUntrustedTokenAsJson({
issuer: issuerForAnonymousTokens,
subject: 'anonymous'
});
case 15:
req.user = decodedToken;
next();
case 17:
case "end":
return _context2.stop();
}
}
}, _callee2, null, [[3, 9]]);
}));
return function (_x2, _x3, _x4) {
return _ref6.apply(this, arguments);
};
}()
);
}
}], [{
key: "issueUntrustedTokenAsJson",
value: function issueUntrustedTokenAsJson(_ref7) {
var issuer = _ref7.issuer,
subject = _ref7.subject,
_ref7$payload = _ref7.payload,
payload = _ref7$payload === void 0 ? {} : _ref7$payload;
if (!issuer) {
throw new Error('Issuer is missing.');
}
if (!subject) {
throw new Error('Subject is missing.');
}
var expiresInMinutes = 60;
var encodedToken = jwt.sign(payload, null, {
algorithm: 'none',
subject: subject,
issuer: issuer,
expiresIn: expiresInMinutes * 60
});
var token = jwt.decode(encodedToken);
return token;
}

@@ -157,2 +282,3 @@ }]);

Limes.IdentityProvider = IdentityProvider;
module.exports = Limes;
{
"name": "limes",
"version": "1.1.0",
"version": "2.0.0",
"description": "limes authenticates users.",

@@ -17,12 +17,10 @@ "contributors": [

"dependencies": {
"@babel/runtime": "7.1.2",
"express-jwt": "5.3.1",
"jsonwebtoken": "8.3.0",
"middleware-flow": "0.8.0"
"@babel/runtime": "7.3.4",
"jsonwebtoken": "8.5.0"
},
"devDependencies": {
"assertthat": "1.0.0",
"assertthat": "2.0.3",
"express": "4.16.4",
"roboter": "2.0.0",
"supertest": "3.3.0"
"roboter": "4.0.2",
"supertest": "4.0.0"
},

@@ -29,0 +27,0 @@ "repository": {

@@ -19,7 +19,7 @@ # limes

Then you can call the `Limes` constructor function to create a new limes instance. You need to specify a parameter object with the `identityProviderName` and either a `privateKey` or a `certificate`, each in `.pem` format. Optionally, you may also provide both:
Now you need to create one or more identity providers. For each identity provider call the `Limes.IdentityProvider` constructor and hand over the `issuer` as well as a `privateKey` or a `certificate`, each in `.pem` format. Optionally, you may provide both:
```javascript
const limes = new Limes({
identityProviderName: 'auth.example.com',
const identityProvider = new Limes.IdentityProvider({
issuer: 'https://auth.thenativeweb.io',
privateKey: await readFile(path.join(__dirname, 'privateKey.pem')),

@@ -30,48 +30,91 @@ certificate: await readFile(path.join(__dirname, 'certificate.pem'))

Please note that you have to specify the private key if you want to issue tokens and the certificate if you want to verify them.
_Please note that you have to specify the private key if you want to issue tokens and the certificate if you want to verify them._
Then you can call the `Limes` constructor function to create a new limes instance. Hand over an array of one or more of the previously created identity providers:
```javascript
const limes = new Limes({
identityProviders: [ identityProvider ]
});
```
### Issuing tokens
To issue a token call the `issueTokenFor` function and provide the subject you want to issue the token for as well as the desired payload:
To issue a token call the `issueToken` function and provide the `issuer` and the `subject` you want to use as well as an optional payload:
```javascript
const token = limes.issueTokenFor('Jane Doe', {
foo: 'bar'
const token = limes.issueToken({
issuer: 'https://auth.thenativeweb.io',
subject: 'jane.doe',
payload: {
'https://auth.thenativeweb.io/email': 'jane.doe@thenativeweb.io'
}
});
```
_Please note that the issuer must match one of the registered identity providers. Otherwise, `issueToken` will throw an error._
#### Issuing untrusted tokens for testing
From time to time, e.g. for testing, you may want to get a JSON object that looks like a decoded token, but avoid the effort to create a signed token first. For this, use the static `issueUntrustedTokenAsJson` function and hand over the desired `issuer`, the `subject`, and an optional `payload`:
```javascript
const decodedToken = Limes.issueUntrustedTokenAsJson({
issuer: 'https://untrusted.thenativeweb.io',
subject: 'jane.doe'
});
```
_Please note that this is highly insecure, and should never be used for production code!_
### Verifying tokens
To verify a token call the `verifyToken` function and provide the token. As a result, it returns the decoded token:
To verify a token call the `verifyToken` function and provide the token. This function tries to verify and decode the token using the identity provider that matches the token's `iss` value and returns the decoded token:
```javascript
const decodedToken = await limes.verifyToken(token);
const decodedToken = await limes.verifyToken({ token });
```
If no identity provider for the token's `iss` value is found, an exception is thrown. Also, an exception is thrown if the token is invalid.
### Using middleware
To verify tokens there is also a middleware for Express. To use it call the `verifyTokenMiddlewareExpress` function and optionally specify the payload for non-authenticated users:
To verify tokens in web applications, there is a middleware for Express. To use it call the `verifyTokenMiddleware` function and hand over a made-up issuer value you want to use for anonymous tokens:
```javascript
app.use(limes.verifyTokenMiddlewareExpress({
payloadWhenAnonymous: {
foo: 'bar'
}
app.use(limes.verifyTokenMiddleware({
issuerForAnonymousTokens: 'https://anonymous.thenativeweb.io'
}));
```
If a request does not provide a token, an anonymous token is issued. If a request does have an invalid token, an expired one, or one with a wrong issuer, the middleware returns a `401` respectively an error. Otherwise, it attaches the decoded token to `req.user`.
_Please note that the issuer for anonymous tokens is made-up, and does not provide any security. It's just a string that is used without further validation._
The middleware expects the token to be inside an HTTP header called `authorization` and prefixed with the term `Bearer`:
The middleware expects the token to be inside the `authorization` HTTP header, prefixed with the term `Bearer`:
```
authorization: Bearer <token>
```
authorization: Bearer <token>
Alternatively, you may transfer the token using the query string parameter `token`:
GET /foo/bar?token=<token>
Either way, the verified and decoded token will be attached to the `req.user` property:
```javascript
const app = express();
app.use(limes.verifyTokenMiddleware({
issuerForAnonymousTokens: 'https://anonymous.thenativeweb.io'
}));
app.get('/', (req, res) => {
res.json(req.user);
});
```
GET /foo/bar?token=<token>
```
If a request does not provide a token, a token for an anonymous user will be issued. This issue uses `anonymous` for the `sub` property, and the aforementioned issuer for anonymous tokens.
_Please make sure that your application code handles anonymous users in an intended way! The middleware does not block anonymous users, it just identifies and marks them!_
If a request does have an invalid token, an expired one, or one from an unknown issuer, the middleware returns the status code `401`.
## Running the build

@@ -86,3 +129,3 @@

The MIT License (MIT)
Copyright (c) 2014-2018 the native web.
Copyright (c) 2014-2019 the native web.

@@ -89,0 +132,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

'use strict';
const expressJwt = require('express-jwt'),
flow = require('middleware-flow'),
jwt = require('jsonwebtoken');
const jwt = require('jsonwebtoken');
const IdentityProvider = require('./IdentityProvider');
class Limes {
constructor (options) {
if (!options) {
throw new Error('Options are missing.');
constructor ({ identityProviders }) {
if (!identityProviders) {
throw new Error('Identity providers are missing.');
}
if (!options.identityProviderName) {
throw new Error('Identity provider name is missing.');
if (identityProviders.length === 0) {
throw new Error('Identity providers are missing.');
}
if (!options.privateKey && !options.certificate) {
throw new Error('Specify private key and / or certificate.');
this.identityProviders = identityProviders;
}
getIdentityProviderByIssuer ({ issuer }) {
if (!issuer) {
throw new Error('Issuer is missing.');
}
const {
identityProviderName,
privateKey,
certificate,
expiresInMinutes = 24 * 60
} = options;
const requestedIdentityProvider = this.identityProviders.find(
identityProvider => identityProvider.issuer === issuer
);
this.identityProviderName = identityProviderName;
this.privateKey = privateKey;
this.certificate = certificate;
this.expiresInMinutes = expiresInMinutes;
if (!requestedIdentityProvider) {
throw new Error(`Issuer '${issuer}' not found.`);
}
return requestedIdentityProvider;
}
issueTokenFor (subject, payload = {}) {
issueToken ({
issuer,
subject,
payload = {}
}) {
if (!issuer) {
throw new Error('Issuer is missing.');
}
if (!subject) {

@@ -37,26 +47,36 @@ throw new Error('Subject is missing.');

return jwt.sign(payload, this.privateKey, {
const identityProvider = this.getIdentityProviderByIssuer({ issuer });
const token = jwt.sign(payload, identityProvider.privateKey, {
algorithm: 'RS256',
expiresIn: this.expiresInMinutes * 60,
subject,
issuer: this.identityProviderName
issuer: identityProvider.issuer,
expiresIn: identityProvider.expiresInMinutes * 60
});
}
issueTokenForAnonymous (payload) {
return this.issueTokenFor('anonymous', payload);
return token;
}
issueDecodedTokenForAnonymous (options) {
const { payloadWhenAnonymous } = options;
static issueUntrustedTokenAsJson ({
issuer,
subject,
payload = {}
}) {
if (!issuer) {
throw new Error('Issuer is missing.');
}
if (!subject) {
throw new Error('Subject is missing.');
}
const issuedAt = Math.floor(Date.now() / 1000);
const expiresAt = issuedAt + (this.expiresInMinutes * 60);
const expiresInMinutes = 60;
const token = payloadWhenAnonymous;
const encodedToken = jwt.sign(payload, null, {
algorithm: 'none',
subject,
issuer,
expiresIn: expiresInMinutes * 60
});
token.iat = issuedAt;
token.exp = expiresAt;
token.iss = this.identityProviderName;
token.sub = 'anonymous';
const token = jwt.decode(encodedToken);

@@ -66,44 +86,87 @@ return token;

async verifyToken (token) {
return new Promise((resolve, reject) => {
jwt.verify(token, this.certificate, {
issuer: this.identityProviderName
}, (err, decodedToken) => {
if (err) {
return reject(err);
}
async verifyToken ({ token }) {
if (!token) {
throw new Error('Token is missing.');
}
resolve(decodedToken);
});
let untrustedDecodedToken;
try {
untrustedDecodedToken = jwt.decode(token);
} catch (ex) {
throw new Error('Failed to verify token.');
}
if (!untrustedDecodedToken) {
throw new Error('Failed to verify token.');
}
const identityProvider = this.getIdentityProviderByIssuer({
issuer: untrustedDecodedToken.iss
});
const decodedToken = await new Promise((resolve, reject) => {
try {
jwt.verify(token, identityProvider.certificate, {
algorithms: [ 'RS256' ],
issuer: identityProvider.issuer
}, (err, verifiedToken) => {
if (err) {
return reject(new Error('Failed to verify token.'));
}
resolve(verifiedToken);
});
} catch (ex) {
reject(ex);
}
});
return decodedToken;
}
verifyTokenMiddlewareExpress (options = {}) {
const { payloadWhenAnonymous = {}} = options;
verifyTokenMiddleware ({ issuerForAnonymousTokens }) {
if (!issuerForAnonymousTokens) {
throw new Error('Issuer for anonymous tokens is missing.');
}
return flow.
try(expressJwt({
secret: this.certificate,
issuer: this.identityProviderName,
getToken (req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return async (req, res, next) => {
let token;
return null;
const authorizationHeader = req.headers.authorization,
authorizationQuery = req.query.token;
if (authorizationHeader) {
const [ authorizationType, authorizationValue ] = authorizationHeader.split(' ');
if (authorizationType === 'Bearer') {
token = authorizationValue;
}
})).
catch((err, req, res, next) => {
if (err.code === 'invalid_token') {
} else if (authorizationQuery) {
token = authorizationQuery;
}
let decodedToken;
if (token) {
try {
decodedToken = await this.verifyToken({ token });
} catch (ex) {
return res.status(401).end();
}
} else {
decodedToken = Limes.issueUntrustedTokenAsJson({
issuer: issuerForAnonymousTokens,
subject: 'anonymous'
});
}
req.user = this.issueDecodedTokenForAnonymous({ payloadWhenAnonymous });
next();
});
req.user = decodedToken;
next();
};
}
}
Limes.IdentityProvider = IdentityProvider;
module.exports = Limes;

@@ -11,10 +11,56 @@ 'use strict';

const Limes = require('../../src/Limes');
const IdentityProvider = require('../../src/IdentityProvider'),
Limes = require('../../src/Limes');
/* eslint-disable no-sync */
const certificate = fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'certificate.pem')),
privateKey = fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'privateKey.pem'));
const keys = {
thenativeweb: {
certificate: fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'auth.thenativeweb.io', 'certificate.pem')),
privateKey: fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'auth.thenativeweb.io', 'privateKey.pem'))
},
intuity: {
certificate: fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'auth.intuity.de', 'certificate.pem')),
privateKey: fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'auth.intuity.de', 'privateKey.pem'))
},
example: {
certificate: fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'auth.example.com', 'certificate.pem')),
privateKey: fs.readFileSync(path.join(__dirname, '..', 'shared', 'keys', 'auth.example.com', 'privateKey.pem'))
}
};
/* eslint-enable no-sync */
suite('Limes', () => {
const identityProviderThenativeweb = new Limes.IdentityProvider({
issuer: 'https://auth.thenativeweb.io',
privateKey: keys.thenativeweb.privateKey,
certificate: keys.thenativeweb.certificate
});
const identityProviderIntuity = new Limes.IdentityProvider({
issuer: 'https://auth.intuity.de',
privateKey: keys.intuity.privateKey,
certificate: keys.intuity.certificate
});
const identityProviderUnknown = new Limes.IdentityProvider({
issuer: 'https://auth.example.com',
privateKey: keys.example.privateKey,
certificate: keys.example.certificate
});
const identityProviderExpired = new Limes.IdentityProvider({
issuer: 'https://auth.thenativeweb.io',
privateKey: keys.thenativeweb.privateKey,
certificate: keys.thenativeweb.certificate,
expiresInMinutes: -5
});
let limes;
setup(() => {
limes = new Limes({
identityProviders: [ identityProviderThenativeweb, identityProviderIntuity ]
});
});
test('is a function.', async () => {

@@ -24,48 +70,64 @@ assert.that(Limes).is.ofType('function');

test('throws an exception if options are missing.', async () => {
test('throws an exception if identity providers are missing.', async () => {
assert.that(() => {
/* eslint-disable no-new */
new Limes();
new Limes({});
/* eslint-enable no-new */
}).is.throwing('Options are missing.');
}).is.throwing('Identity providers are missing.');
});
test('throws an exception if identity provider name is missing.', async () => {
test('throws an exception if identity providers are empty.', async () => {
assert.that(() => {
/* eslint-disable no-new */
new Limes({});
new Limes({ identityProviders: []});
/* eslint-enable no-new */
}).is.throwing('Identity provider name is missing.');
}).is.throwing('Identity providers are missing.');
});
test('throws an exception if private key and certificate are both missing.', async () => {
assert.that(() => {
/* eslint-disable no-new */
new Limes({
identityProviderName: 'auth.example.com'
});
/* eslint-enable no-new */
}).is.throwing('Specify private key and / or certificate.');
suite('IdentityProvider', () => {
test('is the IdentityProvider constructor.', async () => {
assert.that(Limes.IdentityProvider).is.sameAs(IdentityProvider);
});
});
suite('issueTokenFor', () => {
suite('getIdentityProviderByIssuer', () => {
test('is a function.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
});
assert.that(limes.getIdentityProviderByIssuer).is.ofType('function');
});
assert.that(limes.issueTokenFor).is.ofType('function');
test('throws an error if issuer is missing.', async () => {
assert.that(() => {
limes.getIdentityProviderByIssuer({});
}).is.throwing('Issuer is missing.');
});
test('throws an exception if subject is missing.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
test('throws an error if issuer does not exist.', async () => {
assert.that(() => {
limes.getIdentityProviderByIssuer({ issuer: 'https://auth.example.com' });
}).is.throwing(`Issuer 'https://auth.example.com' not found.`);
});
test('returns the requested identity provider.', async () => {
const identityProvider = limes.getIdentityProviderByIssuer({
issuer: 'https://auth.thenativeweb.io'
});
assert.that(identityProvider).is.sameAs(identityProviderThenativeweb);
});
});
suite('issueToken', () => {
test('is a function.', async () => {
assert.that(limes.issueToken).is.ofType('function');
});
test('throws an exception if issuer is missing.', async () => {
assert.that(() => {
limes.issueTokenFor();
limes.issueToken({});
}).is.throwing('Issuer is missing.');
});
test('throws an exception if subject is missing.', async () => {
assert.that(() => {
limes.issueToken({ issuer: 'https://auth.thenativeweb.io' });
}).is.throwing('Subject is missing.');

@@ -75,43 +137,69 @@ });

test('returns a JWT.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
const token = limes.issueToken({
issuer: 'https://auth.thenativeweb.io',
subject: 'jane.doe'
});
const token = limes.issueTokenFor('test.domain.com', { foo: 'bar' });
const decodedToken = await jwt.verify(token, keys.thenativeweb.certificate, { issuer: 'https://auth.thenativeweb.io' });
const decodedToken = await jwt.verify(token, certificate, { issuer: 'auth.example.com' });
assert.that(decodedToken.iss).is.equalTo('https://auth.thenativeweb.io');
assert.that(decodedToken.sub).is.equalTo('jane.doe');
});
assert.that(decodedToken.iss).is.equalTo('auth.example.com');
assert.that(decodedToken.sub).is.equalTo('test.domain.com');
assert.that(decodedToken.foo).is.equalTo('bar');
test('returns a JWT with the given payload.', async () => {
const token = limes.issueToken({
issuer: 'https://auth.thenativeweb.io',
subject: 'jane.doe',
payload: {
'https://auth.thenativeweb.io/email': 'jane.doe@thenativeweb.io'
}
});
const decodedToken = await jwt.verify(token, keys.thenativeweb.certificate, { issuer: 'https://auth.thenativeweb.io' });
assert.that(decodedToken.iss).is.equalTo('https://auth.thenativeweb.io');
assert.that(decodedToken.sub).is.equalTo('jane.doe');
assert.that(decodedToken['https://auth.thenativeweb.io/email']).is.equalTo('jane.doe@thenativeweb.io');
});
});
suite('issueTokenForAnonymous', () => {
suite('issueUntrustedTokenAsJson', () => {
test('is a function.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
});
assert.that(Limes.issueUntrustedTokenAsJson).is.ofType('function');
});
assert.that(limes.issueTokenForAnonymous).is.ofType('function');
test('throws an exception if issuer is missing.', async () => {
assert.that(() => {
Limes.issueUntrustedTokenAsJson({});
}).is.throwing('Issuer is missing.');
});
test('throws an exception if subject is missing.', async () => {
assert.that(() => {
Limes.issueUntrustedTokenAsJson({ issuer: 'https://untrusted.thenativeweb.io' });
}).is.throwing('Subject is missing.');
});
test('returns a JWT.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
const decodedToken = Limes.issueUntrustedTokenAsJson({
issuer: 'https://untrusted.thenativeweb.io',
subject: 'jane.doe'
});
const token = limes.issueTokenForAnonymous({ foo: 'bar' });
assert.that(decodedToken.iss).is.equalTo('https://untrusted.thenativeweb.io');
assert.that(decodedToken.sub).is.equalTo('jane.doe');
});
const decodedToken = await jwt.verify(token, certificate, { issuer: 'auth.example.com' });
test('returns a JWT with the given payload.', async () => {
const decodedToken = Limes.issueUntrustedTokenAsJson({
issuer: 'https://untrusted.thenativeweb.io',
subject: 'jane.doe',
payload: {
'https://untrusted.thenativeweb.io/email': 'jane.doe@thenativeweb.io'
}
});
assert.that(decodedToken.iss).is.equalTo('auth.example.com');
assert.that(decodedToken.sub).is.equalTo('anonymous');
assert.that(decodedToken.foo).is.equalTo('bar');
assert.that(decodedToken.iss).is.equalTo('https://untrusted.thenativeweb.io');
assert.that(decodedToken.sub).is.equalTo('jane.doe');
assert.that(decodedToken['https://untrusted.thenativeweb.io/email']).is.equalTo('jane.doe@thenativeweb.io');
});

@@ -122,224 +210,178 @@ });

test('is a function.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
});
assert.that(limes.verifyToken).is.ofType('function');
});
test('throws an error if token is missing.', async () => {
await assert.that(async () => {
await limes.verifyToken({});
}).is.throwingAsync('Token is missing.');
});
test('returns the decoded token if the token is valid.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
const token = limes.issueToken({
issuer: 'https://auth.thenativeweb.io',
subject: 'jane.doe'
});
const token = limes.issueTokenFor('adc225b7-65b9-48f4-be4d-c5108aa4d1f4', {
foo: 'bar'
const decodedToken = await limes.verifyToken({ token });
assert.that(decodedToken.iss).is.equalTo('https://auth.thenativeweb.io');
assert.that(decodedToken.sub).is.equalTo('jane.doe');
});
test('throws an error if the token is valid, but was issued by an unknown identity provider.', async () => {
const otherLimes = new Limes({
identityProviders: [ identityProviderUnknown ]
});
const decodedToken = await limes.verifyToken(token);
const token = otherLimes.issueToken({
issuer: 'https://auth.example.com',
subject: 'jane.doe'
});
assert.that(decodedToken.iss).is.equalTo('auth.example.com');
assert.that(decodedToken.sub).is.equalTo('adc225b7-65b9-48f4-be4d-c5108aa4d1f4');
assert.that(decodedToken.foo).is.equalTo('bar');
await assert.that(async () => {
await limes.verifyToken({ token });
}).is.throwingAsync(`Issuer 'https://auth.example.com' not found.`);
});
test('throws an error if the token is not valid.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
});
await assert.that(async () => {
await limes.verifyToken('invalidtoken');
}).is.throwingAsync('jwt malformed');
await limes.verifyToken({ token: 'invalidtoken' });
}).is.throwingAsync('Failed to verify token.');
});
test('throws an error if the token contains invalid characters.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
});
await assert.that(async () => {
await limes.verifyToken('invalid token');
}).is.throwingAsync('jwt malformed');
await limes.verifyToken({ token: 'invalid#token' });
}).is.throwingAsync('Failed to verify token.');
});
});
suite('middleware integration', () => {
let limesInThePast,
limesOfSomebodyElse;
suite('verifyTokenMiddleware', () => {
test('is a function.', async () => {
assert.that(limes.verifyTokenMiddleware).is.ofType('function');
});
suiteSetup(() => {
limesInThePast = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate,
expiresInMinutes: -5
test('throws an error if issuer for anonymous tokens is missing.', async () => {
assert.that(() => {
limes.verifyTokenMiddleware({});
}).is.throwing('Issuer for anonymous tokens is missing.');
});
test('returns a function.', async () => {
const middleware = limes.verifyTokenMiddleware({
issuerForAnonymousTokens: 'https://untrusted.thenativeweb.io'
});
limesOfSomebodyElse = new Limes({
identityProviderName: 'somebodyelse.example.com',
privateKey,
certificate
});
assert.that(middleware).is.ofType('function');
});
suite('verifyTokenMiddlewareExpress', () => {
test('is a function.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
});
suite('middleware', () => {
let app;
assert.that(limes.verifyTokenMiddlewareExpress).is.ofType('function');
});
setup(() => {
app = express();
test('returns a function.', async () => {
const limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
app.use(limes.verifyTokenMiddleware({
issuerForAnonymousTokens: 'https://untrusted.thenativeweb.io'
}));
app.get('/', (req, res) => {
res.json(req.user);
});
});
const middleware = limes.verifyTokenMiddlewareExpress();
test('returns an anonymous token for non-authenticated requests.', async () => {
const { status, body } = await request(app).
get('/').
set('accept', 'application/json');
assert.that(middleware).is.ofType('function');
assert.that(status).is.equalTo(200);
assert.that(body.iss).is.equalTo('https://untrusted.thenativeweb.io');
assert.that(body.sub).is.equalTo('anonymous');
});
suite('middleware', () => {
let app,
limes;
setup(() => {
limes = new Limes({
identityProviderName: 'auth.example.com',
privateKey,
certificate
});
app = express();
app.use(limes.verifyTokenMiddlewareExpress({
payloadWhenAnonymous: {
foo: 'anonymous-bar'
}
}));
app.get('/', (req, res) => {
res.send(req.user);
});
});
test('returns an anonymous token for non-authenticated requests.', done => {
request(app).
test('returns 401 for invalid tokens.', async () => {
await assert.that(async () => {
await request(app).
get('/').
set('accept', 'application/json').
end((err, res) => {
assert.that(err).is.null();
assert.that(res.statusCode).is.equalTo(200);
assert.that(res.body.iss).is.equalTo('auth.example.com');
assert.that(res.body.sub).is.equalTo('anonymous');
assert.that(res.body.foo).is.equalTo('anonymous-bar');
done();
});
});
set('authorization', 'Bearer invalidtoken');
}).is.throwingAsync(ex => ex.status === 401);
});
test('returns 401 for invalid authenticated requests.', done => {
request(app).
test('returns 401 for tokens with invalid characters.', async () => {
await assert.that(async () => {
await request(app).
get('/').
set('accept', 'application/json').
set('authorization', 'Bearer invalidtoken').
end((err, res) => {
assert.that(err).is.null();
assert.that(res.statusCode).is.equalTo(401);
done();
});
set('authorization', 'Bearer invalid#token');
}).is.throwingAsync(ex => ex.status === 401);
});
test('returns 401 for expired tokens.', async () => {
limes = new Limes({
identityProviders: [ identityProviderExpired ]
});
test('returns 401 for tokens with invalid characters.', done => {
request(app).
get('/').
set('accept', 'application/json').
set('authorization', 'Bearer invalid token').
end((err, res) => {
assert.that(err).is.null();
assert.that(res.statusCode).is.equalTo(401);
done();
});
const expiredToken = limes.issueToken({
issuer: 'https://auth.thenativeweb.io',
subject: 'jane.doe'
});
test('returns 401 for expired requests.', done => {
const expiredToken = limesInThePast.issueTokenFor('test.domain.com', {
foo: 'authenticated-bar'
});
request(app).
await assert.that(async () => {
await request(app).
get('/').
set('accept', 'application/json').
set('authorization', `Bearer ${expiredToken}`).
end((err, res) => {
assert.that(err).is.null();
assert.that(res.statusCode).is.equalTo(401);
done();
});
set('authorization', `Bearer ${expiredToken}`);
}).is.throwingAsync(ex => ex.status === 401);
});
test('returns 401 for tokens that were issued by an unknown identity provider.', async () => {
const otherLimes = new Limes({
identityProviders: [ identityProviderUnknown ]
});
test('returns 401 for authenticated requests with wrong issuer.', done => {
const token = limesOfSomebodyElse.issueTokenFor('test.domain.com', {
foo: 'authenticated-bar'
});
const token = otherLimes.issueToken({
issuer: 'https://auth.example.com',
subject: 'jane.doe'
});
request(app).
await assert.that(async () => {
await request(app).
get('/').
set('accept', 'application/json').
set('authorization', `Bearer ${token}`).
end((err, res) => {
assert.that(err).is.null();
assert.that(res.statusCode).is.equalTo(401);
done();
});
set('authorization', `Bearer ${token}`);
}).is.throwingAsync(ex => ex.status === 401);
});
test('returns a decoded token for valid tokens.', async () => {
const token = limes.issueToken({
issuer: 'https://auth.thenativeweb.io',
subject: 'jane.doe'
});
test('returns a decoded token for authenticated requests.', done => {
const token = limes.issueTokenFor('test.domain.com', {
foo: 'authenticated-bar'
});
const { status, body } = await request(app).
get('/').
set('accept', 'application/json').
set('authorization', `Bearer ${token}`);
request(app).
get('/').
set('accept', 'application/json').
set('authorization', `Bearer ${token}`).
end((err, res) => {
assert.that(err).is.null();
assert.that(res.statusCode).is.equalTo(200);
assert.that(res.body.iss).is.equalTo('auth.example.com');
assert.that(res.body.sub).is.equalTo('test.domain.com');
assert.that(res.body.foo).is.equalTo('authenticated-bar');
done();
});
assert.that(status).is.equalTo(200);
assert.that(body.iss).is.equalTo('https://auth.thenativeweb.io');
assert.that(body.sub).is.equalTo('jane.doe');
});
test('returns a decoded token for valid tokens sent using the query string.', async () => {
const token = limes.issueToken({
issuer: 'https://auth.thenativeweb.io',
subject: 'jane.doe'
});
test('returns a decoded token for authenticated requests using query string.', done => {
const token = limes.issueTokenFor('test.domain.com', {
foo: 'authenticated-bar'
});
const { status, body } = await request(app).
get(`/?token=${token}`).
set('accept', 'application/json');
request(app).
get(`/?token=${token}`).
set('accept', 'application/json').
end((err, res) => {
assert.that(err).is.null();
assert.that(res.statusCode).is.equalTo(200);
assert.that(res.body.iss).is.equalTo('auth.example.com');
assert.that(res.body.sub).is.equalTo('test.domain.com');
assert.that(res.body.foo).is.equalTo('authenticated-bar');
done();
});
});
assert.that(status).is.equalTo(200);
assert.that(body.iss).is.equalTo('https://auth.thenativeweb.io');
assert.that(body.sub).is.equalTo('jane.doe');
});

@@ -346,0 +388,0 @@ });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc