Comparing version 2.1.2 to 2.1.3
## Changelog | ||
2.1.3: | ||
* Add thread-id to notification payload (#484) | ||
* Workaround an issue where APNS would return an HTTP 500 and make the connection unusable (#449, #480) | ||
* Regularily renew JSON web token (#449, #480) | ||
2.1.2: | ||
@@ -4,0 +9,0 @@ * Add TypeScript type definitions (#441, #465) |
@@ -13,4 +13,4 @@ "use strict"; | ||
let users = [ | ||
{ name: "Wendy", "devices": ["<insert device token>", "<insert device token>"]}, | ||
{ name: "John", "devices": ["<insert device token>"]}, | ||
{ name: "Wendy", "devices": ["<insert device token>", "<insert device token>"]}, | ||
{ name: "John", "devices": ["<insert device token>"]}, | ||
]; | ||
@@ -25,20 +25,20 @@ | ||
let note = new apn.Notification(); | ||
note.alert = `Hey ${user.name}, I just sent my first Push Notification`; | ||
let note = new apn.Notification(); | ||
note.alert = `Hey ${user.name}, I just sent my first Push Notification`; | ||
// The topic is usually the bundle identifier of your application. | ||
note.topic = "<bundle identifier>"; | ||
// The topic is usually the bundle identifier of your application. | ||
note.topic = "<bundle identifier>"; | ||
console.log(`Sending: ${note.compile()} to ${user.devices}`); | ||
console.log(`Sending: ${note.compile()} to ${user.devices}`); | ||
service.send(note, user.devices).then( result => { | ||
console.log("sent:", result.sent.length); | ||
console.log("failed:", result.failed.length); | ||
console.log(result.failed); | ||
}); | ||
service.send(note, user.devices).then( result => { | ||
console.log("sent:", result.sent.length); | ||
console.log("failed:", result.failed.length); | ||
console.log(result.failed); | ||
}); | ||
}); | ||
// For one-shot notification tasks you may wish to shutdown the connection | ||
// after everything is sent, but only call shutdown if you need your | ||
// after everything is sent, but only call shutdown if you need your | ||
// application to terminate. | ||
service.shutdown(); |
285
index.d.ts
@@ -1,171 +0,168 @@ | ||
export interface ProviderToken { | ||
/** | ||
* The filename of the provider token key (as supplied by Apple) to load from disk, or a Buffer/String containing the key data. | ||
*/ | ||
key: Buffer|string; | ||
/** | ||
* The ID of the key issued by Apple | ||
*/ | ||
keyId: string; | ||
/** | ||
* ID of the team associated with the provider token key | ||
*/ | ||
teamId: string; | ||
/** | ||
* The filename of the provider token key (as supplied by Apple) to load from disk, or a Buffer/String containing the key data. | ||
*/ | ||
key: Buffer|string; | ||
/** | ||
* The ID of the key issued by Apple | ||
*/ | ||
keyId: string; | ||
/** | ||
* ID of the team associated with the provider token key | ||
*/ | ||
teamId: string; | ||
} | ||
export interface ProviderOptions { | ||
/** | ||
* Configuration for Provider Authentication Tokens. (Defaults to: null i.e. fallback to Certificates) | ||
*/ | ||
token?: ProviderToken; | ||
/** | ||
* The filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data. (Defaults to: `cert.pem`) | ||
*/ | ||
cert?: string|Buffer; | ||
/** | ||
* The filename of the connection key to load from disk, or a Buffer/String containing the key data. (Defaults to: `key.pem`) | ||
*/ | ||
key?: string|Buffer; | ||
/** | ||
* An array of trusted certificates. Each element should contain either a filename to load, or a Buffer/String (in PEM format) to be used directly. If this is omitted several well known "root" CAs will be used. - You may need to use this as some environments don't include the CA used by Apple (entrust_2048). | ||
*/ | ||
ca?: (string|Buffer)[]; | ||
/** | ||
* File path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer containing the PFX data. If supplied will always be used instead of certificate and key above. | ||
*/ | ||
pfx?: string|Buffer; | ||
/** | ||
* The passphrase for the connection key, if required | ||
*/ | ||
passphrase?: string; | ||
/** | ||
* Specifies which environment to connect to: Production (if true) or Sandbox - The hostname will be set automatically. (Defaults to NODE_ENV == "production", i.e. false unless the NODE_ENV environment variable is set accordingly) | ||
*/ | ||
production?: boolean; | ||
/** | ||
* Reject Unauthorized property to be passed through to tls.connect() (Defaults to `true`) | ||
*/ | ||
rejectUnauthorized?: boolean; | ||
/** | ||
* The maximum number of connection failures that will be tolerated before `apn` will "terminate". (Defaults to: 3) | ||
*/ | ||
connectionRetryLimit?: number; | ||
/** | ||
* Configuration for Provider Authentication Tokens. (Defaults to: null i.e. fallback to Certificates) | ||
*/ | ||
token?: ProviderToken; | ||
/** | ||
* The filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data. (Defaults to: `cert.pem`) | ||
*/ | ||
cert?: string|Buffer; | ||
/** | ||
* The filename of the connection key to load from disk, or a Buffer/String containing the key data. (Defaults to: `key.pem`) | ||
*/ | ||
key?: string|Buffer; | ||
/** | ||
* An array of trusted certificates. Each element should contain either a filename to load, or a Buffer/String (in PEM format) to be used directly. If this is omitted several well known "root" CAs will be used. - You may need to use this as some environments don't include the CA used by Apple (entrust_2048). | ||
*/ | ||
ca?: (string|Buffer)[]; | ||
/** | ||
* File path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer containing the PFX data. If supplied will always be used instead of certificate and key above. | ||
*/ | ||
pfx?: string|Buffer; | ||
/** | ||
* The passphrase for the connection key, if required | ||
*/ | ||
passphrase?: string; | ||
/** | ||
* Specifies which environment to connect to: Production (if true) or Sandbox - The hostname will be set automatically. (Defaults to NODE_ENV == "production", i.e. false unless the NODE_ENV environment variable is set accordingly) | ||
*/ | ||
production?: boolean; | ||
/** | ||
* Reject Unauthorized property to be passed through to tls.connect() (Defaults to `true`) | ||
*/ | ||
rejectUnauthorized?: boolean; | ||
/** | ||
* The maximum number of connection failures that will be tolerated before `apn` will "terminate". (Defaults to: 3) | ||
*/ | ||
connectionRetryLimit?: number; | ||
} | ||
export interface ResponseSent { | ||
device: string; | ||
device: string; | ||
} | ||
export interface ResponseFailure { | ||
device: string; | ||
error?: Error; | ||
status?: string; | ||
response?: { | ||
reason: string; | ||
}; | ||
device: string; | ||
error?: Error; | ||
status?: string; | ||
response?: { | ||
reason: string; | ||
timestamp?: string; | ||
}; | ||
} | ||
export interface Responses { | ||
sent: ResponseSent[]; | ||
failed: ResponseFailure[]; | ||
sent: ResponseSent[]; | ||
failed: ResponseFailure[]; | ||
} | ||
export class Provider { | ||
constructor(options: ProviderOptions); | ||
/** | ||
* This is main interface for sending notifications. Create a Notification object and pass it in, along with a single recipient or an array of them and node-apn will take care of the rest, delivering a copy of the notification to each recipient. | ||
* | ||
* A "recipient" is a String containing the hex-encoded device token. | ||
*/ | ||
send(notification: Notification, recipients: string|string[]): Promise<Responses>; | ||
/** | ||
* Indicate to node-apn that it should close all open connections when the queue of pending notifications is fully drained. This will allow your application to terminate. | ||
*/ | ||
shutdown(): void; | ||
constructor(options: ProviderOptions); | ||
/** | ||
* This is main interface for sending notifications. Create a Notification object and pass it in, along with a single recipient or an array of them and node-apn will take care of the rest, delivering a copy of the notification to each recipient. | ||
* | ||
* A "recipient" is a String containing the hex-encoded device token. | ||
*/ | ||
send(notification: Notification, recipients: string|string[]): Promise<Responses>; | ||
/** | ||
* Indicate to node-apn that it should close all open connections when the queue of pending notifications is fully drained. This will allow your application to terminate. | ||
*/ | ||
shutdown(): void; | ||
} | ||
export interface NotificationAlertOptions { | ||
title?: string; | ||
subtitle?: string; | ||
body: string; | ||
"title-loc-key"?: string; | ||
"title-loc-args"?: string[]; | ||
"action-loc-key"?: string; | ||
"loc-key"?: string; | ||
"loc-args"?: string[]; | ||
"launch-image"?: string; | ||
title?: string; | ||
subtitle?: string; | ||
body: string; | ||
"title-loc-key"?: string; | ||
"title-loc-args"?: string[]; | ||
"action-loc-key"?: string; | ||
"loc-key"?: string; | ||
"loc-args"?: string[]; | ||
"launch-image"?: string; | ||
} | ||
export class Notification { | ||
/** | ||
* You can optionally pass in an object representing the payload, or configure properties on the returned object. | ||
*/ | ||
constructor(payload?: any); | ||
/** | ||
* You can optionally pass in an object representing the payload, or configure properties on the returned object. | ||
*/ | ||
constructor(payload?: any); | ||
/** | ||
* Required: The destination topic for the notification. | ||
*/ | ||
public topic: string; | ||
/** | ||
* A UUID to identify the notification with APNS. If an id is not supplied, APNS will generate one automatically. If an error occurs the response will contain the id. This property populates the apns-id header. | ||
*/ | ||
public id: string; | ||
/** | ||
* The UNIX timestamp representing when the notification should expire. This does not contribute to the 2048 byte payload size limit. An expiry of 0 indicates that the notification expires immediately. | ||
*/ | ||
public expiry: number; | ||
/** | ||
* Provide one of the following values: | ||
* | ||
* - 10 - The push message is sent immediately. (Default) | ||
* > The push notification must trigger an alert, sound, or badge on the device. It is an error use this priority for a push that contains only the content-available key. | ||
* - 5 - The push message is sent at a time that conserves power on the device receiving it. | ||
*/ | ||
public priority: number; | ||
/** | ||
* Required: The destination topic for the notification. | ||
*/ | ||
public topic: string; | ||
/** | ||
* A UUID to identify the notification with APNS. If an id is not supplied, APNS will generate one automatically. If an error occurs the response will contain the id. This property populates the apns-id header. | ||
*/ | ||
public id: string; | ||
/** | ||
* The UNIX timestamp representing when the notification should expire. This does not contribute to the 2048 byte payload size limit. An expiry of 0 indicates that the notification expires immediately. | ||
*/ | ||
public expiry: number; | ||
/** | ||
* Provide one of the following values: | ||
* | ||
* - 10 - The push message is sent immediately. (Default) | ||
* > The push notification must trigger an alert, sound, or badge on the device. It is an error use this priority for a push that contains only the content-available key. | ||
* - 5 - The push message is sent at a time that conserves power on the device receiving it. | ||
*/ | ||
public priority: number; | ||
public collapseId: string; | ||
public threadId: string; | ||
public collapseId: string; | ||
public threadId: string; | ||
/** | ||
* This Object is JSON encoded and sent as the notification payload. When properties have been set on notification.aps (either directly or with convenience setters) these are added to the payload just before it is sent. If payload already contains an aps property it is replaced. | ||
*/ | ||
public payload: any; | ||
/** | ||
* If supplied this payload will be encoded and transmitted as-is. The convenience setters will have no effect on the JSON output. | ||
*/ | ||
public rawPayload: any; | ||
/** | ||
* This Object is JSON encoded and sent as the notification payload. When properties have been set on notification.aps (either directly or with convenience setters) these are added to the payload just before it is sent. If payload already contains an aps property it is replaced. | ||
*/ | ||
public payload: any; | ||
/** | ||
* If supplied this payload will be encoded and transmitted as-is. The convenience setters will have no effect on the JSON output. | ||
*/ | ||
public rawPayload: any; | ||
/** | ||
* The value to specify for `payload.aps.badge` | ||
*/ | ||
public badge: number; | ||
/** | ||
* The value to specify for `payload.aps.sound` | ||
*/ | ||
public sound: string; | ||
/** | ||
* The value to specify for `payload.aps.alert` can be either a `String` or an `Object` as outlined by the payload documentation. | ||
*/ | ||
public alert: string|NotificationAlertOptions; | ||
/** | ||
* Setting this to true will specify "content-available" in the payload when it is compiled. | ||
*/ | ||
public contentAvailable: boolean; | ||
/** | ||
* | ||
*/ | ||
public mutableContent: boolean; | ||
/** | ||
* The value to specify for the `mdm` field where applicable. | ||
*/ | ||
public mdm: string|Object; | ||
/** | ||
* The value to specify for `payload.aps['url-args']`. This used for Safari Push NOtifications and should be an array of values in accordance with the Web Payload Documentation. | ||
*/ | ||
public urlArgs: string[]; | ||
/** | ||
* The value to specify for `payload.aps.badge` | ||
*/ | ||
public badge: number; | ||
/** | ||
* The value to specify for `payload.aps.sound` | ||
*/ | ||
public sound: string; | ||
/** | ||
* The value to specify for `payload.aps.alert` can be either a `String` or an `Object` as outlined by the payload documentation. | ||
*/ | ||
public alert: string|NotificationAlertOptions; | ||
/** | ||
* Setting this to true will specify "content-available" in the payload when it is compiled. | ||
*/ | ||
public contentAvailable: boolean; | ||
/** | ||
* | ||
*/ | ||
public mutableContent: boolean; | ||
/** | ||
* The value to specify for the `mdm` field where applicable. | ||
*/ | ||
public mdm: string|Object; | ||
/** | ||
* The value to specify for `payload.aps['url-args']`. This used for Safari Push NOtifications and should be an array of values in accordance with the Web Payload Documentation. | ||
*/ | ||
public urlArgs: string[]; | ||
} | ||
export function token(token: (string | Buffer)) : string |
30
index.js
const debug = require("debug")("apn"); | ||
const credentials = require("./lib/credentials")({ | ||
logger: debug | ||
logger: debug | ||
}); | ||
const config = require("./lib/config")({ | ||
logger: debug, | ||
prepareCertificate: credentials.certificate, | ||
prepareToken: credentials.token, | ||
prepareCA: credentials.ca, | ||
logger: debug, | ||
prepareCertificate: credentials.certificate, | ||
prepareToken: credentials.token, | ||
prepareCA: credentials.ca, | ||
}); | ||
@@ -20,16 +20,16 @@ | ||
const protocol = { | ||
Serializer: framer.Serializer, | ||
Deserializer: framer.Deserializer, | ||
Compressor: compressor.Compressor, | ||
Decompressor: compressor.Decompressor, | ||
Connection: require("http2/lib/protocol/connection").Connection, | ||
Serializer: framer.Serializer, | ||
Deserializer: framer.Deserializer, | ||
Compressor: compressor.Compressor, | ||
Decompressor: compressor.Decompressor, | ||
Connection: require("http2/lib/protocol/connection").Connection, | ||
}; | ||
const Endpoint = require("./lib/protocol/endpoint")({ | ||
tls, | ||
protocol, | ||
tls, | ||
protocol, | ||
}); | ||
const EndpointManager = require("./lib/protocol/endpointManager")({ | ||
Endpoint, | ||
Endpoint, | ||
}); | ||
@@ -51,5 +51,5 @@ | ||
module.exports = { | ||
Provider, | ||
Notification, | ||
Provider, | ||
Notification, | ||
token, | ||
}; |
@@ -63,2 +63,5 @@ "use strict"; | ||
if (this.config.token) { | ||
if (this.config.token.isExpired(3300)) { | ||
this.config.token.regenerate(this.config.token.generation); | ||
} | ||
headers.authorization = "bearer " + this.config.token.current; | ||
@@ -82,2 +85,7 @@ tokenGeneration = this.config.token.generation; | ||
return; | ||
} else if (status === "500" && response.reason === "InternalServerError") { | ||
stream.connection.close(); | ||
let error = new VError("Error 500, stream ended unexpectedly"); | ||
resolve({ device, error }); | ||
return; | ||
} | ||
@@ -84,0 +92,0 @@ |
@@ -29,2 +29,3 @@ "use strict"; | ||
connectionRetryLimit: 10, | ||
heartBeat: 60000, | ||
}; | ||
@@ -31,0 +32,0 @@ |
@@ -7,30 +7,30 @@ "use strict"; | ||
function APNCertificate(cert) { | ||
if(!cert.publicKey || !cert.validity || !cert.subject) { | ||
throw new Error("certificate object is invalid"); | ||
} | ||
if(!cert.publicKey || !cert.validity || !cert.subject) { | ||
throw new Error("certificate object is invalid"); | ||
} | ||
this._cert = cert; | ||
this._cert = cert; | ||
} | ||
APNCertificate.prototype.key = function() { | ||
return new APNKey(this._cert.publicKey); | ||
return new APNKey(this._cert.publicKey); | ||
}; | ||
APNCertificate.prototype.validity = function() { | ||
return this._cert.validity; | ||
return this._cert.validity; | ||
}; | ||
APNCertificate.prototype.environment = function() { | ||
let environment = { sandbox: false, production: false }; | ||
if (this._cert.getExtension({ "id": oids.applePushServiceClientDevelopment })) { | ||
environment.sandbox = true; | ||
} | ||
let environment = { sandbox: false, production: false }; | ||
if (this._cert.getExtension({ "id": oids.applePushServiceClientProduction })) { | ||
environment.production = true; | ||
} | ||
return environment; | ||
if (this._cert.getExtension({ "id": oids.applePushServiceClientDevelopment })) { | ||
environment.sandbox = true; | ||
} | ||
if (this._cert.getExtension({ "id": oids.applePushServiceClientProduction })) { | ||
environment.production = true; | ||
} | ||
return environment; | ||
}; | ||
module.exports = APNCertificate; | ||
module.exports = APNCertificate; |
@@ -6,13 +6,13 @@ "use strict"; | ||
function APNKey(key) { | ||
if(!key || !key.n || !key.e) { | ||
throw new Error("key is not a valid public key"); | ||
} | ||
if(!key || !key.n || !key.e) { | ||
throw new Error("key is not a valid public key"); | ||
} | ||
this._key = key; | ||
this._key = key; | ||
} | ||
APNKey.prototype.fingerprint = function() { | ||
return forge.pki.getPublicKeyFingerprint(this._key, {encoding: "hex"}); | ||
return forge.pki.getPublicKeyFingerprint(this._key, {encoding: "hex"}); | ||
}; | ||
module.exports = APNKey; | ||
module.exports = APNKey; |
"use strict"; | ||
var oids = { | ||
"applePushServiceClientDevelopment" : "1.2.840.113635.100.6.3.1", | ||
"applePushServiceClientProduction" : "1.2.840.113635.100.6.3.2", | ||
"applePushServiceClientDevelopment" : "1.2.840.113635.100.6.3.1", | ||
"applePushServiceClientProduction" : "1.2.840.113635.100.6.3.2", | ||
}; | ||
module.exports = oids; | ||
module.exports = oids; |
"use strict"; | ||
module.exports = function (dependencies) { | ||
const parsePkcs12 = dependencies.parsePkcs12; | ||
const parsePemKey = dependencies.parsePemKey; | ||
const parsePemCert = dependencies.parsePemCert; | ||
function parse(credentials) { | ||
var parsed = {}; | ||
const parsePkcs12 = dependencies.parsePkcs12; | ||
const parsePemKey = dependencies.parsePemKey; | ||
const parsePemCert = dependencies.parsePemCert; | ||
function parse(credentials) { | ||
var parsed = {}; | ||
parsed.key = parsePemKey(credentials.key, credentials.passphrase); | ||
parsed.certificates = parsePemCert(credentials.cert); | ||
parsed.key = parsePemKey(credentials.key, credentials.passphrase); | ||
parsed.certificates = parsePemCert(credentials.cert); | ||
var pkcs12Parsed = parsePkcs12(credentials.pfx, credentials.passphrase); | ||
if (pkcs12Parsed) { | ||
parsed.key = pkcs12Parsed.key; | ||
parsed.certificates = pkcs12Parsed.certificates; | ||
} | ||
var pkcs12Parsed = parsePkcs12(credentials.pfx, credentials.passphrase); | ||
if (pkcs12Parsed) { | ||
parsed.key = pkcs12Parsed.key; | ||
parsed.certificates = pkcs12Parsed.certificates; | ||
} | ||
return parsed; | ||
} | ||
return parsed; | ||
} | ||
return parse; | ||
}; | ||
return parse; | ||
}; |
@@ -8,29 +8,29 @@ "use strict"; | ||
function apnCertificateFromPem(certData) { | ||
if (!certData) { | ||
return null; | ||
} | ||
if (!certData) { | ||
return null; | ||
} | ||
var pemMessages; | ||
try { | ||
pemMessages = forge.pem.decode(certData); | ||
} | ||
catch (e) { | ||
if (e.message.match("Invalid PEM formatted message.")) { | ||
throw new Error("unable to parse certificate, not a valid PEM file"); | ||
} | ||
} | ||
var certificates = []; | ||
var pemMessages; | ||
try { | ||
pemMessages = forge.pem.decode(certData); | ||
} | ||
catch (e) { | ||
if (e.message.match("Invalid PEM formatted message.")) { | ||
throw new Error("unable to parse certificate, not a valid PEM file"); | ||
} | ||
} | ||
var certificates = []; | ||
pemMessages.forEach(function(message) { | ||
if (!message.type.match(new RegExp("CERTIFICATE$"))) { | ||
return; | ||
} | ||
var certAsn1 = forge.asn1.fromDer(message.body); | ||
var forgeCertificate = forge.pki.certificateFromAsn1(certAsn1); | ||
pemMessages.forEach(function(message) { | ||
if (!message.type.match(new RegExp("CERTIFICATE$"))) { | ||
return; | ||
} | ||
var certAsn1 = forge.asn1.fromDer(message.body); | ||
var forgeCertificate = forge.pki.certificateFromAsn1(certAsn1); | ||
certificates.push(new APNCertificate(forgeCertificate)); | ||
}); | ||
return certificates; | ||
certificates.push(new APNCertificate(forgeCertificate)); | ||
}); | ||
return certificates; | ||
} | ||
module.exports = apnCertificateFromPem; | ||
module.exports = apnCertificateFromPem; |
@@ -8,54 +8,54 @@ "use strict"; | ||
function findAndDecryptKey(pemMessages, passphrase) { | ||
let apnKey = null; | ||
pemMessages.forEach(function(message) { | ||
if (!message.type.match(/KEY/)) { | ||
return; | ||
} | ||
let apnKey = null; | ||
pemMessages.forEach(function(message) { | ||
if (!message.type.match(/KEY/)) { | ||
return; | ||
} | ||
let key = forge.pki.decryptRsaPrivateKey(forge.pem.encode(message), passphrase); | ||
let key = forge.pki.decryptRsaPrivateKey(forge.pem.encode(message), passphrase); | ||
if(!key) { | ||
if ((message.procType && message.procType.type === "ENCRYPTED") || message.type.match(/ENCRYPTED/)) { | ||
throw new Error("unable to parse key, incorrect passphrase"); | ||
} | ||
} | ||
else if(apnKey) { | ||
throw new Error("multiple keys found in PEM file"); | ||
} | ||
else { | ||
apnKey = new APNKey(key); | ||
} | ||
}); | ||
return apnKey; | ||
if(!key) { | ||
if ((message.procType && message.procType.type === "ENCRYPTED") || message.type.match(/ENCRYPTED/)) { | ||
throw new Error("unable to parse key, incorrect passphrase"); | ||
} | ||
} | ||
else if(apnKey) { | ||
throw new Error("multiple keys found in PEM file"); | ||
} | ||
else { | ||
apnKey = new APNKey(key); | ||
} | ||
}); | ||
return apnKey; | ||
} | ||
function apnKeyFromPem(keyPem, passphrase) { | ||
if (!keyPem) { | ||
return null; | ||
} | ||
if (!keyPem) { | ||
return null; | ||
} | ||
try { | ||
let pemMessages = forge.pem.decode(keyPem); | ||
let apnKey = findAndDecryptKey(pemMessages, passphrase); | ||
if (apnKey) { | ||
return apnKey; | ||
} | ||
} | ||
catch (e) { | ||
if (e.message.match(/Unsupported OID/)) { | ||
throw new Error("unable to parse key, unsupported format: " + e.oid); | ||
} | ||
else if(e.message.match(/Invalid PEM formatted message/)) { | ||
throw new Error("unable to parse key, not a valid PEM file"); | ||
} | ||
else if (e.message.match(/multiple keys/)) { | ||
throw e; | ||
} | ||
else if (e.message.match(/unable to parse key/)) { | ||
throw e; | ||
} | ||
} | ||
throw new Error("unable to parse key, no private key found"); | ||
try { | ||
let pemMessages = forge.pem.decode(keyPem); | ||
let apnKey = findAndDecryptKey(pemMessages, passphrase); | ||
if (apnKey) { | ||
return apnKey; | ||
} | ||
} | ||
catch (e) { | ||
if (e.message.match(/Unsupported OID/)) { | ||
throw new Error("unable to parse key, unsupported format: " + e.oid); | ||
} | ||
else if(e.message.match(/Invalid PEM formatted message/)) { | ||
throw new Error("unable to parse key, not a valid PEM file"); | ||
} | ||
else if (e.message.match(/multiple keys/)) { | ||
throw e; | ||
} | ||
else if (e.message.match(/unable to parse key/)) { | ||
throw e; | ||
} | ||
} | ||
throw new Error("unable to parse key, no private key found"); | ||
} | ||
module.exports = apnKeyFromPem; |
@@ -9,52 +9,52 @@ "use strict"; | ||
function decryptPkcs12FromAsn1(asn1, passphrase) { | ||
try { | ||
return forge.pkcs12.pkcs12FromAsn1(asn1, false, passphrase); | ||
} | ||
catch (e) { | ||
// OpenSSL-exported files need an empty string, if no password was specified | ||
// during export. | ||
if (passphrase) { | ||
throw e; | ||
} | ||
return forge.pkcs12.pkcs12FromAsn1(asn1, false, ""); | ||
} | ||
try { | ||
return forge.pkcs12.pkcs12FromAsn1(asn1, false, passphrase); | ||
} | ||
catch (e) { | ||
// OpenSSL-exported files need an empty string, if no password was specified | ||
// during export. | ||
if (passphrase) { | ||
throw e; | ||
} | ||
return forge.pkcs12.pkcs12FromAsn1(asn1, false, ""); | ||
} | ||
} | ||
function apnCredentialsFromPkcs12(p12Data, passphrase) { | ||
if (!p12Data) { | ||
return; | ||
} | ||
if (!p12Data) { | ||
return; | ||
} | ||
let asn1 = forge.asn1.fromDer(p12Data.toString("binary"), false); | ||
let pkcs12; | ||
try { | ||
pkcs12 = decryptPkcs12FromAsn1(asn1, passphrase); | ||
} | ||
catch(e) { | ||
if (e.message.match("Invalid password")) { | ||
throw new Error("unable to parse credentials, incorrect passphrase"); | ||
} | ||
else { | ||
throw new Error("unable to parse credentials, not a PFX/P12 file"); | ||
} | ||
} | ||
let asn1 = forge.asn1.fromDer(p12Data.toString("binary"), false); | ||
let pkcs12; | ||
try { | ||
pkcs12 = decryptPkcs12FromAsn1(asn1, passphrase); | ||
} | ||
catch(e) { | ||
if (e.message.match("Invalid password")) { | ||
throw new Error("unable to parse credentials, incorrect passphrase"); | ||
} | ||
else { | ||
throw new Error("unable to parse credentials, not a PFX/P12 file"); | ||
} | ||
} | ||
let credentials = { "key": null, "certificates": []}; | ||
pkcs12.safeContents.forEach(function(safeContents) { | ||
safeContents.safeBags.forEach(function(safeBag) { | ||
if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) { | ||
if(credentials.key) { | ||
throw new Error("multiple keys found in PFX/P12 file"); | ||
} | ||
credentials.key = new APNKey(safeBag.key); | ||
} | ||
else if(safeBag.type === forge.pki.oids.certBag) { | ||
credentials.certificates.push(new APNCertificate(safeBag.cert)); | ||
} | ||
}); | ||
}); | ||
let credentials = { "key": null, "certificates": []}; | ||
pkcs12.safeContents.forEach(function(safeContents) { | ||
safeContents.safeBags.forEach(function(safeBag) { | ||
if(safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) { | ||
if(credentials.key) { | ||
throw new Error("multiple keys found in PFX/P12 file"); | ||
} | ||
credentials.key = new APNKey(safeBag.key); | ||
} | ||
else if(safeBag.type === forge.pki.oids.certBag) { | ||
credentials.certificates.push(new APNCertificate(safeBag.cert)); | ||
} | ||
}); | ||
}); | ||
return credentials; | ||
return credentials; | ||
} | ||
module.exports = apnCredentialsFromPkcs12; | ||
module.exports = apnCredentialsFromPkcs12; |
"use strict"; | ||
function validateCredentials(credentials) { | ||
let certificate = credentials.certificates[0]; | ||
let certificate = credentials.certificates[0]; | ||
if (credentials.key.fingerprint() !== certificate.key().fingerprint()) { | ||
throw new Error("certificate and key do not match"); | ||
} | ||
if (credentials.key.fingerprint() !== certificate.key().fingerprint()) { | ||
throw new Error("certificate and key do not match"); | ||
} | ||
let validity = certificate.validity(); | ||
if (validity.notAfter.getTime() < Date.now()) { | ||
throw new Error("certificate has expired: " + validity.notAfter.toJSON()); | ||
} | ||
let validity = certificate.validity(); | ||
if (validity.notAfter.getTime() < Date.now()) { | ||
throw new Error("certificate has expired: " + validity.notAfter.toJSON()); | ||
} | ||
if (credentials.production !== undefined) { | ||
let environment = certificate.environment(); | ||
if ( (credentials.production && !environment.production) || | ||
(!credentials.production && !environment.sandbox)) { | ||
throw new Error("certificate does not support configured environment, production: " + credentials.production); | ||
} | ||
} | ||
if (credentials.production !== undefined) { | ||
let environment = certificate.environment(); | ||
if ( (credentials.production && !environment.production) || | ||
(!credentials.production && !environment.sandbox)) { | ||
throw new Error("certificate does not support configured environment, production: " + credentials.production); | ||
} | ||
} | ||
} | ||
module.exports = validateCredentials; | ||
module.exports = validateCredentials; |
"use strict"; | ||
module.exports = function (dependencies) { | ||
const logger = dependencies.logger; | ||
const logger = dependencies.logger; | ||
const resolve = require("./resolve"); | ||
const resolve = require("./resolve"); | ||
const parseCertificate = require("./certificate/parse")({ | ||
parsePkcs12: require("./certificate/parsePkcs12"), | ||
parsePemKey: require("./certificate/parsePemKey"), | ||
parsePemCert: require("./certificate/parsePemCertificate"), | ||
}); | ||
const parseCertificate = require("./certificate/parse")({ | ||
parsePkcs12: require("./certificate/parsePkcs12"), | ||
parsePemKey: require("./certificate/parsePemKey"), | ||
parsePemCert: require("./certificate/parsePemCertificate"), | ||
}); | ||
const loadCertificate = require("./certificate/load")({ | ||
resolve | ||
}); | ||
const loadCertificate = require("./certificate/load")({ | ||
resolve | ||
}); | ||
const prepareCertificate = require("./certificate/prepare")({ | ||
load: loadCertificate, | ||
parse: parseCertificate, | ||
validate: require("./certificate/validate"), | ||
logger: logger, | ||
}); | ||
const prepareCertificate = require("./certificate/prepare")({ | ||
load: loadCertificate, | ||
parse: parseCertificate, | ||
validate: require("./certificate/validate"), | ||
logger: logger, | ||
}); | ||
const sign = require("jsonwebtoken/sign"); | ||
const sign = require("jsonwebtoken/sign"); | ||
const decode = require("jsonwebtoken/decode"); | ||
const prepareToken = require("./token/prepare")({ | ||
sign, | ||
resolve, | ||
}); | ||
const prepareToken = require("./token/prepare")({ | ||
sign, | ||
resolve, | ||
decode | ||
}); | ||
const prepareCA = require("./ca/prepare")({ | ||
resolve, | ||
}); | ||
const prepareCA = require("./ca/prepare")({ | ||
resolve, | ||
}); | ||
return { | ||
certificate: prepareCertificate, | ||
token: prepareToken, | ||
ca: prepareCA, | ||
}; | ||
return { | ||
certificate: prepareCertificate, | ||
token: prepareToken, | ||
ca: prepareCA, | ||
}; | ||
}; |
@@ -6,16 +6,16 @@ "use strict"; | ||
function resolveCredential(value) { | ||
if (!value) { | ||
return value; | ||
} | ||
if(/-----BEGIN ([A-Z\s*]+)-----/.test(value)) { | ||
return value; | ||
} | ||
else if(Buffer.isBuffer(value)) { | ||
return value; | ||
} | ||
else { | ||
return fs.readFileSync(value); | ||
} | ||
if (!value) { | ||
return value; | ||
} | ||
if(/-----BEGIN ([A-Z\s*]+)-----/.test(value)) { | ||
return value; | ||
} | ||
else if(Buffer.isBuffer(value)) { | ||
return value; | ||
} | ||
else { | ||
return fs.readFileSync(value); | ||
} | ||
} | ||
module.exports = resolveCredential; |
@@ -7,2 +7,3 @@ "use strict"; | ||
const sign = dependencies.sign; | ||
const decode = dependencies.decode; | ||
const resolve = dependencies.resolve; | ||
@@ -28,7 +29,16 @@ | ||
current: token(), | ||
regenerate: function (generation) { | ||
iat: null, | ||
regenerate(generation) { | ||
if (generation === this.generation) { | ||
this.generation += 1; | ||
this.current = token(); | ||
this.iat = null; | ||
} | ||
}, | ||
isExpired(validSeconds) { | ||
if (this.iat == null) { | ||
let decoded = decode(this.current); | ||
this.iat = decoded.iat; | ||
} | ||
return (Math.floor(Date.now() / 1000) - this.iat) >= validSeconds; | ||
} | ||
@@ -35,0 +45,0 @@ }; |
"use strict"; | ||
module.exports = { | ||
set alert(value) { | ||
this.aps.alert = value; | ||
}, | ||
set alert(value) { | ||
this.aps.alert = value; | ||
}, | ||
get body() { | ||
if (this.aps.alert) { | ||
return this.aps.alert.body || this.aps.alert; | ||
} | ||
return this.aps.alert; | ||
}, | ||
get body() { | ||
if (this.aps.alert) { | ||
return this.aps.alert.body || this.aps.alert; | ||
} | ||
return this.aps.alert; | ||
}, | ||
set body(value) { | ||
if(typeof this.aps.alert !== "object") { | ||
this.aps.alert = value; | ||
} | ||
else { | ||
this.prepareAlert(); | ||
this.aps.alert.body = value; | ||
} | ||
}, | ||
set body(value) { | ||
if (typeof this.aps.alert !== "object") { | ||
this.aps.alert = value; | ||
} | ||
else { | ||
this.prepareAlert(); | ||
this.aps.alert.body = value; | ||
} | ||
}, | ||
set locKey(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["loc-key"] = value; | ||
}, | ||
set locKey(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["loc-key"] = value; | ||
}, | ||
set locArgs(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["loc-args"] = value; | ||
}, | ||
set locArgs(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["loc-args"] = value; | ||
}, | ||
set title(value) { | ||
this.prepareAlert(); | ||
this.aps.alert.title = value; | ||
}, | ||
set title(value) { | ||
this.prepareAlert(); | ||
this.aps.alert.title = value; | ||
}, | ||
set subtitle(value) { | ||
this.prepareAlert(); | ||
this.aps.alert.subtitle = value; | ||
}, | ||
set subtitle(value) { | ||
this.prepareAlert(); | ||
this.aps.alert.subtitle = value; | ||
}, | ||
set titleLocKey(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["title-loc-key"] = value; | ||
}, | ||
set titleLocKey(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["title-loc-key"] = value; | ||
}, | ||
set titleLocArgs(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["title-loc-args"] = value; | ||
}, | ||
set titleLocArgs(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["title-loc-args"] = value; | ||
}, | ||
set action(value) { | ||
this.prepareAlert(); | ||
this.aps.alert.action = value; | ||
}, | ||
set action(value) { | ||
this.prepareAlert(); | ||
this.aps.alert.action = value; | ||
}, | ||
set actionLocKey(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["action-loc-key"] = value; | ||
}, | ||
set actionLocKey(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["action-loc-key"] = value; | ||
}, | ||
set launchImage(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["launch-image"] = value; | ||
}, | ||
set launchImage(value) { | ||
this.prepareAlert(); | ||
this.aps.alert["launch-image"] = value; | ||
}, | ||
set badge(value) { | ||
if (typeof value === "number" || value === undefined) { | ||
this.aps.badge = value; | ||
} | ||
}, | ||
set badge(value) { | ||
if (typeof value === "number" || value === undefined) { | ||
this.aps.badge = value; | ||
} | ||
}, | ||
set sound(value) { | ||
if (typeof value === "string" || value === undefined) { | ||
this.aps.sound = value; | ||
} | ||
}, | ||
set sound(value) { | ||
if (typeof value === "string" || value === undefined) { | ||
this.aps.sound = value; | ||
} | ||
}, | ||
set contentAvailable(value) { | ||
if (value === true || value === 1) { | ||
this.aps["content-available"] = 1; | ||
} else { | ||
this.aps["content-available"] = undefined; | ||
} | ||
}, | ||
set contentAvailable(value) { | ||
if (value === true || value === 1) { | ||
this.aps["content-available"] = 1; | ||
} else { | ||
this.aps["content-available"] = undefined; | ||
} | ||
}, | ||
set mutableContent(value) { | ||
if (value === true || value === 1) { | ||
this.aps["mutable-content"] = 1; | ||
} else { | ||
this.aps["mutable-content"] = undefined; | ||
} | ||
}, | ||
set mutableContent(value) { | ||
if (value === true || value === 1) { | ||
this.aps["mutable-content"] = 1; | ||
} else { | ||
this.aps["mutable-content"] = undefined; | ||
} | ||
}, | ||
set mdm(value) { | ||
this._mdm = value; | ||
}, | ||
set mdm(value) { | ||
this._mdm = value; | ||
}, | ||
set urlArgs(value) { | ||
if(Array.isArray(value) || value === undefined) { | ||
this.aps["url-args"] = value; | ||
} | ||
}, | ||
set urlArgs(value) { | ||
if (Array.isArray(value) || value === undefined) { | ||
this.aps["url-args"] = value; | ||
} | ||
}, | ||
set category(value) { | ||
if(typeof value === "string" || value === undefined) { | ||
this.aps.category = value; | ||
} | ||
}, | ||
set category(value) { | ||
if (typeof value === "string" || value === undefined) { | ||
this.aps.category = value; | ||
} | ||
}, | ||
prepareAlert: function () { | ||
if(typeof this.aps.alert !== "object") { | ||
this.aps.alert = {"body": this.aps.alert}; | ||
} | ||
} | ||
set threadId(value) { | ||
if(typeof value === "string" || value === undefined) { | ||
this.aps["thread-id"] = value; | ||
} | ||
}, | ||
prepareAlert: function () { | ||
if (typeof this.aps.alert !== "object") { | ||
this.aps.alert = {"body": this.aps.alert}; | ||
} | ||
} | ||
}; |
@@ -7,17 +7,17 @@ "use strict"; | ||
function Notification (payload) { | ||
this.encoding = "utf8"; | ||
this.payload = {}; | ||
this.compiled = false; | ||
this.encoding = "utf8"; | ||
this.payload = {}; | ||
this.compiled = false; | ||
this.aps = {}; | ||
this.expiry = 0; | ||
this.priority = 10; | ||
this.aps = {}; | ||
this.expiry = 0; | ||
this.priority = 10; | ||
if (payload) { | ||
for(let key in payload) { | ||
if (payload.hasOwnProperty(key)) { | ||
this[key] = payload[key]; | ||
} | ||
} | ||
} | ||
if (payload) { | ||
for(let key in payload) { | ||
if (payload.hasOwnProperty(key)) { | ||
this[key] = payload[key]; | ||
} | ||
} | ||
} | ||
} | ||
@@ -27,41 +27,38 @@ | ||
// Create setter methods for properties | ||
["payload", "expiry", "priority", "alert", "body", "locKey", | ||
"locArgs", "title", "subtitle", "titleLocKey", "titleLocArgs", "action", | ||
"actionLocKey", "launchImage", "badge", "sound", "contentAvailable", | ||
"mutableContent", "mdm", "urlArgs", "category"].forEach( propName => { | ||
const methodName = "set" + propName[0].toUpperCase() + propName.slice(1); | ||
Notification.prototype[methodName] = function (value) { | ||
this[propName] = value; | ||
return this; | ||
}; | ||
"mutableContent", "mdm", "urlArgs", "category", "threadId"].forEach( propName => { | ||
const methodName = "set" + propName[0].toUpperCase() + propName.slice(1); | ||
Notification.prototype[methodName] = function (value) { | ||
this[propName] = value; | ||
return this; | ||
}; | ||
}); | ||
Notification.prototype.headers = function headers() { | ||
let headers = {}; | ||
let headers = {}; | ||
if (this.priority !== 10) { | ||
headers["apns-priority"] = this.priority; | ||
} | ||
if (this.priority !== 10) { | ||
headers["apns-priority"] = this.priority; | ||
} | ||
if (this.id) { | ||
headers["apns-id"] = this.id; | ||
} | ||
if (this.id) { | ||
headers["apns-id"] = this.id; | ||
} | ||
if (this.expiry > 0) { | ||
headers["apns-expiration"] = this.expiry; | ||
} | ||
if (this.expiry > 0) { | ||
headers["apns-expiration"] = this.expiry; | ||
} | ||
if (this.topic) { | ||
headers["apns-topic"] = this.topic; | ||
} | ||
if (this.topic) { | ||
headers["apns-topic"] = this.topic; | ||
} | ||
if (this.collapseId) { | ||
headers["apns-collapse-id"] = this.collapseId; | ||
} | ||
if (this.collapseId) { | ||
headers["apns-collapse-id"] = this.collapseId; | ||
} | ||
if (this.threadId) { | ||
headers["thread-id"] = this.threadId; | ||
} | ||
return headers; | ||
return headers; | ||
}; | ||
@@ -75,6 +72,6 @@ | ||
Notification.prototype.compile = function () { | ||
if(!this.compiled) { | ||
this.compiled = JSON.stringify(this); | ||
} | ||
return this.compiled; | ||
if(!this.compiled) { | ||
this.compiled = JSON.stringify(this); | ||
} | ||
return this.compiled; | ||
}; | ||
@@ -87,3 +84,3 @@ | ||
Notification.prototype.length = function () { | ||
return Buffer.byteLength(this.compile(), this.encoding || "utf8"); | ||
return Buffer.byteLength(this.compile(), this.encoding || "utf8"); | ||
}; | ||
@@ -95,21 +92,21 @@ | ||
Notification.prototype.apsPayload = function() { | ||
var aps = this.aps; | ||
var aps = this.aps; | ||
return Object.keys(aps).find( key => aps[key] !== undefined ) ? aps : undefined; | ||
return Object.keys(aps).find( key => aps[key] !== undefined ) ? aps : undefined; | ||
}; | ||
Notification.prototype.toJSON = function () { | ||
if (this.rawPayload != null) { | ||
return this.rawPayload; | ||
} | ||
if (typeof this._mdm === "string") { | ||
return { "mdm": this._mdm }; | ||
} | ||
if (this.rawPayload != null) { | ||
return this.rawPayload; | ||
} | ||
this.payload.aps = this.apsPayload(); | ||
if (typeof this._mdm === "string") { | ||
return { "mdm": this._mdm }; | ||
} | ||
return this.payload; | ||
this.payload.aps = this.apsPayload(); | ||
return this.payload; | ||
}; | ||
module.exports = Notification; |
@@ -32,2 +32,5 @@ "use strict"; | ||
this._maximumStreamSlots = 0; | ||
this._lastSuccessPingedTime = null; | ||
this._pingedThreshold = (this.options.heartBeat || 60000) * 2.5; | ||
this._heartBeatInterval = (this.options.heartBeat || 60000); | ||
@@ -38,2 +41,3 @@ options.ALPNProtocols = ["h2"]; | ||
this._setupHTTP2Pipeline(); | ||
this._heartBeatIntervalCheck = this._setupHTTP2HealthCheck(); | ||
} | ||
@@ -94,2 +98,14 @@ | ||
Endpoint.prototype._setupHTTP2HealthCheck = function healthcheck() { | ||
return setInterval(() => { | ||
if (this._lastSuccessPingedTime != null && (Date.now() - this._lastSuccessPingedTime) > this._pingedThreshold) { | ||
this._error("Not receiving Ping response after " + this._pingedThreshold + " ms"); | ||
} else { | ||
this._connection.ping(() => { | ||
this._lastSuccessPingedTime = Date.now(); | ||
}); | ||
} | ||
}, this._heartBeatInterval); | ||
}; | ||
Endpoint.prototype._protocolError = function protocolError(component, errCode) { | ||
@@ -173,2 +189,3 @@ this._error(component + " error: " + errCode); | ||
Endpoint.prototype.destroy = function destroy() { | ||
clearInterval(this._heartBeatIntervalCheck); | ||
this._socket.destroy(); | ||
@@ -175,0 +192,0 @@ }; |
{ | ||
"name": "apn", | ||
"description": "An interface to the Apple Push Notification service for Node.js", | ||
"version": "2.1.2", | ||
"version": "2.1.3", | ||
"author": "Andrew Naylor <argon@mkbot.net>", | ||
@@ -31,7 +31,7 @@ "contributors": [ | ||
"dependencies": { | ||
"debug": "^2.3.0", | ||
"debug": "^2.6.0", | ||
"http2": "https://github.com/node-apn/node-http2/archive/apn-2.1.2.tar.gz", | ||
"node-forge": "^0.6.45", | ||
"jsonwebtoken": "^7.1.9", | ||
"verror": "^1.8.1" | ||
"node-forge": "^0.6.48", | ||
"jsonwebtoken": "^7.2.1", | ||
"verror": "^1.9.0" | ||
}, | ||
@@ -59,5 +59,5 @@ "devDependencies": { | ||
"engines": { | ||
"node": ">= 4.0.0" | ||
"node": ">= 4.6.0" | ||
}, | ||
"license": "MIT" | ||
} |
@@ -40,3 +40,3 @@ [<p align="center"><img src="doc/logo.png" alt="node-apn" width="450" height="auto"></p>][node-apn] | ||
$ npm install apn | ||
$ npm install apn | ||
@@ -60,8 +60,8 @@ ## Quick Start | ||
var options = { | ||
token: { | ||
key: "path/to/key.p8", | ||
keyId: "T0K3NK3Y1D", | ||
teamId: "T34M1D", | ||
}, | ||
production: false, | ||
token: { | ||
key: "path/to/key.p8", | ||
keyId: "T0K3NK3Y1D", | ||
teamId: "T34M1D" | ||
}, | ||
production: false | ||
}; | ||
@@ -102,3 +102,3 @@ | ||
apnProvider.send(note, deviceToken).then( (result) => { | ||
// see documentation for an explanation of result | ||
// see documentation for an explanation of result | ||
}); | ||
@@ -105,0 +105,0 @@ ``` |
@@ -7,2 +7,28 @@ "use strict"; | ||
function builtNotification() { | ||
return { | ||
headers: {}, | ||
body: JSON.stringify({ aps: { badge: 1 } }), | ||
}; | ||
} | ||
function FakeStream(deviceId, statusCode, response) { | ||
const fakeStream = new stream.Transform({ | ||
transform: sinon.spy(function(chunk, encoding, callback) { | ||
expect(this.headers).to.be.calledOnce; | ||
const headers = this.headers.firstCall.args[0]; | ||
expect(headers[":path"].substring(10)).to.equal(deviceId); | ||
this.emit("headers", { | ||
":status": statusCode | ||
}); | ||
callback(null, Buffer.from(JSON.stringify(response) || "")); | ||
}) | ||
}); | ||
fakeStream.headers = sinon.stub(); | ||
return fakeStream; | ||
} | ||
describe("Client", function () { | ||
@@ -126,2 +152,3 @@ let fakes, Client; | ||
regenerate: sinon.stub(), | ||
isExpired: sinon.stub() | ||
}; | ||
@@ -526,2 +553,3 @@ | ||
regenerate: sinon.stub(), | ||
isExpired: sinon.stub() | ||
} | ||
@@ -646,2 +674,35 @@ | ||
}); | ||
it("regenerate token", function () { | ||
fakes.stream = new FakeStream("abcd1234", "200"); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
fakes.token.isExpired = function (current, validSeconds) { | ||
return true; | ||
} | ||
let client = new Client({ | ||
address: "testapi", | ||
token: fakes.token | ||
}); | ||
return client.write(builtNotification(), "abcd1234") | ||
.then(function () { | ||
expect(fakes.token.generation).to.equal(1); | ||
}); | ||
}); | ||
it("internal server error", function () { | ||
fakes.stream = new FakeStream("abcd1234", "500", { reason: "InternalServerError" }); | ||
fakes.stream.connection = sinon.stub(); | ||
fakes.stream.connection.close = sinon.stub(); | ||
fakes.endpointManager.getStream.onCall(0).returns(fakes.stream); | ||
let client = new Client({ | ||
address: "testapi", | ||
token: fakes.token | ||
}); | ||
return expect(client.write(builtNotification(), "abcd1234")).to.eventually.have.deep.property("error.jse_shortmsg","Error 500, stream ended unexpectedly"); | ||
}); | ||
}); | ||
@@ -716,27 +777,1 @@ }); | ||
}); | ||
function builtNotification() { | ||
return { | ||
headers: {}, | ||
body: JSON.stringify({ aps: { badge: 1 } }), | ||
}; | ||
} | ||
function FakeStream(deviceId, statusCode, response) { | ||
const fakeStream = new stream.Transform({ | ||
transform: sinon.spy(function(chunk, encoding, callback) { | ||
expect(this.headers).to.be.calledOnce; | ||
const headers = this.headers.firstCall.args[0]; | ||
expect(headers[":path"].substring(10)).to.equal(deviceId); | ||
this.emit("headers", { | ||
":status": statusCode | ||
}); | ||
callback(null, Buffer.from(JSON.stringify(response) || "")); | ||
}) | ||
}); | ||
fakeStream.headers = sinon.stub(); | ||
return fakeStream; | ||
} |
@@ -32,2 +32,3 @@ "use strict"; | ||
connectionRetryLimit: 10, | ||
heartBeat: 60000, | ||
}); | ||
@@ -34,0 +35,0 @@ }); |
@@ -9,102 +9,102 @@ "use strict"; | ||
describe("APNCertificate", function() { | ||
let certPem; | ||
before(function() { | ||
certPem = fs.readFileSync("test/credentials/support/cert.pem"); | ||
}); | ||
let certPem; | ||
before(function() { | ||
certPem = fs.readFileSync("test/credentials/support/cert.pem"); | ||
}); | ||
let cert; | ||
beforeEach(function() { | ||
cert = forge.pki.certificateFromPem(certPem.toString()); | ||
}); | ||
let cert; | ||
beforeEach(function() { | ||
cert = forge.pki.certificateFromPem(certPem.toString()); | ||
}); | ||
describe("accepts a Certificate object", function() { | ||
it("does not throw", function() { | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.not.throw(Error); | ||
}); | ||
}); | ||
describe("accepts a Certificate object", function() { | ||
it("does not throw", function() { | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.not.throw(Error); | ||
}); | ||
}); | ||
describe("throws", function() { | ||
it("missing public key", function() { | ||
delete cert.publicKey; | ||
describe("throws", function() { | ||
it("missing public key", function() { | ||
delete cert.publicKey; | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.throw("certificate object is invalid"); | ||
}); | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.throw("certificate object is invalid"); | ||
}); | ||
it("missing validity", function() { | ||
delete cert.validity; | ||
it("missing validity", function() { | ||
delete cert.validity; | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.throw("certificate object is invalid"); | ||
}); | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.throw("certificate object is invalid"); | ||
}); | ||
it("missing subject", function() { | ||
delete cert.subject; | ||
it("missing subject", function() { | ||
delete cert.subject; | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.throw("certificate object is invalid"); | ||
}); | ||
}); | ||
expect(function() { | ||
new APNCertificate(cert); | ||
}).to.throw("certificate object is invalid"); | ||
}); | ||
}); | ||
describe("key", function() { | ||
it("returns an APNKey", function() { | ||
expect(new APNCertificate(cert).key()).to.be.an.instanceof(APNKey); | ||
}); | ||
describe("key", function() { | ||
it("returns an APNKey", function() { | ||
expect(new APNCertificate(cert).key()).to.be.an.instanceof(APNKey); | ||
}); | ||
it("returns the the certificates public key", function() { | ||
expect(new APNCertificate(cert).key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
it("returns the the certificates public key", function() { | ||
expect(new APNCertificate(cert).key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
describe("validity", function() { | ||
it("returns an object containing notBefore", function() { | ||
expect(new APNCertificate(cert).validity()) | ||
.to.have.property("notBefore") | ||
.and | ||
.to.eql(new Date("2015-01-01T00:00:00")); | ||
}); | ||
describe("validity", function() { | ||
it("returns an object containing notBefore", function() { | ||
expect(new APNCertificate(cert).validity()) | ||
.to.have.property("notBefore") | ||
.and | ||
.to.eql(new Date("2015-01-01T00:00:00")); | ||
}); | ||
it("returns an object containing notAfter", function() { | ||
expect(new APNCertificate(cert).validity()) | ||
.to.have.property("notAfter") | ||
.and | ||
.to.eql(new Date("2025-01-01T00:00:00")); | ||
}); | ||
}); | ||
it("returns an object containing notAfter", function() { | ||
expect(new APNCertificate(cert).validity()) | ||
.to.have.property("notAfter") | ||
.and | ||
.to.eql(new Date("2025-01-01T00:00:00")); | ||
}); | ||
}); | ||
describe("environment", function() { | ||
describe("development certificate", function() { | ||
it("sandbox flag", function() { | ||
expect(new APNCertificate(cert).environment().sandbox).to.be.true; | ||
}); | ||
describe("environment", function() { | ||
describe("development certificate", function() { | ||
it("sandbox flag", function() { | ||
expect(new APNCertificate(cert).environment().sandbox).to.be.true; | ||
}); | ||
it("production flag", function() { | ||
expect(new APNCertificate(cert).environment().production).to.be.false; | ||
}); | ||
}); | ||
it("production flag", function() { | ||
expect(new APNCertificate(cert).environment().production).to.be.false; | ||
}); | ||
}); | ||
describe("production certificate", function() { | ||
let productionCertPem, productionCert; | ||
before(function() { | ||
productionCertPem = fs.readFileSync("test/credentials/support/certProduction.pem"); | ||
}); | ||
beforeEach(function() { | ||
productionCert = forge.pki.certificateFromPem(productionCertPem.toString()); | ||
}); | ||
describe("production certificate", function() { | ||
let productionCertPem, productionCert; | ||
before(function() { | ||
productionCertPem = fs.readFileSync("test/credentials/support/certProduction.pem"); | ||
}); | ||
it("sandbox flag", function() { | ||
expect(new APNCertificate(productionCert).environment().sandbox).to.be.false; | ||
}); | ||
beforeEach(function() { | ||
productionCert = forge.pki.certificateFromPem(productionCertPem.toString()); | ||
}); | ||
it("production flag", function() { | ||
expect(new APNCertificate(productionCert).environment().production).to.be.true; | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("sandbox flag", function() { | ||
expect(new APNCertificate(productionCert).environment().sandbox).to.be.false; | ||
}); | ||
it("production flag", function() { | ||
expect(new APNCertificate(productionCert).environment().production).to.be.true; | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -8,33 +8,33 @@ "use strict"; | ||
describe("APNKey", function() { | ||
it("initialises with a node-forge public key", function() { | ||
expect(new APNKey({ n: 12345, e: 65536})).to.be.an.instanceof(APNKey); | ||
}); | ||
it("initialises with a node-forge public key", function() { | ||
expect(new APNKey({ n: 12345, e: 65536})).to.be.an.instanceof(APNKey); | ||
}); | ||
describe("throws", function() { | ||
it("missing modulus", function() { | ||
expect(function() { | ||
new APNKey({ e: 65536 }); | ||
}).to.throw("key is not a valid public key"); | ||
}); | ||
describe("throws", function() { | ||
it("missing modulus", function() { | ||
expect(function() { | ||
new APNKey({ e: 65536 }); | ||
}).to.throw("key is not a valid public key"); | ||
}); | ||
it("missing exponent", function() { | ||
expect(function() { | ||
new APNKey({ n: 12345 }); | ||
}).to.throw("key is not a valid public key"); | ||
}); | ||
it("missing exponent", function() { | ||
expect(function() { | ||
new APNKey({ n: 12345 }); | ||
}).to.throw("key is not a valid public key"); | ||
}); | ||
it("undefined", function() { | ||
expect(function() { | ||
new APNKey(); | ||
}).to.throw("key is not a valid public key"); | ||
}); | ||
}); | ||
it("undefined", function() { | ||
expect(function() { | ||
new APNKey(); | ||
}).to.throw("key is not a valid public key"); | ||
}); | ||
}); | ||
describe("fingerprint", function() { | ||
it("returns the fingerprint of the public key", function() { | ||
let keyPem = fs.readFileSync("test/credentials/support/key.pem"); | ||
let apnKey = new APNKey(forge.pki.decryptRsaPrivateKey(keyPem)); | ||
expect(apnKey.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); | ||
describe("fingerprint", function() { | ||
it("returns the fingerprint of the public key", function() { | ||
let keyPem = fs.readFileSync("test/credentials/support/key.pem"); | ||
let apnKey = new APNKey(forge.pki.decryptRsaPrivateKey(keyPem)); | ||
expect(apnKey.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); |
@@ -6,71 +6,71 @@ "use strict"; | ||
describe("loadCredentials", function() { | ||
let pfx, cert, key, loadCredentials; | ||
before(function () { | ||
pfx = fs.readFileSync("test/support/initializeTest.pfx"); | ||
cert = fs.readFileSync("test/support/initializeTest.crt"); | ||
key = fs.readFileSync("test/support/initializeTest.key"); | ||
let pfx, cert, key, loadCredentials; | ||
before(function () { | ||
pfx = fs.readFileSync("test/support/initializeTest.pfx"); | ||
cert = fs.readFileSync("test/support/initializeTest.crt"); | ||
key = fs.readFileSync("test/support/initializeTest.key"); | ||
const resolve = require("../../../lib/credentials/resolve"); | ||
loadCredentials = require("../../../lib/credentials/certificate/load")({ resolve }); | ||
}); | ||
const resolve = require("../../../lib/credentials/resolve"); | ||
loadCredentials = require("../../../lib/credentials/certificate/load")({ resolve }); | ||
}); | ||
it("should load a pfx file from disk", function () { | ||
return expect(loadCredentials({ pfx: "test/support/initializeTest.pfx" }) | ||
.pfx.toString()).to.equal(pfx.toString()); | ||
}); | ||
it("should load a pfx file from disk", function () { | ||
return expect(loadCredentials({ pfx: "test/support/initializeTest.pfx" }) | ||
.pfx.toString()).to.equal(pfx.toString()); | ||
}); | ||
it("should provide pfx data from memory", function () { | ||
return expect(loadCredentials({ pfx: pfx }).pfx.toString()) | ||
.to.equal(pfx.toString()); | ||
}); | ||
it("should provide pfx data from memory", function () { | ||
return expect(loadCredentials({ pfx: pfx }).pfx.toString()) | ||
.to.equal(pfx.toString()); | ||
}); | ||
it("should provide pfx data explicitly passed in pfxData parameter", function () { | ||
return expect(loadCredentials({ pfxData: pfx }).pfx.toString()) | ||
.to.equal(pfx.toString()); | ||
}); | ||
it("should provide pfx data explicitly passed in pfxData parameter", function () { | ||
return expect(loadCredentials({ pfxData: pfx }).pfx.toString()) | ||
.to.equal(pfx.toString()); | ||
}); | ||
it("should load a certificate from disk", function () { | ||
return expect(loadCredentials({ cert: "test/support/initializeTest.crt", key: null}) | ||
.cert.toString()).to.equal(cert.toString()); | ||
}); | ||
it("should load a certificate from disk", function () { | ||
return expect(loadCredentials({ cert: "test/support/initializeTest.crt", key: null}) | ||
.cert.toString()).to.equal(cert.toString()); | ||
}); | ||
it("should provide a certificate from a Buffer", function () { | ||
return expect(loadCredentials({ cert: cert, key: null}).cert.toString()) | ||
it("should provide a certificate from a Buffer", function () { | ||
return expect(loadCredentials({ cert: cert, key: null}).cert.toString()) | ||
.to.equal(cert.toString()); | ||
}); | ||
}); | ||
it("should provide a certificate from a String", function () { | ||
return expect(loadCredentials({ cert: cert.toString(), key: null}).cert) | ||
.to.equal(cert.toString()); | ||
}); | ||
it("should provide a certificate from a String", function () { | ||
return expect(loadCredentials({ cert: cert.toString(), key: null}).cert) | ||
.to.equal(cert.toString()); | ||
}); | ||
it("should provide certificate data explicitly passed in the certData parameter", function () { | ||
return expect(loadCredentials({ certData: cert, key: null}).cert.toString()) | ||
.to.equal(cert.toString()); | ||
}); | ||
it("should provide certificate data explicitly passed in the certData parameter", function () { | ||
return expect(loadCredentials({ certData: cert, key: null}).cert.toString()) | ||
.to.equal(cert.toString()); | ||
}); | ||
it("should load a key from disk", function () { | ||
return expect(loadCredentials({ cert: null, key: "test/support/initializeTest.key"}) | ||
.key.toString()).to.equal(key.toString()); | ||
}); | ||
it("should load a key from disk", function () { | ||
return expect(loadCredentials({ cert: null, key: "test/support/initializeTest.key"}) | ||
.key.toString()).to.equal(key.toString()); | ||
}); | ||
it("should provide a key from a Buffer", function () { | ||
return expect(loadCredentials({ cert: null, key: key}).key.toString()) | ||
.to.equal(key.toString()); | ||
}); | ||
it("should provide a key from a Buffer", function () { | ||
return expect(loadCredentials({ cert: null, key: key}).key.toString()) | ||
.to.equal(key.toString()); | ||
}); | ||
it("should provide a key from a String", function () { | ||
return expect(loadCredentials({ cert: null, key: key.toString()}).key) | ||
.to.equal(key.toString()); | ||
}); | ||
it("should provide a key from a String", function () { | ||
return expect(loadCredentials({ cert: null, key: key.toString()}).key) | ||
.to.equal(key.toString()); | ||
}); | ||
it("should provide key data explicitly passed in the keyData parameter", function () { | ||
return expect(loadCredentials({ cert: null, keyData: key}).key.toString()) | ||
.to.equal(key.toString()); | ||
}); | ||
it("should provide key data explicitly passed in the keyData parameter", function () { | ||
return expect(loadCredentials({ cert: null, keyData: key}).key.toString()) | ||
.to.equal(key.toString()); | ||
}); | ||
it("should inclue the passphrase in the resolved value", function() { | ||
return expect(loadCredentials({ passphrase: "apntest" }).passphrase) | ||
.to.equal("apntest"); | ||
}); | ||
it("should inclue the passphrase in the resolved value", function() { | ||
return expect(loadCredentials({ passphrase: "apntest" }).passphrase) | ||
.to.equal("apntest"); | ||
}); | ||
}); |
@@ -9,117 +9,117 @@ "use strict"; | ||
describe("parseCredentials", function() { | ||
let fakes, parseCredentials; | ||
const pfxKey = new APNKey({n: 1, e: 1 }); | ||
const pfxCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} }); | ||
let fakes, parseCredentials; | ||
const pemKey = new APNKey({n: 2, e: 1 }); | ||
const pemCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} }); | ||
beforeEach(function() { | ||
fakes = { | ||
parsePkcs12: sinon.stub(), | ||
parsePemKey: sinon.stub(), | ||
parsePemCert: sinon.stub(), | ||
}; | ||
const pfxKey = new APNKey({n: 1, e: 1 }); | ||
const pfxCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} }); | ||
fakes.parsePemKey.withArgs("pemkey").returns(pemKey); | ||
const pemKey = new APNKey({n: 2, e: 1 }); | ||
const pemCert = new APNCertificate({publicKey: {}, validity: {}, subject: {} }); | ||
fakes.parsePemKey.withArgs("pemcert").returns(pemCert); | ||
beforeEach(function() { | ||
fakes = { | ||
parsePkcs12: sinon.stub(), | ||
parsePemKey: sinon.stub(), | ||
parsePemCert: sinon.stub(), | ||
}; | ||
parseCredentials = require("../../../lib/credentials/certificate/parse")(fakes); | ||
}); | ||
fakes.parsePemKey.withArgs("pemkey").returns(pemKey); | ||
describe("with PFX file", function() { | ||
it("returns the parsed key", function() { | ||
fakes.parsePkcs12.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
fakes.parsePemKey.withArgs("pemcert").returns(pemCert); | ||
const parsed = parseCredentials({ pfx: "pfxData" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
parseCredentials = require("../../../lib/credentials/certificate/parse")(fakes); | ||
}); | ||
it("returns the parsed certificates", function() { | ||
fakes.parsePkcs12.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
describe("with PFX file", function() { | ||
it("returns the parsed key", function() { | ||
fakes.parsePkcs12.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
const parsed = parseCredentials({ pfx: "pfxData" }); | ||
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate); | ||
}); | ||
const parsed = parseCredentials({ pfx: "pfxData" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
describe("having passphrase", function() { | ||
beforeEach(function() { | ||
fakes.parsePkcs12.withArgs("encryptedPfxData", "apntest").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
fakes.parsePkcs12.withArgs("encryptedPfxData", sinon.match.any).throws(new Error("unable to read credentials, incorrect passphrase")); | ||
}); | ||
it("returns the parsed key", function() { | ||
const parsed = parseCredentials({ pfx: "encryptedPfxData", passphrase: "apntest" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
it("returns the parsed certificates", function() { | ||
fakes.parsePkcs12.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
it("throws when passphrase is incorrect", function() { | ||
expect(function() { | ||
parseCredentials({ pfx: "encryptedPfxData", passphrase: "incorrectpassphrase" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
const parsed = parseCredentials({ pfx: "pfxData" }); | ||
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate); | ||
}); | ||
it("throws when passphrase is not supplied", function() { | ||
expect(function() { | ||
parseCredentials({ pfx: "encryptedPfxData" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
}); | ||
}); | ||
describe("having passphrase", function() { | ||
beforeEach(function() { | ||
fakes.parsePkcs12.withArgs("encryptedPfxData", "apntest").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
fakes.parsePkcs12.withArgs("encryptedPfxData", sinon.match.any).throws(new Error("unable to read credentials, incorrect passphrase")); | ||
}); | ||
describe("with PEM key", function() { | ||
it("returns the parsed key", function() { | ||
fakes.parsePemKey.withArgs("pemKeyData").returns(pemKey); | ||
it("returns the parsed key", function() { | ||
const parsed = parseCredentials({ pfx: "encryptedPfxData", passphrase: "apntest" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
const parsed = parseCredentials({ key: "pemKeyData" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
it("throws when passphrase is incorrect", function() { | ||
expect(function() { | ||
parseCredentials({ pfx: "encryptedPfxData", passphrase: "incorrectpassphrase" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
describe("having passphrase", function() { | ||
beforeEach(function() { | ||
fakes.parsePemKey.withArgs("encryptedPemKeyData", "apntest").returns(pemKey); | ||
fakes.parsePemKey.withArgs("encryptedPemKeyData", sinon.match.any).throws(new Error("unable to load key, incorrect passphrase")); | ||
}); | ||
it("throws when passphrase is not supplied", function() { | ||
expect(function() { | ||
parseCredentials({ pfx: "encryptedPfxData" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
}); | ||
}); | ||
it("returns the parsed key", function() { | ||
const parsed = parseCredentials({ key: "encryptedPemKeyData", passphrase: "apntest" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
describe("with PEM key", function() { | ||
it("returns the parsed key", function() { | ||
fakes.parsePemKey.withArgs("pemKeyData").returns(pemKey); | ||
it("throws when passphrase is incorrect", function() { | ||
expect(function() { | ||
parseCredentials({ key: "encryptedPemKeyData", passphrase: "incorrectpassphrase" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
const parsed = parseCredentials({ key: "pemKeyData" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
it("throws when passphrase is not supplied", function() { | ||
expect(function() { | ||
parseCredentials({ key: "encryptedPemKeyData" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
}); | ||
}); | ||
describe("having passphrase", function() { | ||
beforeEach(function() { | ||
fakes.parsePemKey.withArgs("encryptedPemKeyData", "apntest").returns(pemKey); | ||
fakes.parsePemKey.withArgs("encryptedPemKeyData", sinon.match.any).throws(new Error("unable to load key, incorrect passphrase")); | ||
}); | ||
describe("with PEM certificate", function() { | ||
it("returns the parsed certificate", function() { | ||
fakes.parsePemCert.withArgs("pemCertData").returns([pemCert]); | ||
it("returns the parsed key", function() { | ||
const parsed = parseCredentials({ key: "encryptedPemKeyData", passphrase: "apntest" }); | ||
expect(parsed.key).to.be.an.instanceof(APNKey); | ||
}); | ||
const parsed = parseCredentials({ cert: "pemCertData" }); | ||
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate); | ||
}); | ||
}); | ||
it("throws when passphrase is incorrect", function() { | ||
expect(function() { | ||
parseCredentials({ key: "encryptedPemKeyData", passphrase: "incorrectpassphrase" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
describe("both PEM and PFX data is supplied", function() { | ||
it("it prefers PFX to PEM", function() { | ||
fakes.parsePkcs12.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
fakes.parsePemKey.withArgs("pemKeyData").returns(pemKey); | ||
fakes.parsePemCert.withArgs("pemCertData").returns([pemCert]); | ||
it("throws when passphrase is not supplied", function() { | ||
expect(function() { | ||
parseCredentials({ key: "encryptedPemKeyData" }); | ||
}).to.throw(/incorrect passphrase/); | ||
}); | ||
}); | ||
}); | ||
const parsed = parseCredentials({ pfx: "pfxData", key: "pemKeyData", cert: "pemCertData"}); | ||
expect(parsed.key).to.equal(pfxKey); | ||
expect(parsed.certificates[0]).to.equal(pfxCert); | ||
}); | ||
}); | ||
}); | ||
describe("with PEM certificate", function() { | ||
it("returns the parsed certificate", function() { | ||
fakes.parsePemCert.withArgs("pemCertData").returns([pemCert]); | ||
const parsed = parseCredentials({ cert: "pemCertData" }); | ||
expect(parsed.certificates[0]).to.be.an.instanceof(APNCertificate); | ||
}); | ||
}); | ||
describe("both PEM and PFX data is supplied", function() { | ||
it("it prefers PFX to PEM", function() { | ||
fakes.parsePkcs12.withArgs("pfxData").returns({ key: pfxKey, certificates: [pfxCert] }); | ||
fakes.parsePemKey.withArgs("pemKeyData").returns(pemKey); | ||
fakes.parsePemCert.withArgs("pemCertData").returns([pemCert]); | ||
const parsed = parseCredentials({ pfx: "pfxData", key: "pemKeyData", cert: "pemCertData"}); | ||
expect(parsed.key).to.equal(pfxKey); | ||
expect(parsed.certificates[0]).to.equal(pfxCert); | ||
}); | ||
}); | ||
}); |
@@ -8,85 +8,85 @@ "use strict"; | ||
describe("parsePemCertificate", function() { | ||
describe("with PEM certificate", function() { | ||
let cert, certProperties; | ||
before(function() { | ||
cert = fs.readFileSync("test/credentials/support/cert.pem"); | ||
}); | ||
describe("with PEM certificate", function() { | ||
let cert, certProperties; | ||
before(function() { | ||
cert = fs.readFileSync("test/credentials/support/cert.pem"); | ||
}); | ||
beforeEach(function() { | ||
certProperties = parsePemCertificate(cert); | ||
}); | ||
beforeEach(function() { | ||
certProperties = parsePemCertificate(cert); | ||
}); | ||
describe("return value", function() { | ||
it("is an array", function() { | ||
expect(certProperties).to.be.an("array"); | ||
}); | ||
describe("return value", function() { | ||
it("is an array", function() { | ||
expect(certProperties).to.be.an("array"); | ||
}); | ||
it("contains one element", function() { | ||
expect(certProperties).to.have.length(1); | ||
}); | ||
it("contains one element", function() { | ||
expect(certProperties).to.have.length(1); | ||
}); | ||
describe("certificate [0]", function() { | ||
it("is an APNCertificate", function() { | ||
expect(certProperties[0]).to.be.an.instanceof(APNCertificate); | ||
}); | ||
describe("certificate [0]", function() { | ||
it("is an APNCertificate", function() { | ||
expect(certProperties[0]).to.be.an.instanceof(APNCertificate); | ||
}); | ||
it("has the correct fingerprint", function() { | ||
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("has the correct fingerprint", function() { | ||
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("with PEM containing multiple certificates", function() { | ||
let cert, certProperties; | ||
before(function() { | ||
cert = fs.readFileSync("test/credentials/support/certIssuerKey.pem"); | ||
}); | ||
describe("with PEM containing multiple certificates", function() { | ||
let cert, certProperties; | ||
before(function() { | ||
cert = fs.readFileSync("test/credentials/support/certIssuerKey.pem"); | ||
}); | ||
beforeEach(function() { | ||
certProperties = parsePemCertificate(cert); | ||
}); | ||
beforeEach(function() { | ||
certProperties = parsePemCertificate(cert); | ||
}); | ||
it("returns the correct number of certificates", function() { | ||
expect(certProperties).to.have.length(2); | ||
}); | ||
it("returns the correct number of certificates", function() { | ||
expect(certProperties).to.have.length(2); | ||
}); | ||
describe("certificate [0]", function() { | ||
it("has the correct fingerprint", function() { | ||
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
describe("certificate [0]", function() { | ||
it("has the correct fingerprint", function() { | ||
expect(certProperties[0].key().fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
describe("certificate [1]", function() { | ||
it("has the correct fingerprint", function() { | ||
expect(certProperties[1].key().fingerprint()).to.equal("ccff221d67cb3335649f9b4fbb311948af76f4b2"); | ||
}); | ||
}); | ||
}); | ||
describe("certificate [1]", function() { | ||
it("has the correct fingerprint", function() { | ||
expect(certProperties[1].key().fingerprint()).to.equal("ccff221d67cb3335649f9b4fbb311948af76f4b2"); | ||
}); | ||
}); | ||
}); | ||
describe("with a PKCS#12 file", function() { | ||
it("throws", function() { | ||
let pfx = fs.readFileSync("test/credentials/support/certIssuerKey.p12"); | ||
expect(function() { | ||
parsePemCertificate(pfx); | ||
}).to.throw("unable to parse certificate, not a valid PEM file"); | ||
}); | ||
}); | ||
describe("with a PKCS#12 file", function() { | ||
it("throws", function() { | ||
let pfx = fs.readFileSync("test/credentials/support/certIssuerKey.p12"); | ||
expect(function() { | ||
parsePemCertificate(pfx); | ||
}).to.throw("unable to parse certificate, not a valid PEM file"); | ||
}); | ||
}); | ||
describe("with a key", function() { | ||
it("returns an empty array", function() { | ||
let key = fs.readFileSync("test/credentials/support/key.pem"); | ||
expect(parsePemCertificate(key)).to.be.empty; | ||
}); | ||
}); | ||
describe("returns null", function() { | ||
it("for null", function() { | ||
expect(parsePemCertificate(null)).to.be.null; | ||
}); | ||
describe("with a key", function() { | ||
it("returns an empty array", function() { | ||
let key = fs.readFileSync("test/credentials/support/key.pem"); | ||
expect(parsePemCertificate(key)).to.be.empty; | ||
}); | ||
}); | ||
it("for undefined", function() { | ||
expect(parsePemCertificate(undefined)).to.be.null; | ||
}); | ||
}); | ||
}); | ||
describe("returns null", function() { | ||
it("for null", function() { | ||
expect(parsePemCertificate(null)).to.be.null; | ||
}); | ||
it("for undefined", function() { | ||
expect(parsePemCertificate(undefined)).to.be.null; | ||
}); | ||
}); | ||
}); |
@@ -8,90 +8,90 @@ "use strict"; | ||
describe("parsePemKey", function() { | ||
describe("returns APNKey", function() { | ||
describe("RSA key", function() { | ||
let key; | ||
beforeEach(function() { | ||
let keyData = fs.readFileSync("test/credentials/support/key.pem"); | ||
key = parsePemKey(keyData); | ||
}); | ||
describe("returns APNKey", function() { | ||
describe("RSA key", function() { | ||
let key; | ||
beforeEach(function() { | ||
let keyData = fs.readFileSync("test/credentials/support/key.pem"); | ||
key = parsePemKey(keyData); | ||
}); | ||
it("correct type", function() { | ||
expect(key).to.be.an.instanceof(APNKey); | ||
}); | ||
it("correct type", function() { | ||
expect(key).to.be.an.instanceof(APNKey); | ||
}); | ||
it("with correct fingerprint", function() { | ||
expect(key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
it("with correct fingerprint", function() { | ||
expect(key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
it("openssl-encrypted RSA key, correct password", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyEncrypted.pem"); | ||
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey); | ||
}); | ||
it("openssl-encrypted RSA key, correct password", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyEncrypted.pem"); | ||
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey); | ||
}); | ||
it("PKCS#8 encrypted key, correct password", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem"); | ||
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey); | ||
}); | ||
it("PKCS#8 encrypted key, correct password", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem"); | ||
expect(parsePemKey(key, "apntest")).to.be.an.instanceof(APNKey); | ||
}); | ||
it("PEM containing certificates and key", function() { | ||
let certAndKey = fs.readFileSync("test/credentials/support/certKey.pem"); | ||
expect(parsePemKey(certAndKey)).to.be.an.instanceof(APNKey); | ||
}); | ||
}); | ||
it("PEM containing certificates and key", function() { | ||
let certAndKey = fs.readFileSync("test/credentials/support/certKey.pem"); | ||
expect(parsePemKey(certAndKey)).to.be.an.instanceof(APNKey); | ||
}); | ||
}); | ||
describe("throws with", function() { | ||
it("PKCS#8 key (unsupported format)", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyPKCS8.pem"); | ||
expect(function() { | ||
parsePemKey(key); | ||
}).to.throw("unable to parse key, unsupported format"); | ||
}); | ||
describe("throws with", function() { | ||
it("PKCS#8 key (unsupported format)", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyPKCS8.pem"); | ||
expect(function() { | ||
parsePemKey(key); | ||
}).to.throw("unable to parse key, unsupported format"); | ||
}); | ||
it("RSA encrypted key, incorrect passphrase", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyEncrypted.pem"); | ||
expect(function() { | ||
parsePemKey(key, "not-the-passphrase"); | ||
}).to.throw("unable to parse key, incorrect passphrase"); | ||
}); | ||
it("RSA encrypted key, incorrect passphrase", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyEncrypted.pem"); | ||
expect(function() { | ||
parsePemKey(key, "not-the-passphrase"); | ||
}).to.throw("unable to parse key, incorrect passphrase"); | ||
}); | ||
it("PKCS#8 encrypted key, incorrect passphrase", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem"); | ||
expect(function() { | ||
parsePemKey(key, "not-the-passphrase"); | ||
}).to.throw("unable to parse key, incorrect passphrase"); | ||
}); | ||
it("PEM certificate", function() { | ||
let cert = fs.readFileSync("test/credentials/support/cert.pem"); | ||
expect(function() { | ||
parsePemKey(cert); | ||
}).to.throw("unable to parse key, no private key found"); | ||
}); | ||
it("PKCS#12 file", function() { | ||
let pkcs12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12"); | ||
expect(function() { | ||
parsePemKey(pkcs12); | ||
}).to.throw("unable to parse key, not a valid PEM file"); | ||
}); | ||
}); | ||
it("PKCS#8 encrypted key, incorrect passphrase", function() { | ||
let key = fs.readFileSync("test/credentials/support/keyPKCS8Encrypted.pem"); | ||
expect(function() { | ||
parsePemKey(key, "not-the-passphrase"); | ||
}).to.throw("unable to parse key, incorrect passphrase"); | ||
}); | ||
describe("multiple keys", function() { | ||
it("throws", function() { | ||
let keys = fs.readFileSync("test/credentials/support/multipleKeys.pem"); | ||
expect(function() { | ||
parsePemKey(keys); | ||
}).to.throw("multiple keys found in PEM file"); | ||
}); | ||
}); | ||
it("PEM certificate", function() { | ||
let cert = fs.readFileSync("test/credentials/support/cert.pem"); | ||
expect(function() { | ||
parsePemKey(cert); | ||
}).to.throw("unable to parse key, no private key found"); | ||
}); | ||
describe("returns null", function() { | ||
it("for null", function() { | ||
expect(parsePemKey()).to.be.null; | ||
}); | ||
it("PKCS#12 file", function() { | ||
let pkcs12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12"); | ||
expect(function() { | ||
parsePemKey(pkcs12); | ||
}).to.throw("unable to parse key, not a valid PEM file"); | ||
}); | ||
}); | ||
it("for undefined", function() { | ||
expect(parsePemKey()).to.be.null; | ||
}); | ||
}); | ||
}); | ||
describe("multiple keys", function() { | ||
it("throws", function() { | ||
let keys = fs.readFileSync("test/credentials/support/multipleKeys.pem"); | ||
expect(function() { | ||
parsePemKey(keys); | ||
}).to.throw("multiple keys found in PEM file"); | ||
}); | ||
}); | ||
describe("returns null", function() { | ||
it("for null", function() { | ||
expect(parsePemKey()).to.be.null; | ||
}); | ||
it("for undefined", function() { | ||
expect(parsePemKey()).to.be.null; | ||
}); | ||
}); | ||
}); |
@@ -11,113 +11,113 @@ "use strict"; | ||
describe("parsePkcs12", function() { | ||
describe("with PKCS#12 data", function() { | ||
var p12, properties; | ||
describe("return value", function() { | ||
var credentials; | ||
before(function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12"); | ||
credentials = parsePkcs12(p12); | ||
}); | ||
describe("with PKCS#12 data", function() { | ||
var p12, properties; | ||
describe("return value", function() { | ||
var credentials; | ||
before(function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKey.p12"); | ||
credentials = parsePkcs12(p12); | ||
}); | ||
it("is an object", function() { | ||
expect(credentials).to.be.an("object"); | ||
}); | ||
it("is an object", function() { | ||
expect(credentials).to.be.an("object"); | ||
}); | ||
it("contains a private key", function() { | ||
expect(credentials).to.include.keys("key"); | ||
}); | ||
it("contains a private key", function() { | ||
expect(credentials).to.include.keys("key"); | ||
}); | ||
describe("private key", function() { | ||
it("is an instance of APNKey", function() { | ||
expect(credentials.key).to.be.an.instanceof(APNKey); | ||
}); | ||
describe("private key", function() { | ||
it("is an instance of APNKey", function() { | ||
expect(credentials.key).to.be.an.instanceof(APNKey); | ||
}); | ||
it("has the correct fingerprint", function() { | ||
expect(credentials.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
it("has the correct fingerprint", function() { | ||
expect(credentials.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
it("contains a certificate chain", function() { | ||
expect(credentials).to.include.keys("certificates"); | ||
}); | ||
it("contains a certificate chain", function() { | ||
expect(credentials).to.include.keys("certificates"); | ||
}); | ||
describe("certificate chain", function() { | ||
it("is an array", function() { | ||
expect(credentials.certificates).to.be.an("array"); | ||
}); | ||
describe("certificate chain", function() { | ||
it("is an array", function() { | ||
expect(credentials.certificates).to.be.an("array"); | ||
}); | ||
it("contains the correct number of certificates", function() { | ||
expect(credentials.certificates.length).to.equal(2); | ||
}); | ||
it("contains the correct number of certificates", function() { | ||
expect(credentials.certificates.length).to.equal(2); | ||
}); | ||
it("contains APNCertificate objects", function() { | ||
var certificates = credentials.certificates; | ||
certificates.forEach(function(certificate) { | ||
expect(certificate).to.be.an.instanceof(APNCertificate); | ||
}); | ||
}); | ||
it("contains APNCertificate objects", function() { | ||
var certificates = credentials.certificates; | ||
certificates.forEach(function(certificate) { | ||
expect(certificate).to.be.an.instanceof(APNCertificate); | ||
}); | ||
}); | ||
it("contains certificates with the correct fingerprints", function() { | ||
var fingerprints = ["2d594c9861227dd22ba5ae37cc9354e9117a804d", "ccff221d67cb3335649f9b4fbb311948af76f4b2"]; | ||
var certificates = credentials.certificates; | ||
certificates.forEach(function(certificate, index) { | ||
expect(certificate.key().fingerprint()).to.equal(fingerprints[index]); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("contains certificates with the correct fingerprints", function() { | ||
var fingerprints = ["2d594c9861227dd22ba5ae37cc9354e9117a804d", "ccff221d67cb3335649f9b4fbb311948af76f4b2"]; | ||
var certificates = credentials.certificates; | ||
certificates.forEach(function(certificate, index) { | ||
expect(certificate.key().fingerprint()).to.equal(fingerprints[index]); | ||
}); | ||
}); | ||
}); | ||
}); | ||
// OpenSSL exports keys having no passphrase as a C string with a \0 byte appended | ||
describe("having empty passphrase (OpenSSL-CLI-generated file)", function() { | ||
describe("return value", function() { | ||
it("has the correct key", function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyOpenSSL.p12"); | ||
properties = parsePkcs12(p12); | ||
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); | ||
// OpenSSL exports keys having no passphrase as a C string with a \0 byte appended | ||
describe("having empty passphrase (OpenSSL-CLI-generated file)", function() { | ||
describe("return value", function() { | ||
it("has the correct key", function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyOpenSSL.p12"); | ||
properties = parsePkcs12(p12); | ||
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); | ||
describe("with correct passphrase", function() { | ||
describe("return value", function() { | ||
it("has the correct key", function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12"); | ||
properties = parsePkcs12(p12, "apntest"); | ||
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); | ||
describe("with incorrect passphrase", function() { | ||
it("throws", function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12"); | ||
expect(function() { | ||
parsePkcs12(p12, "notthepassphrase"); | ||
}).to.throw("unable to parse credentials, incorrect passphrase"); | ||
}); | ||
}); | ||
describe("with correct passphrase", function() { | ||
describe("return value", function() { | ||
it("has the correct key", function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12"); | ||
properties = parsePkcs12(p12, "apntest"); | ||
expect(properties.key.fingerprint()).to.equal("2d594c9861227dd22ba5ae37cc9354e9117a804d"); | ||
}); | ||
}); | ||
}); | ||
describe("with incorrect passphrase", function() { | ||
it("throws", function() { | ||
p12 = fs.readFileSync("test/credentials/support/certIssuerKeyPassphrase.p12"); | ||
expect(function() { | ||
parsePkcs12(p12, "notthepassphrase"); | ||
}).to.throw("unable to parse credentials, incorrect passphrase"); | ||
}); | ||
}); | ||
// Unclear whether multiple keys in one PKCS#12 file can be distinguished | ||
// at present if there's more than one just throw a warning. Should also | ||
// do the same thing in apnKeyFromPem | ||
describe("multiple keys", function() { | ||
it("throws", function() { | ||
p12 = fs.readFileSync("test/credentials/support/multipleKeys.p12"); | ||
expect(function() { | ||
parsePkcs12(p12); | ||
}).to.throw("multiple keys found in PFX/P12 file"); | ||
}); | ||
}); | ||
}); | ||
// Unclear whether multiple keys in one PKCS#12 file can be distinguished | ||
// at present if there's more than one just throw a warning. Should also | ||
// do the same thing in apnKeyFromPem | ||
describe("multiple keys", function() { | ||
it("throws", function() { | ||
p12 = fs.readFileSync("test/credentials/support/multipleKeys.p12"); | ||
expect(function() { | ||
parsePkcs12(p12); | ||
}).to.throw("multiple keys found in PFX/P12 file"); | ||
}); | ||
}); | ||
}); | ||
describe("PEM file", function() { | ||
it("throws", function() { | ||
var pem = fs.readFileSync("test/credentials/support/certKey.pem"); | ||
expect(function() { | ||
parsePkcs12(pem); | ||
}).to.throw("unable to parse credentials, not a PFX/P12 file"); | ||
}); | ||
}); | ||
describe("PEM file", function() { | ||
it("throws", function() { | ||
var pem = fs.readFileSync("test/credentials/support/certKey.pem"); | ||
expect(function() { | ||
parsePkcs12(pem); | ||
}).to.throw("unable to parse credentials, not a PFX/P12 file"); | ||
}); | ||
}); | ||
it("returns undefined for undefined", function() { | ||
expect(parsePkcs12()).to.be.undefined; | ||
}); | ||
}); | ||
it("returns undefined for undefined", function() { | ||
expect(parsePkcs12()).to.be.undefined; | ||
}); | ||
}); |
@@ -9,122 +9,122 @@ "use strict"; | ||
describe("validateCredentials", function() { | ||
let credentials; | ||
beforeEach(function() { | ||
credentials = fakeCredentials(); | ||
}); | ||
let credentials; | ||
beforeEach(function() { | ||
credentials = fakeCredentials(); | ||
}); | ||
describe("with valid credentials", function() { | ||
it("returns", function() { | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.not.throw(); | ||
}); | ||
}); | ||
describe("with valid credentials", function() { | ||
it("returns", function() { | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.not.throw(); | ||
}); | ||
}); | ||
describe("with mismatched key and certificate", function() { | ||
it("throws", function() { | ||
sinon.stub(credentials.certificates[0]._key, "fingerprint").returns("fingerprint2"); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw(/certificate and key do not match/); | ||
}); | ||
}); | ||
describe("with mismatched key and certificate", function() { | ||
it("throws", function() { | ||
sinon.stub(credentials.certificates[0]._key, "fingerprint").returns("fingerprint2"); | ||
describe("with expired certificate", function() { | ||
it("throws", function() { | ||
sinon.stub(credentials.certificates[0], "validity") | ||
.returns({ | ||
notBefore: new Date(Date.now() - 100000), | ||
notAfter: new Date(Date.now() - 10000) | ||
}); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw(/certificate and key do not match/); | ||
}); | ||
}); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw(/certificate has expired/); | ||
}); | ||
}); | ||
describe("with expired certificate", function() { | ||
it("throws", function() { | ||
sinon.stub(credentials.certificates[0], "validity") | ||
.returns({ | ||
notBefore: new Date(Date.now() - 100000), | ||
notAfter: new Date(Date.now() - 10000) | ||
}); | ||
describe("with incorrect environment", function() { | ||
it("throws with sandbox cert in production", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: false, | ||
sandbox: true | ||
}); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw(/certificate has expired/); | ||
}); | ||
}); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw("certificate does not support configured environment, production: true"); | ||
}); | ||
describe("with incorrect environment", function() { | ||
it("throws with sandbox cert in production", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: false, | ||
sandbox: true | ||
}); | ||
it("throws with production cert in sandbox", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: true, | ||
sandbox: false | ||
}); | ||
credentials.production = false; | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw("certificate does not support configured environment, production: true"); | ||
}); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw("certificate does not support configured environment, production: false"); | ||
}); | ||
}); | ||
it("throws with production cert in sandbox", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: true, | ||
sandbox: false | ||
}); | ||
credentials.production = false; | ||
describe("with missing production flag", function() { | ||
it("does not throw", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: true, | ||
sandbox: false | ||
}); | ||
credentials.production = undefined; | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.throw("certificate does not support configured environment, production: false"); | ||
}); | ||
}); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.not.throw(); | ||
}); | ||
}); | ||
describe("with missing production flag", function() { | ||
it("does not throw", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: true, | ||
sandbox: false | ||
}); | ||
credentials.production = undefined; | ||
describe("with certificate supporting both environments", function() { | ||
it("does not throw", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: true, | ||
sandbox: true | ||
}); | ||
credentials.production = false; | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.not.throw(); | ||
}); | ||
}); | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.not.throw(); | ||
}); | ||
}); | ||
describe("with certificate supporting both environments", function() { | ||
it("does not throw", function() { | ||
sinon.stub(credentials.certificates[0], "environment") | ||
.returns({ | ||
production: true, | ||
sandbox: true | ||
}); | ||
credentials.production = false; | ||
expect(function() { | ||
validateCredentials(credentials); | ||
}).to.not.throw(); | ||
}); | ||
}); | ||
}); | ||
fakeCredentials = function() { | ||
return { | ||
key: { | ||
_fingerprint: "fingerprint1", | ||
fingerprint: function() { return this._fingerprint; }, | ||
}, | ||
certificates: [{ | ||
_key: { | ||
_fingerprint: "fingerprint1", | ||
fingerprint: function() { return this._fingerprint; }, | ||
}, | ||
_validity: { | ||
notBefore: new Date(Date.now() - 100000), | ||
notAfter: new Date(Date.now() + 100000) | ||
}, | ||
key: function() { return this._key; }, | ||
validity: function() { | ||
return this._validity; | ||
}, | ||
environment: function() { | ||
return { production: true, sandbox: false }; | ||
} | ||
}], | ||
production: true | ||
}; | ||
return { | ||
key: { | ||
_fingerprint: "fingerprint1", | ||
fingerprint: function() { return this._fingerprint; }, | ||
}, | ||
certificates: [{ | ||
_key: { | ||
_fingerprint: "fingerprint1", | ||
fingerprint: function() { return this._fingerprint; }, | ||
}, | ||
_validity: { | ||
notBefore: new Date(Date.now() - 100000), | ||
notAfter: new Date(Date.now() + 100000) | ||
}, | ||
key: function() { return this._key; }, | ||
validity: function() { | ||
return this._validity; | ||
}, | ||
environment: function() { | ||
return { production: true, sandbox: false }; | ||
} | ||
}], | ||
production: true | ||
}; | ||
}; |
@@ -7,42 +7,42 @@ "use strict"; | ||
describe("resolve", function() { | ||
var pfx, cert, key; | ||
before(function () { | ||
pfx = fs.readFileSync("test/support/initializeTest.pfx"); | ||
cert = fs.readFileSync("test/support/initializeTest.crt"); | ||
key = fs.readFileSync("test/support/initializeTest.key"); | ||
}); | ||
var pfx, cert, key; | ||
before(function () { | ||
pfx = fs.readFileSync("test/support/initializeTest.pfx"); | ||
cert = fs.readFileSync("test/support/initializeTest.crt"); | ||
key = fs.readFileSync("test/support/initializeTest.key"); | ||
}); | ||
it("returns PEM string as supplied", function() { | ||
expect(resolve(cert.toString())) | ||
.to.be.a("string") | ||
.and.to.equal(cert.toString()); | ||
}); | ||
it("returns PEM string as supplied", function() { | ||
expect(resolve(cert.toString())) | ||
.to.be.a("string") | ||
.and.to.equal(cert.toString()); | ||
}); | ||
it("returns Buffer as supplied", function() { | ||
expect(resolve(pfx)) | ||
.to.satisfy(Buffer.isBuffer) | ||
.and.to.equal(pfx); | ||
}); | ||
it("returns Buffer as supplied", function() { | ||
expect(resolve(pfx)) | ||
.to.satisfy(Buffer.isBuffer) | ||
.and.to.equal(pfx); | ||
}); | ||
describe("with file path", function() { | ||
it("returns a Buffer for valid path", function() { | ||
return expect(resolve("test/support/initializeTest.key")) | ||
.to.satisfy(Buffer.isBuffer); | ||
}); | ||
it("returns contents for value path", function () { | ||
return expect(resolve("test/support/initializeTest.key") | ||
.toString()).to.equal(key.toString()); | ||
}); | ||
describe("with file path", function() { | ||
it("returns a Buffer for valid path", function() { | ||
return expect(resolve("test/support/initializeTest.key")) | ||
.to.satisfy(Buffer.isBuffer); | ||
}); | ||
it("throws for invalid path", function() { | ||
return expect(() => { resolve("test/support/fail/initializeTest.key") }) | ||
.to.throw; | ||
}); | ||
}); | ||
it("returns contents for value path", function () { | ||
return expect(resolve("test/support/initializeTest.key") | ||
.toString()).to.equal(key.toString()); | ||
}); | ||
it("returns null/undefined as supplied", function() { | ||
expect(resolve(null)).to.be.null; | ||
expect(resolve()).to.be.undefined; | ||
}); | ||
it("throws for invalid path", function() { | ||
return expect(() => { resolve("test/support/fail/initializeTest.key") }) | ||
.to.throw; | ||
}); | ||
}); | ||
it("returns null/undefined as supplied", function() { | ||
expect(resolve(null)).to.be.null; | ||
expect(resolve()).to.be.undefined; | ||
}); | ||
}); |
@@ -12,2 +12,3 @@ "use strict"; | ||
resolve: sinon.stub(), | ||
decode: sinon.stub(), | ||
}; | ||
@@ -111,2 +112,29 @@ | ||
}); | ||
context("`isExpired` called with expired token", function () { | ||
let token; | ||
beforeEach(function () { | ||
fakes.resolve.withArgs("key.pem").returns("keyData"); | ||
fakes.decode.onCall(0).returns({iat:Math.floor(Date.now() / 1000)-1}); | ||
token = prepareToken(testOptions); | ||
}); | ||
it("token is not expired", function () { | ||
expect(token.isExpired(0)).to.equal(true); | ||
}); | ||
}); | ||
context("`isExpired` called with valid token", function () { | ||
let token; | ||
beforeEach(function () { | ||
fakes.resolve.withArgs("key.pem").returns("keyData"); | ||
fakes.decode.onCall(0).returns({iat:Math.floor(Date.now() / 1000)}); | ||
token = prepareToken(testOptions); | ||
}); | ||
it("token is not expired", function () { | ||
expect(token.isExpired(5)).to.equal(false); | ||
}); | ||
}); | ||
}); | ||
@@ -113,0 +141,0 @@ }); |
@@ -676,2 +676,28 @@ "use strict"; | ||
describe("thread-id", function() { | ||
it("defaults to undefined", function() { | ||
expect(compiledOutput()).to.not.have.deep.property("aps.thread\-id"); | ||
}); | ||
it("can be set to a string", function() { | ||
note.threadId = "the-thread-id"; | ||
expect(compiledOutput()).to.have.deep.property("aps.thread\-id", "the-thread-id"); | ||
}); | ||
it("can be set to undefined", function() { | ||
note.threadId = "the-thread-id"; | ||
note.threadId = undefined; | ||
expect(compiledOutput()).to.not.have.deep.property("aps.thread\-id"); | ||
}); | ||
describe("setThreadId", function () { | ||
it("is chainable", function () { | ||
expect(note.setThreadId("the-thread-id")).to.equal(note); | ||
expect(compiledOutput()).to.have.deep.property("aps.thread\-id", "the-thread-id"); | ||
}); | ||
}); | ||
}); | ||
context("when no aps properties are set", function() { | ||
@@ -678,0 +704,0 @@ it("is not present", function() { |
@@ -146,10 +146,2 @@ "use strict"; | ||
}); | ||
context("threadId is set", function () { | ||
it("contains the thread-id header", function () { | ||
note.threadId = "io.apn.thread.1"; | ||
expect(note.headers()).to.have.property("thread-id", "io.apn.thread.1"); | ||
}); | ||
}); | ||
}); | ||
@@ -156,0 +148,0 @@ |
@@ -21,3 +21,3 @@ "use strict"; | ||
tls: { | ||
connect: sinon.stub(), | ||
connect: sinon.stub() | ||
}, | ||
@@ -114,8 +114,8 @@ protocol: { | ||
expect(fakes.tls.connect).to.be.calledWith(sinon.match({ | ||
host: "localtest", | ||
port: 443, | ||
servername: "localtest" | ||
})); | ||
}); | ||
expect(fakes.tls.connect).to.be.calledWith(sinon.match({ | ||
host: "localtest", | ||
port: 443, | ||
servername: "localtest" | ||
})); | ||
}); | ||
}); | ||
@@ -730,2 +730,34 @@ }); | ||
}); | ||
describe("ping", function () { | ||
let endpoint; | ||
beforeEach(function () { | ||
streams.connection.ping = (a) => {}; | ||
sinon.stub(streams.connection, "ping", (callback) => { | ||
callback(); | ||
}); | ||
this.clock = sinon.useFakeTimers(); | ||
endpoint = new Endpoint({ | ||
heartBeat: 1 | ||
}); | ||
}); | ||
afterEach(function () { | ||
this.clock.restore(); | ||
}); | ||
it("should update last success pinged time", function () { | ||
this.clock.tick(10); | ||
expect(endpoint._lastSuccessPingedTime).to.not.equal(null); | ||
}); | ||
it("should throw error when pinged failed", function () { | ||
endpoint._lastSuccessPingedTime = Date.now() - endpoint._pingedThreshold; | ||
try { | ||
this.clock.tick(10); | ||
} catch (error) { | ||
var e = error; | ||
} | ||
expect(endpoint.lastError).to.have.string("Not receiving Ping response after"); | ||
}); | ||
}); | ||
}); |
@@ -11,2 +11,2 @@ "use strict"; | ||
global.expect = chai.expect; | ||
global.expect = chai.expect; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
339468
4990
Updateddebug@^2.6.0
Updatedjsonwebtoken@^7.2.1
Updatednode-forge@^0.6.48
Updatedverror@^1.9.0