@knocklabs/node 0.4.14 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,13 +16,16 @@ npm install @knocklabs/node
16
16
 
17
17
  To use the library you must provide a secret API key, provided in the Knock dashboard.
18
18
 
19
- You can set it as an environment variable:
19
+ If you are using [enhanced security mode](https://docs.knock.app/client-integration/authenticating-users) you will also need to provide your signing key.
20
+
21
+ You can set both as environment variables:
20
22
 
21
23
  ```bash
22
24
  KNOCK_API_KEY="sk_12345"
25
+ KNOCK_SIGNING_KEY="S25vY2sga25vY2sh..."
23
26
  ```
24
27
 
25
- Or, you can set it before your application starts:
28
+ You can also pass the Knock API key in the constructor. The signing key is passed separately to the `signUserToken` method (see below):
26
29
 
27
30
  ```javascript
28
31
  const { Knock } = require("@knocklabs/node");
@@ -135,27 +138,24 @@ await knockClient.users.getChannelData("jhammond", KNOCK_APNS_CHANNEL_ID);
135
138
 
136
139
  ### Signing JWTs
137
140
 
138
- You can use the `jsonwebtoken` package to [sign JWTs easily](https://www.npmjs.com/package/jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback).
139
- You will need to generate an environment specific signing key, which you can find in the Knock dashboard.
141
+ When using [enhanced security mode](https://docs.knock.app/client-integration/authenticating-users) (recommended in production), you will need to sign JWTs server-side to authenticate your users.
140
142
 
141
- If you're using a signing token you will need to pass this to your client to perform authentication.
142
- You can read more about [client-side authentication here](https://docs.knock.app/client-integration/authenticating-users).
143
+ You will need to generate an environment specific signing key in the Knock dashboard under "API Keys", and then enable enhanced security mode for your environment.
143
144
 
144
145
  ```javascript
145
- const jwt = require("jsonwebtoken");
146
-
147
- const currentTime = Math.floor(Date.now() / 1000);
148
-
149
- const token = jwt.sign(
150
- {
151
- // The user id to sign this key for
152
- sub: "jhammond",
153
- // When the token was issued
154
- iat: currentTime,
155
- // When the token expires (1 hour)
156
- exp: currentTime + 60 * 60,
157
- },
158
- process.env.KNOCK_SIGNING_KEY,
159
- { algorithm: "RS256" },
160
- );
146
+ const { Knock } = require("@knocklabs/node");
147
+
148
+ // When signing user tokens, you do not need to instantiate a Knock client.
149
+
150
+ // jhammond is the user id for which to sign this token
151
+ const token = Knock.signUserToken("jhammond", {
152
+ // The signing key from the Knock Dashboard in base-64 or PEM-encoded format.
153
+ // If not provided, the key will be read from the KNOCK_SIGNING_KEY environment variable.
154
+ signingKey: "S25vY2sga25vY2sh...",
155
+ // Optional: How long the token should be valid for, in seconds (default 1 hour)
156
+ // For long-lived connections, you will need to refresh the token before it expires.
157
+ expiresIn: 60 * 60,
158
+ });
159
+
160
+ // This token can now be safely passed to your client e.g. in a cookie or API response.
161
161
  ```
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knocklabs/node",
3
- "version": "0.4.14",
3
+ "version": "0.4.16",
4
4
  "description": "Library for interacting with the Knock API",
5
5
  "homepage": "https://github.com/knocklabs/knock-node",
6
6
  "author": "@knocklabs",
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/jest": "26.0.23",
35
+ "@types/jsonwebtoken": "^9.0.1",
35
36
  "@types/node": "^15.0.1",
36
37
  "@types/pluralize": "0.0.29",
37
38
  "axios-mock-adapter": "1.19.0",
@@ -43,6 +44,7 @@
43
44
  "typescript": "4.2.4"
44
45
  },
45
46
  "dependencies": {
46
- "axios": "^0.21.1"
47
+ "axios": "^0.21.1",
48
+ "jsonwebtoken": "^9.0.0"
47
49
  }
48
50
  }
@@ -4,6 +4,11 @@ export declare class NoApiKeyProvidedException extends Error {
4
4
  readonly name: string;
5
5
  readonly message: string;
6
6
  }
7
+ export declare class NoSigningKeyProvidedException extends Error {
8
+ readonly status: number;
9
+ readonly name: string;
10
+ readonly message: string;
11
+ }
7
12
  export declare class GenericServerException implements HttpException {
8
13
  readonly status: number;
9
14
  readonly requestID: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BadRequestException = exports.UnprocessableEntityException = exports.NotFoundException = exports.UnauthorizedException = exports.GenericServerException = exports.NoApiKeyProvidedException = void 0;
3
+ exports.BadRequestException = exports.UnprocessableEntityException = exports.NotFoundException = exports.UnauthorizedException = exports.GenericServerException = exports.NoSigningKeyProvidedException = exports.NoApiKeyProvidedException = void 0;
4
4
  class NoApiKeyProvidedException extends Error {
5
5
  constructor() {
6
6
  super(...arguments);
@@ -11,6 +11,17 @@ class NoApiKeyProvidedException extends Error {
11
11
  }
12
12
  }
13
13
  exports.NoApiKeyProvidedException = NoApiKeyProvidedException;
14
+ class NoSigningKeyProvidedException extends Error {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.status = 500;
18
+ this.name = "NoSigningKeyProvidedException";
19
+ this.message = `Missing or invalid signing key key. Pass it as an option to Knock.signUserToken(userId, {signingKey: "S25vY2sga25vY2sh..."}) ` +
20
+ `or define it in the KNOCK_SIGNING_KEY environment variable. The signing key can either be a Base-64 encoded string ` +
21
+ `or a PEM-encoded certificate. For more information, see https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security-enabled`;
22
+ }
23
+ }
24
+ exports.NoSigningKeyProvidedException = NoSigningKeyProvidedException;
14
25
  class GenericServerException {
15
26
  constructor(status, message, requestID) {
16
27
  this.status = status;
@@ -46,4 +46,12 @@ export interface Condition {
46
46
  variable: string;
47
47
  operator: string;
48
48
  }
49
+ export interface SignUserTokenOptions {
50
+ /**
51
+ * The signing key to use to sign the token. If not provided, the KNOCK_SIGNING_KEY environment variable will be used.
52
+ */
53
+ signingKey?: string;
54
+ /** The expiration time of the token in seconds. Defaults to 1 hour. */
55
+ expiresInSeconds?: number;
56
+ }
49
57
  export {};
@@ -1,5 +1,5 @@
1
1
  import { AxiosResponse } from "axios";
2
- import { KnockOptions, PostAndPutOptions } from "./common/interfaces";
2
+ import { KnockOptions, PostAndPutOptions, SignUserTokenOptions } from "./common/interfaces";
3
3
  import { Users } from "./resources/users";
4
4
  import { Preferences } from "./resources/preferences";
5
5
  import { Workflows } from "./resources/workflows";
@@ -22,6 +22,15 @@ declare class Knock {
22
22
  readonly tenants: Tenants;
23
23
  constructor(key?: string | undefined, options?: KnockOptions);
24
24
  notify(workflowKey: string, properties: TriggerWorkflowProperties): Promise<import("./resources/workflows/interfaces").WorkflowRun>;
25
+ /**
26
+ * Generate JWT for authenticating client-side requests (e.g. in-app feeds)
27
+ * For more information, visit https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security-enabled
28
+ *
29
+ * @param userId {string} The ID of the user that needs a token, e.g. the user viewing an in-app feed.
30
+ * @param options Optionally specify the signing key to use (in PEM or base-64 encoded format), and how long the token should be valid for in seconds
31
+ * @returns {string} A JWT token that can be used to authenticate requests to the Knock API (e.g. by passing into the <KnockFeedProvider /> component)
32
+ */
33
+ static signUserToken(userId: string, options: SignUserTokenOptions): string;
25
34
  post(path: string, entity: any, options?: PostAndPutOptions): Promise<AxiosResponse>;
26
35
  put(path: string, entity: any, options?: PostAndPutOptions): Promise<AxiosResponse>;
27
36
  delete(path: string, entity?: any): Promise<AxiosResponse>;
package/dist/src/knock.js CHANGED
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.Knock = void 0;
16
16
  const axios_1 = __importDefault(require("axios"));
17
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
17
18
  const package_json_1 = require("../package.json");
18
19
  const exceptions_1 = require("./common/exceptions");
19
20
  const users_1 = require("./resources/users");
@@ -57,6 +58,29 @@ class Knock {
57
58
  return this.workflows.trigger(workflowKey, properties);
58
59
  });
59
60
  }
61
+ /**
62
+ * Generate JWT for authenticating client-side requests (e.g. in-app feeds)
63
+ * For more information, visit https://docs.knock.app/in-app-ui/security-and-authentication#authentication-with-enhanced-security-enabled
64
+ *
65
+ * @param userId {string} The ID of the user that needs a token, e.g. the user viewing an in-app feed.
66
+ * @param options Optionally specify the signing key to use (in PEM or base-64 encoded format), and how long the token should be valid for in seconds
67
+ * @returns {string} A JWT token that can be used to authenticate requests to the Knock API (e.g. by passing into the <KnockFeedProvider /> component)
68
+ */
69
+ static signUserToken(userId, options) {
70
+ var _a;
71
+ const signingKey = prepareSigningKey(options.signingKey);
72
+ // JWT NumericDates specified in seconds:
73
+ const currentTime = Math.floor(Date.now() / 1000);
74
+ // Default to 1 hour from now
75
+ const expireInSeconds = (_a = options.expiresInSeconds) !== null && _a !== void 0 ? _a : 60 * 60;
76
+ return jsonwebtoken_1.default.sign({
77
+ sub: userId,
78
+ iat: currentTime,
79
+ exp: currentTime + expireInSeconds,
80
+ }, signingKey, {
81
+ algorithm: "RS256",
82
+ });
83
+ }
60
84
  post(path, entity, options = {}) {
61
85
  return __awaiter(this, void 0, void 0, function* () {
62
86
  try {
@@ -144,3 +168,14 @@ class Knock {
144
168
  }
145
169
  }
146
170
  exports.Knock = Knock;
171
+ function prepareSigningKey(key) {
172
+ const maybeSigningKey = key !== null && key !== void 0 ? key : process.env.KNOCK_SIGNING_KEY;
173
+ if (!maybeSigningKey)
174
+ throw new exceptions_1.NoSigningKeyProvidedException();
175
+ if (maybeSigningKey.startsWith("-----BEGIN"))
176
+ return maybeSigningKey;
177
+ // LS0tLS1CRUdJTi is the base64 encoded version of "-----BEGIN"
178
+ if (maybeSigningKey.startsWith("LS0tLS1CRUdJTi"))
179
+ return Buffer.from(maybeSigningKey, "base64").toString("utf-8");
180
+ throw new exceptions_1.NoSigningKeyProvidedException();
181
+ }
@@ -1,6 +1,6 @@
1
1
  import { ChannelData, CommonMetadata, SetChannelDataProperties, PaginatedResponse, ChannelType } from "../../common/interfaces";
2
2
  import { Knock } from "../../knock";
3
- import { BulkSetObjectOption, ListObjectOptions, Object, SetObjectProperties } from "./interfaces";
3
+ import { AddObjectSubscriptionProperties, BulkSetObjectOption, DeleteObjectSubscriptionProperties, ListObjectOptions, ListObjectSubscriptionsOptions, Object, ObjectSubscription, SetObjectProperties } from "./interfaces";
4
4
  import { BulkOperation } from "../bulk_operations/interfaces";
5
5
  import { ListMessagesOptions, Message } from "../messages/interfaces";
6
6
  import { ChannelTypePreferences, PreferenceOptions, PreferenceSet, SetPreferencesProperties, WorkflowPreferences, WorkflowPreferenceSetting } from "../preferences/interfaces";
@@ -30,4 +30,7 @@ export declare class Objects {
30
30
  setWorkflowPreferences(collection: string, objectId: string, workflowKey: string, setting: WorkflowPreferenceSetting, options?: PreferenceOptions): Promise<PreferenceSet>;
31
31
  setCategoriesPreferences(collection: string, objectId: string, categoryPreferences: WorkflowPreferences, options?: PreferenceOptions): Promise<PreferenceSet>;
32
32
  setCategoryPreferences(collection: string, objectId: string, categoryKey: string, setting: WorkflowPreferenceSetting, options?: PreferenceOptions): Promise<PreferenceSet>;
33
+ listSubscriptions(collection: string, objectId: string, options?: ListObjectSubscriptionsOptions): Promise<PaginatedResponse<ObjectSubscription>>;
34
+ addSubscriptions(collection: string, objectId: string, properties?: AddObjectSubscriptionProperties): Promise<ObjectSubscription[]>;
35
+ deleteSubscriptions(collection: string, objectId: string, properties?: DeleteObjectSubscriptionProperties): Promise<ObjectSubscription[]>;
33
36
  }
@@ -160,5 +160,26 @@ class Objects {
160
160
  return data;
161
161
  });
162
162
  }
163
+ //
164
+ // Subscriptions
165
+ //
166
+ listSubscriptions(collection, objectId, options = {}) {
167
+ return __awaiter(this, void 0, void 0, function* () {
168
+ const { data } = yield this.knock.get(`/v1/objects/${collection}/${objectId}/subscriptions`, options);
169
+ return data;
170
+ });
171
+ }
172
+ addSubscriptions(collection, objectId, properties = { recipients: [] }) {
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ const { data } = yield this.knock.post(`/v1/objects/${collection}/${objectId}/subscriptions`, properties);
175
+ return data;
176
+ });
177
+ }
178
+ deleteSubscriptions(collection, objectId, properties = { recipients: [] }) {
179
+ return __awaiter(this, void 0, void 0, function* () {
180
+ const { data } = yield this.knock.delete(`/v1/objects/${collection}/${objectId}/subscriptions`, properties);
181
+ return data;
182
+ });
183
+ }
163
184
  }
164
185
  exports.Objects = Objects;
@@ -1,4 +1,6 @@
1
1
  import { CommonMetadata, PaginationOptions } from "../../common/interfaces";
2
+ import { User } from "../users/interfaces";
3
+ import { Recipient, RecipientWithUpsert } from "../workflows/interfaces";
2
4
  export interface ObjectRef {
3
5
  collection: string;
4
6
  id: string;
@@ -23,3 +25,18 @@ export interface ListObjectOptions extends PaginationOptions {
23
25
  object_id?: string;
24
26
  name?: string;
25
27
  }
28
+ export interface ListObjectSubscriptionsOptions extends PaginationOptions {
29
+ }
30
+ export interface ObjectSubscription<T = CommonMetadata> {
31
+ recipient: User | Object;
32
+ properties: T;
33
+ inserted_at: string;
34
+ updated_at: string;
35
+ }
36
+ export interface AddObjectSubscriptionProperties {
37
+ recipients: (Recipient | RecipientWithUpsert)[];
38
+ properties?: CommonMetadata;
39
+ }
40
+ export interface DeleteObjectSubscriptionProperties {
41
+ recipients: Recipient[];
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knocklabs/node",
3
- "version": "0.4.14",
3
+ "version": "0.4.16",
4
4
  "description": "Library for interacting with the Knock API",
5
5
  "homepage": "https://github.com/knocklabs/knock-node",
6
6
  "author": "@knocklabs",
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/jest": "26.0.23",
35
+ "@types/jsonwebtoken": "^9.0.1",
35
36
  "@types/node": "^15.0.1",
36
37
  "@types/pluralize": "0.0.29",
37
38
  "axios-mock-adapter": "1.19.0",
@@ -43,6 +44,7 @@
43
44
  "typescript": "4.2.4"
44
45
  },
45
46
  "dependencies": {
46
- "axios": "^0.21.1"
47
+ "axios": "^0.21.1",
48
+ "jsonwebtoken": "^9.0.0"
47
49
  }
48
50
  }