@guardian/pan-domain-node 1.0.0 → 1.1.0
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/CHANGELOG.md +6 -0
- package/README.md +12 -1
- package/dist/src/api.d.ts +2 -2
- package/dist/src/api.js +2 -2
- package/dist/src/fetch-public-key.d.ts +2 -1
- package/dist/src/fetch-public-key.js +48 -20
- package/dist/src/panda.d.ts +4 -2
- package/dist/src/panda.js +39 -18
- package/dist/src/utils.d.ts +1 -1
- package/dist/src/utils.js +29 -16
- package/dist/test/fixtures.js +2 -2
- package/dist/test/panda.test.js +17 -17
- package/dist/test/utils.test.js +1 -1
- package/package.json +9 -6
- package/src/fetch-public-key.ts +29 -13
- package/src/panda.ts +12 -4
- package/test/panda.test.ts +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -40,16 +40,27 @@ Grace period: [------------- 24 hours ------]
|
|
|
40
40
|
npm install --save-dev @guardian/pan-domain-node
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
### Setup
|
|
44
|
+
The library load the public key file from a S3 object. Consuming applications can specify the S3 object via the arugments 'region', 'bucket' and 'keyFile' to the constructor of `PanDomainAuthentication` class as shown in the Initialisation below.
|
|
45
|
+
|
|
46
|
+
Therefore, the application must run with an AWS credential that has read access to the S3 object in that bucket.
|
|
47
|
+
|
|
48
|
+
You may refer to [Pan Domain authentication documentation](https://github.com/guardian/pan-domain-authentication) for details on how this authentication works.
|
|
49
|
+
|
|
43
50
|
### Initialisation
|
|
44
51
|
```typescript
|
|
45
52
|
import { PanDomainAuthentication, AuthenticationStatus, User, guardianValidation } from '@guardian/pan-domain-node';
|
|
53
|
+
import { fromIni } from "@aws-sdk/credential-providers";
|
|
54
|
+
|
|
55
|
+
const credentialsProvider = fromIni(); // get credentials locally using the default profile
|
|
46
56
|
|
|
47
57
|
const panda = new PanDomainAuthentication(
|
|
48
58
|
"gutoolsAuth-assym", // cookie name
|
|
49
59
|
"eu-west-1", // AWS region
|
|
50
60
|
"pan-domain-auth-settings", // Settings bucket
|
|
51
61
|
"local.dev-gutools.co.uk.settings.public", // Settings file
|
|
52
|
-
guardianValidation
|
|
62
|
+
guardianValidation,
|
|
63
|
+
credentialsProvider, // it can be omitted if the app runs in AWS cloud. In this case, "fromNodeProviderChain" is used by default.
|
|
53
64
|
);
|
|
54
65
|
|
|
55
66
|
// alternatively customise the validation function and pass at construction
|
package/dist/src/api.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface CookieFailure extends Failure {
|
|
|
29
29
|
export interface UnknownFailure extends Failure {
|
|
30
30
|
reason: 'unknown';
|
|
31
31
|
}
|
|
32
|
-
export
|
|
32
|
+
export type AuthenticationResult = FreshSuccess | StaleSuccess | CookieFailure | UserValidationFailure | UnknownFailure;
|
|
33
33
|
export interface User {
|
|
34
34
|
firstName: string;
|
|
35
35
|
lastName: string;
|
|
@@ -40,5 +40,5 @@ export interface User {
|
|
|
40
40
|
expires: number;
|
|
41
41
|
multifactor: boolean;
|
|
42
42
|
}
|
|
43
|
-
export
|
|
43
|
+
export type ValidateUserFn = (user: User) => boolean;
|
|
44
44
|
export declare function guardianValidation(user: User): boolean;
|
package/dist/src/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.gracePeriodInMillis = exports.PanDomainAuthentication = void 0;
|
|
4
|
+
exports.guardianValidation = guardianValidation;
|
|
4
5
|
var panda_1 = require("./panda");
|
|
5
6
|
Object.defineProperty(exports, "PanDomainAuthentication", { enumerable: true, get: function () { return panda_1.PanDomainAuthentication; } });
|
|
6
7
|
// We continue to consider the request authenticated for
|
|
@@ -19,4 +20,3 @@ function guardianValidation(user) {
|
|
|
19
20
|
const isGuardianUser = user.email.indexOf('guardian.co.uk') !== -1;
|
|
20
21
|
return isGuardianUser && user.multifactor;
|
|
21
22
|
}
|
|
22
|
-
exports.guardianValidation = guardianValidation;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { S3 } from "@aws-sdk/client-s3";
|
|
1
2
|
export interface PublicKeyHolder {
|
|
2
3
|
key: string;
|
|
3
4
|
lastUpdated: Date;
|
|
4
5
|
}
|
|
5
|
-
export declare function fetchPublicKey(
|
|
6
|
+
export declare function fetchPublicKey(s3: S3, bucket: string, keyFile: string): Promise<PublicKeyHolder>;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -11,30 +15,54 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
11
15
|
}) : function(o, v) {
|
|
12
16
|
o["default"] = v;
|
|
13
17
|
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
21
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.fetchPublicKey =
|
|
36
|
+
exports.fetchPublicKey = fetchPublicKey;
|
|
23
37
|
const iniparser = __importStar(require("iniparser"));
|
|
24
38
|
const utils_1 = require("./utils");
|
|
25
|
-
function fetchPublicKey(
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
function fetchPublicKey(s3, bucket, keyFile) {
|
|
40
|
+
const publicKeyLocation = {
|
|
41
|
+
Bucket: bucket,
|
|
42
|
+
Key: keyFile,
|
|
43
|
+
};
|
|
44
|
+
return s3.getObject(publicKeyLocation)
|
|
45
|
+
.then(({ Body }) => Body === null || Body === void 0 ? void 0 : Body.transformToString())
|
|
46
|
+
.then((pandaConfigIni) => {
|
|
47
|
+
if (!pandaConfigIni) {
|
|
48
|
+
throw Error(`could not read panda config ${JSON.stringify(publicKeyLocation)}`);
|
|
34
49
|
}
|
|
35
50
|
else {
|
|
36
|
-
|
|
51
|
+
const config = iniparser.parseString(pandaConfigIni);
|
|
52
|
+
if (config.publicKey) {
|
|
53
|
+
return {
|
|
54
|
+
key: (0, utils_1.base64ToPEM)(config.publicKey, "PUBLIC"),
|
|
55
|
+
lastUpdated: new Date()
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(`Failed to retrieve panda public key from ${JSON.stringify(config)}`);
|
|
60
|
+
throw new Error("Missing publicKey setting from config");
|
|
61
|
+
}
|
|
37
62
|
}
|
|
63
|
+
})
|
|
64
|
+
.catch((error) => {
|
|
65
|
+
console.error(`Error fetching public key from S3: ${error}`);
|
|
66
|
+
throw error;
|
|
38
67
|
});
|
|
39
68
|
}
|
|
40
|
-
exports.fetchPublicKey = fetchPublicKey;
|
package/dist/src/panda.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { User, AuthenticationResult, ValidateUserFn } from './api';
|
|
3
2
|
import { PublicKeyHolder } from './fetch-public-key';
|
|
3
|
+
import { S3 } from "@aws-sdk/client-s3";
|
|
4
|
+
import { AwsCredentialIdentityProvider } from "@aws-sdk/types";
|
|
4
5
|
export declare function createCookie(user: User, privateKey: string): string;
|
|
5
6
|
export declare function verifyUser(pandaCookie: string | undefined, publicKey: string, currentTime: Date, validateUser: ValidateUserFn): AuthenticationResult;
|
|
6
7
|
export declare class PanDomainAuthentication {
|
|
@@ -12,7 +13,8 @@ export declare class PanDomainAuthentication {
|
|
|
12
13
|
publicKey: Promise<PublicKeyHolder>;
|
|
13
14
|
keyCacheTimeInMillis: number;
|
|
14
15
|
keyUpdateTimer?: NodeJS.Timeout;
|
|
15
|
-
|
|
16
|
+
s3Client: S3;
|
|
17
|
+
constructor(cookieName: string, region: string, bucket: string, keyFile: string, validateUser: ValidateUserFn, credentialsProvider?: AwsCredentialIdentityProvider);
|
|
16
18
|
stop(): void;
|
|
17
19
|
getPublicKey(): Promise<string>;
|
|
18
20
|
verify(requestCookies: string): Promise<AuthenticationResult>;
|
package/dist/src/panda.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -11,19 +15,33 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
11
15
|
}) : function(o, v) {
|
|
12
16
|
o["default"] = v;
|
|
13
17
|
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
21
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.PanDomainAuthentication =
|
|
36
|
+
exports.PanDomainAuthentication = void 0;
|
|
37
|
+
exports.createCookie = createCookie;
|
|
38
|
+
exports.verifyUser = verifyUser;
|
|
23
39
|
const cookie = __importStar(require("cookie"));
|
|
24
40
|
const utils_1 = require("./utils");
|
|
25
41
|
const api_1 = require("./api");
|
|
26
42
|
const fetch_public_key_1 = require("./fetch-public-key");
|
|
43
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
44
|
+
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
27
45
|
function createCookie(user, privateKey) {
|
|
28
46
|
let queryParams = [];
|
|
29
47
|
queryParams.push("firstName=" + user.firstName);
|
|
@@ -36,10 +54,9 @@ function createCookie(user, privateKey) {
|
|
|
36
54
|
queryParams.push("multifactor=" + String(user.multifactor));
|
|
37
55
|
const combined = queryParams.join("&");
|
|
38
56
|
const queryParamsString = Buffer.from(combined).toString('base64');
|
|
39
|
-
const signature = utils_1.sign(combined, privateKey);
|
|
57
|
+
const signature = (0, utils_1.sign)(combined, privateKey);
|
|
40
58
|
return queryParamsString + "." + signature;
|
|
41
59
|
}
|
|
42
|
-
exports.createCookie = createCookie;
|
|
43
60
|
function verifyUser(pandaCookie, publicKey, currentTime, validateUser) {
|
|
44
61
|
if (!pandaCookie) {
|
|
45
62
|
return {
|
|
@@ -47,7 +64,7 @@ function verifyUser(pandaCookie, publicKey, currentTime, validateUser) {
|
|
|
47
64
|
reason: 'no-cookie'
|
|
48
65
|
};
|
|
49
66
|
}
|
|
50
|
-
const parsedCookie = utils_1.parseCookie(pandaCookie);
|
|
67
|
+
const parsedCookie = (0, utils_1.parseCookie)(pandaCookie);
|
|
51
68
|
if (!parsedCookie) {
|
|
52
69
|
return {
|
|
53
70
|
success: false,
|
|
@@ -55,7 +72,7 @@ function verifyUser(pandaCookie, publicKey, currentTime, validateUser) {
|
|
|
55
72
|
};
|
|
56
73
|
}
|
|
57
74
|
const { data, signature } = parsedCookie;
|
|
58
|
-
if (!utils_1.verifySignature(data, signature, publicKey)) {
|
|
75
|
+
if (!(0, utils_1.verifySignature)(data, signature, publicKey)) {
|
|
59
76
|
return {
|
|
60
77
|
success: false,
|
|
61
78
|
reason: 'invalid-cookie'
|
|
@@ -63,7 +80,7 @@ function verifyUser(pandaCookie, publicKey, currentTime, validateUser) {
|
|
|
63
80
|
}
|
|
64
81
|
const currentTimestampInMillis = currentTime.getTime();
|
|
65
82
|
try {
|
|
66
|
-
const user = utils_1.parseUser(data);
|
|
83
|
+
const user = (0, utils_1.parseUser)(data);
|
|
67
84
|
const isExpired = user.expires < currentTimestampInMillis;
|
|
68
85
|
if (isExpired) {
|
|
69
86
|
const gracePeriodEndsAtEpochTimeMillis = user.expires + api_1.gracePeriodInMillis;
|
|
@@ -103,16 +120,20 @@ function verifyUser(pandaCookie, publicKey, currentTime, validateUser) {
|
|
|
103
120
|
};
|
|
104
121
|
}
|
|
105
122
|
}
|
|
106
|
-
exports.verifyUser = verifyUser;
|
|
107
123
|
class PanDomainAuthentication {
|
|
108
|
-
constructor(cookieName, region, bucket, keyFile, validateUser) {
|
|
124
|
+
constructor(cookieName, region, bucket, keyFile, validateUser, credentialsProvider = (0, credential_providers_1.fromNodeProviderChain)()) {
|
|
109
125
|
this.keyCacheTimeInMillis = 60 * 1000; // 1 minute
|
|
110
126
|
this.cookieName = cookieName;
|
|
111
127
|
this.region = region;
|
|
112
128
|
this.bucket = bucket;
|
|
113
129
|
this.keyFile = keyFile;
|
|
114
130
|
this.validateUser = validateUser;
|
|
115
|
-
|
|
131
|
+
const standardAwsConfig = {
|
|
132
|
+
region: region,
|
|
133
|
+
credentials: credentialsProvider,
|
|
134
|
+
};
|
|
135
|
+
this.s3Client = new client_s3_1.S3(standardAwsConfig);
|
|
136
|
+
this.publicKey = (0, fetch_public_key_1.fetchPublicKey)(this.s3Client, bucket, keyFile);
|
|
116
137
|
this.keyUpdateTimer = setInterval(() => this.getPublicKey(), this.keyCacheTimeInMillis);
|
|
117
138
|
}
|
|
118
139
|
stop() {
|
|
@@ -126,7 +147,7 @@ class PanDomainAuthentication {
|
|
|
126
147
|
const now = new Date();
|
|
127
148
|
const diff = now.getTime() - lastUpdated.getTime();
|
|
128
149
|
if (diff > this.keyCacheTimeInMillis) {
|
|
129
|
-
this.publicKey = fetch_public_key_1.fetchPublicKey(this.
|
|
150
|
+
this.publicKey = (0, fetch_public_key_1.fetchPublicKey)(this.s3Client, this.bucket, this.keyFile);
|
|
130
151
|
return this.publicKey.then(({ key }) => key);
|
|
131
152
|
}
|
|
132
153
|
else {
|
package/dist/src/utils.d.ts
CHANGED
package/dist/src/utils.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -11,22 +15,37 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
11
15
|
}) : function(o, v) {
|
|
12
16
|
o["default"] = v;
|
|
13
17
|
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
21
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.
|
|
36
|
+
exports.decodeBase64 = decodeBase64;
|
|
37
|
+
exports.parseCookie = parseCookie;
|
|
38
|
+
exports.verifySignature = verifySignature;
|
|
39
|
+
exports.sign = sign;
|
|
40
|
+
exports.base64ToPEM = base64ToPEM;
|
|
41
|
+
exports.httpGet = httpGet;
|
|
42
|
+
exports.parseUser = parseUser;
|
|
23
43
|
const crypto = __importStar(require("crypto"));
|
|
24
44
|
const https = __importStar(require("https"));
|
|
25
45
|
const url_1 = require("url");
|
|
26
46
|
function decodeBase64(data) {
|
|
27
47
|
return Buffer.from(data, 'base64').toString('utf8');
|
|
28
48
|
}
|
|
29
|
-
exports.decodeBase64 = decodeBase64;
|
|
30
49
|
/**
|
|
31
50
|
* Check if a string is valid base64
|
|
32
51
|
*/
|
|
@@ -57,7 +76,6 @@ function parseCookie(cookie) {
|
|
|
57
76
|
signature: signature
|
|
58
77
|
};
|
|
59
78
|
}
|
|
60
|
-
exports.parseCookie = parseCookie;
|
|
61
79
|
/**
|
|
62
80
|
* Verify signed data using nodeJs crypto library
|
|
63
81
|
*/
|
|
@@ -66,14 +84,12 @@ function verifySignature(message, signature, pandaPublicKey) {
|
|
|
66
84
|
.update(message, 'utf8')
|
|
67
85
|
.verify(pandaPublicKey, signature, 'base64');
|
|
68
86
|
}
|
|
69
|
-
exports.verifySignature = verifySignature;
|
|
70
87
|
function sign(message, privateKey) {
|
|
71
88
|
const sign = crypto.createSign("sha256WithRSAEncryption");
|
|
72
89
|
sign.write(message);
|
|
73
90
|
sign.end();
|
|
74
91
|
return sign.sign(privateKey, 'base64');
|
|
75
92
|
}
|
|
76
|
-
exports.sign = sign;
|
|
77
93
|
const ASCII_NEW_LINE = String.fromCharCode(10);
|
|
78
94
|
function base64ToPEM(key, headerFooter) {
|
|
79
95
|
const PEM_HEADER = `-----BEGIN ${headerFooter} KEY-----`;
|
|
@@ -91,7 +107,6 @@ function base64ToPEM(key, headerFooter) {
|
|
|
91
107
|
ret.push(Buffer.from(PEM_FOOTER).toString('ascii'));
|
|
92
108
|
return ret.join(ASCII_NEW_LINE);
|
|
93
109
|
}
|
|
94
|
-
exports.base64ToPEM = base64ToPEM;
|
|
95
110
|
function httpGet(path) {
|
|
96
111
|
return new Promise((resolve, reject) => {
|
|
97
112
|
const data = [];
|
|
@@ -113,7 +128,6 @@ function httpGet(path) {
|
|
|
113
128
|
});
|
|
114
129
|
});
|
|
115
130
|
}
|
|
116
|
-
exports.httpGet = httpGet;
|
|
117
131
|
function parseUser(data) {
|
|
118
132
|
const params = new url_1.URLSearchParams(data);
|
|
119
133
|
function stringField(name) {
|
|
@@ -152,4 +166,3 @@ function parseUser(data) {
|
|
|
152
166
|
multifactor: booleanField("multifactor")
|
|
153
167
|
};
|
|
154
168
|
}
|
|
155
|
-
exports.parseUser = parseUser;
|
package/dist/test/fixtures.js
CHANGED
|
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.sampleNonGuardianCookie = exports.sampleCookieWithoutMultifactor = exports.sampleCookie = exports.publicKey = exports.encodedPublicKey = exports.privateKey = exports.encodedPrivateKey = void 0;
|
|
4
4
|
const utils_1 = require("../src/utils");
|
|
5
5
|
exports.encodedPrivateKey = "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";
|
|
6
|
-
exports.privateKey = utils_1.base64ToPEM(exports.encodedPrivateKey, "RSA PRIVATE");
|
|
6
|
+
exports.privateKey = (0, utils_1.base64ToPEM)(exports.encodedPrivateKey, "RSA PRIVATE");
|
|
7
7
|
exports.encodedPublicKey = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAur0hOjhB2QWjwOopCR+Qo27AYv97BJkVaKWPXpj9RfvY1wtpIratDN6tkXN9WCRPzVX8+5qaW034Kvf9WwBZD1ntS8iHYwY1YUaU8Mrp2sRT3K0RqyBlTswIH3HIqpASqv7ZtwDHdhk7Cbd13P5aomJSOjYFhCDUi3sRbjJP1kb6uQLdkZj8fIU518HzSR7Kw7p2mbDqSrGbnaeHWd0Tr3BvDHp9Pi0KpSAVm2qWAXix+BcjMA4ar7kLU1Pre0lt4K4DlSvq5XoHdX9/yvS6KGf+8pXDR9bY6dgRPSG4mzKpiKfkv1eXE8WKs0q3217QZItaSjocw4d0o47vSN+/MAh9V5Zewyb6ogs8JicX3Y3FPG29I1g8iLf3kBZ7V4mUimGuOq/L+1YVvyOTb2zWWMjNmECO2lrxXJc5LWRs5FmSJyCdilRktDE0WTGUo89O+DcF2752qtpUmlV2fllU1LXIAn0TJKiAZspKakamifrgYFIzZK4oZ8wDeFesQB/a/U7wtyv85vknzCtMLI28dnpGQ/ZFHNqWYVaHoHsnEmWion7lgMctnpY5pwKFfUSfZecl2Xqwjk1HZj71A9TFNQj+/x4z957cNtx+utAkGinK3eZF+H1o5YnSgjg4hN41kbXttk8nADerPdF7hDS6np7xzUl6qOicJhEOJ5x0c18CAwEAAQ==";
|
|
8
|
-
exports.publicKey = utils_1.base64ToPEM(exports.encodedPublicKey, "PUBLIC");
|
|
8
|
+
exports.publicKey = (0, utils_1.base64ToPEM)(exports.encodedPublicKey, "PUBLIC");
|
|
9
9
|
// The comments above each fixture are the Scala case classe representation used to generate the encoded cookie
|
|
10
10
|
/*
|
|
11
11
|
val user = AuthenticatedUser(
|
package/dist/test/panda.test.js
CHANGED
|
@@ -15,14 +15,14 @@ const fetch_public_key_1 = require("../src/fetch-public-key");
|
|
|
15
15
|
const fixtures_1 = require("./fixtures");
|
|
16
16
|
const utils_1 = require("../src/utils");
|
|
17
17
|
jest.mock('../src/fetch-public-key');
|
|
18
|
-
jest.useFakeTimers(
|
|
18
|
+
jest.useFakeTimers();
|
|
19
19
|
function userFromCookie(cookie) {
|
|
20
20
|
// This function is only used to generate a `User` object from
|
|
21
21
|
// a well-formed text fixture cookie, in order to check that successful
|
|
22
22
|
// `AuthenticationResult`s have the right shape. As such we don't want
|
|
23
23
|
// to have to deal with the case of a bad cookie so we just cast to `ParsedCookie`.
|
|
24
|
-
const parsedCookie = utils_1.parseCookie(cookie);
|
|
25
|
-
return utils_1.parseUser(parsedCookie.data);
|
|
24
|
+
const parsedCookie = (0, utils_1.parseCookie)(cookie);
|
|
25
|
+
return (0, utils_1.parseUser)(parsedCookie.data);
|
|
26
26
|
}
|
|
27
27
|
describe('verifyUser', function () {
|
|
28
28
|
test("fail to authenticate if cookie is missing", () => {
|
|
@@ -30,7 +30,7 @@ describe('verifyUser', function () {
|
|
|
30
30
|
success: false,
|
|
31
31
|
reason: 'no-cookie'
|
|
32
32
|
};
|
|
33
|
-
expect(panda_1.verifyUser(undefined, "", new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
33
|
+
expect((0, panda_1.verifyUser)(undefined, "", new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
34
34
|
});
|
|
35
35
|
test("fail to authenticate if signature is malformed", () => {
|
|
36
36
|
const [data, signature] = fixtures_1.sampleCookie.split(".");
|
|
@@ -39,7 +39,7 @@ describe('verifyUser', function () {
|
|
|
39
39
|
success: false,
|
|
40
40
|
reason: 'invalid-cookie'
|
|
41
41
|
};
|
|
42
|
-
expect(panda_1.verifyUser(testCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
42
|
+
expect((0, panda_1.verifyUser)(testCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
43
43
|
});
|
|
44
44
|
test("fail to authenticate if cookie expired and we're outside the grace period", () => {
|
|
45
45
|
// Cookie expires at epoch time 1234
|
|
@@ -48,15 +48,15 @@ describe('verifyUser', function () {
|
|
|
48
48
|
success: false,
|
|
49
49
|
reason: 'expired-cookie'
|
|
50
50
|
};
|
|
51
|
-
expect(panda_1.verifyUser(fixtures_1.sampleCookie, fixtures_1.publicKey, afterEndOfGracePeriod, api_1.guardianValidation)).toStrictEqual(expected);
|
|
51
|
+
expect((0, panda_1.verifyUser)(fixtures_1.sampleCookie, fixtures_1.publicKey, afterEndOfGracePeriod, api_1.guardianValidation)).toStrictEqual(expected);
|
|
52
52
|
});
|
|
53
53
|
test("fail to authenticate if user fails validation function", () => {
|
|
54
|
-
expect(panda_1.verifyUser(fixtures_1.sampleCookieWithoutMultifactor, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual({
|
|
54
|
+
expect((0, panda_1.verifyUser)(fixtures_1.sampleCookieWithoutMultifactor, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual({
|
|
55
55
|
success: false,
|
|
56
56
|
reason: 'invalid-user',
|
|
57
57
|
user: userFromCookie(fixtures_1.sampleCookieWithoutMultifactor)
|
|
58
58
|
});
|
|
59
|
-
expect(panda_1.verifyUser(fixtures_1.sampleNonGuardianCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual({
|
|
59
|
+
expect((0, panda_1.verifyUser)(fixtures_1.sampleNonGuardianCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual({
|
|
60
60
|
success: false,
|
|
61
61
|
reason: 'invalid-user',
|
|
62
62
|
user: userFromCookie(fixtures_1.sampleNonGuardianCookie)
|
|
@@ -68,7 +68,7 @@ describe('verifyUser', function () {
|
|
|
68
68
|
reason: 'invalid-cookie'
|
|
69
69
|
};
|
|
70
70
|
const slightlyBadCookie = fixtures_1.sampleCookie.slice(0, -2);
|
|
71
|
-
expect(panda_1.verifyUser(slightlyBadCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
71
|
+
expect((0, panda_1.verifyUser)(slightlyBadCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
72
72
|
});
|
|
73
73
|
test("fail to authenticate with invalid-cookie reason if data part is not base64", () => {
|
|
74
74
|
const expected = {
|
|
@@ -78,7 +78,7 @@ describe('verifyUser', function () {
|
|
|
78
78
|
const [_, signature] = fixtures_1.sampleCookie.split(".");
|
|
79
79
|
const nonBase64Data = "not-base64-data";
|
|
80
80
|
const testCookie = `${nonBase64Data}.${signature}`;
|
|
81
|
-
expect(panda_1.verifyUser(testCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
81
|
+
expect((0, panda_1.verifyUser)(testCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
82
82
|
});
|
|
83
83
|
test("fail to authenticate with invalid-cookie reason if signature part is not base64", () => {
|
|
84
84
|
const expected = {
|
|
@@ -88,7 +88,7 @@ describe('verifyUser', function () {
|
|
|
88
88
|
const [data, _] = fixtures_1.sampleCookie.split(".");
|
|
89
89
|
const nonBase64Signature = "not-base64-signature";
|
|
90
90
|
const testCookie = `${data}.${nonBase64Signature}`;
|
|
91
|
-
expect(panda_1.verifyUser(testCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
91
|
+
expect((0, panda_1.verifyUser)(testCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
92
92
|
});
|
|
93
93
|
test("fail to authenticate with invalid-cookie reason if cookie has no dot separator", () => {
|
|
94
94
|
const expected = {
|
|
@@ -96,7 +96,7 @@ describe('verifyUser', function () {
|
|
|
96
96
|
reason: 'invalid-cookie'
|
|
97
97
|
};
|
|
98
98
|
const noDotCookie = fixtures_1.sampleCookie.replace(".", "");
|
|
99
|
-
expect(panda_1.verifyUser(noDotCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
99
|
+
expect((0, panda_1.verifyUser)(noDotCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
100
100
|
});
|
|
101
101
|
test("fail to authenticate with invalid-cookie reason if cookie has multiple dot separators", () => {
|
|
102
102
|
const expected = {
|
|
@@ -104,7 +104,7 @@ describe('verifyUser', function () {
|
|
|
104
104
|
reason: 'invalid-cookie'
|
|
105
105
|
};
|
|
106
106
|
const multipleDotsCookie = fixtures_1.sampleCookie.replace(".", "..");
|
|
107
|
-
expect(panda_1.verifyUser(multipleDotsCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
107
|
+
expect((0, panda_1.verifyUser)(multipleDotsCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
108
108
|
});
|
|
109
109
|
test("authenticate if the cookie and user are valid", () => {
|
|
110
110
|
const expected = {
|
|
@@ -113,7 +113,7 @@ describe('verifyUser', function () {
|
|
|
113
113
|
shouldRefreshCredentials: false,
|
|
114
114
|
user: userFromCookie(fixtures_1.sampleCookie)
|
|
115
115
|
};
|
|
116
|
-
expect(panda_1.verifyUser(fixtures_1.sampleCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
116
|
+
expect((0, panda_1.verifyUser)(fixtures_1.sampleCookie, fixtures_1.publicKey, new Date(0), api_1.guardianValidation)).toStrictEqual(expected);
|
|
117
117
|
});
|
|
118
118
|
test("authenticate with shouldRefreshCredentials if cookie expired but we're within the grace period", () => {
|
|
119
119
|
const beforeEndOfGracePeriod = new Date(1234 + api_1.gracePeriodInMillis - 1);
|
|
@@ -123,7 +123,7 @@ describe('verifyUser', function () {
|
|
|
123
123
|
shouldRefreshCredentials: true,
|
|
124
124
|
mustRefreshByEpochTimeMillis: 1234 + api_1.gracePeriodInMillis
|
|
125
125
|
};
|
|
126
|
-
expect(panda_1.verifyUser(fixtures_1.sampleCookie, fixtures_1.publicKey, beforeEndOfGracePeriod, api_1.guardianValidation)).toStrictEqual(expected);
|
|
126
|
+
expect((0, panda_1.verifyUser)(fixtures_1.sampleCookie, fixtures_1.publicKey, beforeEndOfGracePeriod, api_1.guardianValidation)).toStrictEqual(expected);
|
|
127
127
|
});
|
|
128
128
|
});
|
|
129
129
|
describe('createCookie', function () {
|
|
@@ -137,8 +137,8 @@ describe('createCookie', function () {
|
|
|
137
137
|
expires: 1234,
|
|
138
138
|
multifactor: true
|
|
139
139
|
};
|
|
140
|
-
const cookie = panda_1.createCookie(user, fixtures_1.privateKey);
|
|
141
|
-
expect(utils_1.decodeBase64(cookie)).toEqual(utils_1.decodeBase64(fixtures_1.sampleCookie));
|
|
140
|
+
const cookie = (0, panda_1.createCookie)(user, fixtures_1.privateKey);
|
|
141
|
+
expect((0, utils_1.decodeBase64)(cookie)).toEqual((0, utils_1.decodeBase64)(fixtures_1.sampleCookie));
|
|
142
142
|
expect(cookie).toEqual(fixtures_1.sampleCookie);
|
|
143
143
|
});
|
|
144
144
|
});
|
package/dist/test/utils.test.js
CHANGED
|
@@ -4,7 +4,7 @@ const utils_1 = require("../src/utils");
|
|
|
4
4
|
const fixtures_1 = require("./fixtures");
|
|
5
5
|
const url_1 = require("url");
|
|
6
6
|
test("decode a cookie", () => {
|
|
7
|
-
const parsedCookie = utils_1.parseCookie(fixtures_1.sampleCookie);
|
|
7
|
+
const parsedCookie = (0, utils_1.parseCookie)(fixtures_1.sampleCookie);
|
|
8
8
|
expect(parsedCookie).toBeDefined();
|
|
9
9
|
// Unfortunately the above expect() doesn't narrow the type
|
|
10
10
|
if (parsedCookie) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guardian/pan-domain-node",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "NodeJs implementation of Guardian pan-domain auth verification",
|
|
5
5
|
"main": "dist/src/api.js",
|
|
6
6
|
"types": "dist/src/api.d.ts",
|
|
@@ -25,13 +25,16 @@
|
|
|
25
25
|
"@changesets/cli": "^2.27.10",
|
|
26
26
|
"@types/cookie": "^0.4.0",
|
|
27
27
|
"@types/iniparser": "0.0.29",
|
|
28
|
-
"@types/jest": "^
|
|
29
|
-
"@types/node": "^
|
|
30
|
-
"jest": "^
|
|
31
|
-
"ts-jest": "^
|
|
32
|
-
"typescript": "^
|
|
28
|
+
"@types/jest": "^29.5.14",
|
|
29
|
+
"@types/node": "^22.10.1",
|
|
30
|
+
"jest": "^29.7.0",
|
|
31
|
+
"ts-jest": "^29.2.5",
|
|
32
|
+
"typescript": "^5.7.3"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@aws-sdk/client-s3": "^3.299.0",
|
|
36
|
+
"@aws-sdk/credential-providers": "^3.299.0",
|
|
37
|
+
"@aws-sdk/types": "^3.299.0",
|
|
35
38
|
"cookie": "^0.4.1",
|
|
36
39
|
"iniparser": "^1.0.5"
|
|
37
40
|
},
|
package/src/fetch-public-key.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as iniparser from 'iniparser';
|
|
2
2
|
import {base64ToPEM, httpGet} from './utils';
|
|
3
|
+
import { S3 } from "@aws-sdk/client-s3";
|
|
3
4
|
|
|
4
5
|
export interface PublicKeyHolder {
|
|
5
6
|
key: string,
|
|
@@ -7,21 +8,36 @@ export interface PublicKeyHolder {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
export function fetchPublicKey(
|
|
11
|
-
const path = `https://s3.${region}.amazonaws.com/${bucket}/${keyFile}`;
|
|
11
|
+
export function fetchPublicKey(s3: S3, bucket: string, keyFile: string): Promise<PublicKeyHolder> {
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const publicKeyLocation = {
|
|
14
|
+
Bucket: bucket,
|
|
15
|
+
Key: keyFile,
|
|
16
|
+
};
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
return s3.getObject(publicKeyLocation)
|
|
19
|
+
.then(({ Body }) => Body?.transformToString())
|
|
20
|
+
.then((pandaConfigIni) => {
|
|
21
|
+
if (!pandaConfigIni) {
|
|
22
|
+
throw Error(`could not read panda config ${JSON.stringify(publicKeyLocation)}`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const config: { publicKey?: string } = iniparser.parseString(pandaConfigIni);
|
|
26
|
+
if (config.publicKey) {
|
|
27
|
+
return {
|
|
28
|
+
key: base64ToPEM(config.publicKey, "PUBLIC"),
|
|
29
|
+
lastUpdated: new Date()
|
|
30
|
+
};
|
|
31
|
+
} else {
|
|
32
|
+
console.log(`Failed to retrieve panda public key from ${JSON.stringify(config)}`);
|
|
33
|
+
throw new Error("Missing publicKey setting from config");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
.catch((error) => {
|
|
38
|
+
console.error(`Error fetching public key from S3: ${error}`);
|
|
39
|
+
throw error;
|
|
40
|
+
});
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
|
package/src/panda.ts
CHANGED
|
@@ -3,6 +3,9 @@ import * as cookie from 'cookie';
|
|
|
3
3
|
import {parseCookie, parseUser, sign, verifySignature} from './utils';
|
|
4
4
|
import {User, AuthenticationResult, ValidateUserFn, gracePeriodInMillis} from './api';
|
|
5
5
|
import { fetchPublicKey, PublicKeyHolder } from './fetch-public-key';
|
|
6
|
+
import { S3 } from "@aws-sdk/client-s3";
|
|
7
|
+
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
|
|
8
|
+
import { AwsCredentialIdentityProvider } from "@aws-sdk/types";
|
|
6
9
|
|
|
7
10
|
export function createCookie(user: User, privateKey: string): string {
|
|
8
11
|
let queryParams: string[] = [];
|
|
@@ -103,15 +106,21 @@ export class PanDomainAuthentication {
|
|
|
103
106
|
publicKey: Promise<PublicKeyHolder>;
|
|
104
107
|
keyCacheTimeInMillis: number = 60 * 1000; // 1 minute
|
|
105
108
|
keyUpdateTimer?: NodeJS.Timeout;
|
|
109
|
+
s3Client: S3;
|
|
106
110
|
|
|
107
|
-
constructor(cookieName: string, region: string, bucket: string, keyFile: string, validateUser: ValidateUserFn) {
|
|
111
|
+
constructor(cookieName: string, region: string, bucket: string, keyFile: string, validateUser: ValidateUserFn, credentialsProvider: AwsCredentialIdentityProvider = fromNodeProviderChain()) {
|
|
108
112
|
this.cookieName = cookieName;
|
|
109
113
|
this.region = region;
|
|
110
114
|
this.bucket = bucket;
|
|
111
115
|
this.keyFile = keyFile;
|
|
112
116
|
this.validateUser = validateUser;
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
const standardAwsConfig = {
|
|
119
|
+
region: region,
|
|
120
|
+
credentials: credentialsProvider,
|
|
121
|
+
};
|
|
122
|
+
this.s3Client = new S3(standardAwsConfig);
|
|
123
|
+
this.publicKey = fetchPublicKey(this.s3Client, bucket, keyFile);
|
|
115
124
|
|
|
116
125
|
this.keyUpdateTimer = setInterval(() => this.getPublicKey(), this.keyCacheTimeInMillis);
|
|
117
126
|
}
|
|
@@ -129,7 +138,7 @@ export class PanDomainAuthentication {
|
|
|
129
138
|
const diff = now.getTime() - lastUpdated.getTime();
|
|
130
139
|
|
|
131
140
|
if(diff > this.keyCacheTimeInMillis) {
|
|
132
|
-
this.publicKey = fetchPublicKey(this.
|
|
141
|
+
this.publicKey = fetchPublicKey(this.s3Client, this.bucket, this.keyFile);
|
|
133
142
|
return this.publicKey.then(({ key }) => key);
|
|
134
143
|
} else {
|
|
135
144
|
return key;
|
|
@@ -141,7 +150,6 @@ export class PanDomainAuthentication {
|
|
|
141
150
|
return this.getPublicKey().then(publicKey => {
|
|
142
151
|
const cookies = cookie.parse(requestCookies);
|
|
143
152
|
const pandaCookie = cookies[this.cookieName];
|
|
144
|
-
|
|
145
153
|
return verifyUser(pandaCookie, publicKey, new Date(), this.validateUser);
|
|
146
154
|
});
|
|
147
155
|
}
|
package/test/panda.test.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import {decodeBase64, parseCookie, ParsedCookie, parseUser} from "../src/utils";
|
|
18
18
|
|
|
19
19
|
jest.mock('../src/fetch-public-key');
|
|
20
|
-
jest.useFakeTimers(
|
|
20
|
+
jest.useFakeTimers();
|
|
21
21
|
|
|
22
22
|
function userFromCookie(cookie: string): User {
|
|
23
23
|
// This function is only used to generate a `User` object from
|