@prairielearn/signed-token 1.0.15 → 2.0.1
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 +12 -0
- package/dist/index.js +16 -25
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +8 -7
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.checkSignedToken = exports.getCheckedSignedTokenData = exports.generateSignedToken = void 0;
|
|
7
|
-
const base64url_1 = __importDefault(require("base64url"));
|
|
8
|
-
const debug_1 = __importDefault(require("debug"));
|
|
9
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
10
|
-
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
11
|
-
const debug = (0, debug_1.default)('prairielearn:csrf');
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import base64url from 'base64url';
|
|
3
|
+
import debugfn from 'debug';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
const debug = debugfn('prairielearn:csrf');
|
|
12
6
|
const sep = '.';
|
|
13
|
-
function generateSignedToken(data, secretKey) {
|
|
7
|
+
export function generateSignedToken(data, secretKey) {
|
|
14
8
|
debug(`generateSignedToken(): data = ${JSON.stringify(data)}`);
|
|
15
9
|
debug(`generateSignedToken(): secretKey = ${secretKey}`);
|
|
16
10
|
const dataJSON = JSON.stringify(data);
|
|
17
|
-
const dataString =
|
|
11
|
+
const dataString = base64url.default.encode(dataJSON);
|
|
18
12
|
const dateString = new Date().getTime().toString(36);
|
|
19
13
|
const checkString = dateString + sep + dataString;
|
|
20
|
-
const signature =
|
|
21
|
-
const encodedSignature =
|
|
14
|
+
const signature = crypto.createHmac('sha256', secretKey).update(checkString).digest('hex');
|
|
15
|
+
const encodedSignature = base64url.default.encode(signature);
|
|
22
16
|
debug(`generateSignedToken(): ${JSON.stringify({
|
|
23
17
|
dataString,
|
|
24
18
|
dateString,
|
|
@@ -29,12 +23,11 @@ function generateSignedToken(data, secretKey) {
|
|
|
29
23
|
debug(`generateSignedToken(): token = ${token}`);
|
|
30
24
|
return token;
|
|
31
25
|
}
|
|
32
|
-
|
|
33
|
-
function getCheckedSignedTokenData(token, secretKey, options = {}) {
|
|
26
|
+
export function getCheckedSignedTokenData(token, secretKey, options = {}) {
|
|
34
27
|
debug(`getCheckedSignedTokenData(): token = ${token}`);
|
|
35
28
|
debug(`getCheckedSignedTokenData(): secretKey = ${secretKey}`);
|
|
36
29
|
debug(`getCheckedSignedTokenData(): options = ${JSON.stringify(options)}`);
|
|
37
|
-
if (!
|
|
30
|
+
if (!_.isString(token)) {
|
|
38
31
|
debug(`getCheckedSignedTokenData(): FAIL - token is not string`);
|
|
39
32
|
return null;
|
|
40
33
|
}
|
|
@@ -49,8 +42,8 @@ function getCheckedSignedTokenData(token, secretKey, options = {}) {
|
|
|
49
42
|
const tokenDataString = match[2];
|
|
50
43
|
// check the signature
|
|
51
44
|
const checkString = tokenDateString + sep + tokenDataString;
|
|
52
|
-
const checkSignature =
|
|
53
|
-
const encodedCheckSignature =
|
|
45
|
+
const checkSignature = crypto.createHmac('sha256', secretKey).update(checkString).digest('hex');
|
|
46
|
+
const encodedCheckSignature = base64url.default.encode(checkSignature);
|
|
54
47
|
if (encodedCheckSignature !== tokenSignature) {
|
|
55
48
|
debug(`getCheckedSignedTokenData(): FAIL - signature mismatch: checkSig=${encodedCheckSignature} != tokenSig=${tokenSignature}`);
|
|
56
49
|
return null;
|
|
@@ -75,7 +68,7 @@ function getCheckedSignedTokenData(token, secretKey, options = {}) {
|
|
|
75
68
|
// get the data
|
|
76
69
|
let tokenDataJSON, tokenData;
|
|
77
70
|
try {
|
|
78
|
-
tokenDataJSON =
|
|
71
|
+
tokenDataJSON = base64url.default.decode(tokenDataString);
|
|
79
72
|
}
|
|
80
73
|
catch (e) {
|
|
81
74
|
debug(`getCheckedSignedTokenData(): FAIL - could not base64 decode: ${tokenDateString}`);
|
|
@@ -91,8 +84,7 @@ function getCheckedSignedTokenData(token, secretKey, options = {}) {
|
|
|
91
84
|
debug(`getCheckedSignedTokenData(): tokenData = ${tokenData}`);
|
|
92
85
|
return tokenData;
|
|
93
86
|
}
|
|
94
|
-
|
|
95
|
-
function checkSignedToken(token, data, secretKey, options = {}) {
|
|
87
|
+
export function checkSignedToken(token, data, secretKey, options = {}) {
|
|
96
88
|
debug(`checkSignedToken(): token = ${token}`);
|
|
97
89
|
debug(`checkSignedToken(): data = ${JSON.stringify(data)}`);
|
|
98
90
|
debug(`checkSignedToken(): secretKey = ${secretKey}`);
|
|
@@ -102,9 +94,8 @@ function checkSignedToken(token, data, secretKey, options = {}) {
|
|
|
102
94
|
debug(`checkSignedToken(): tokenData = ${JSON.stringify(tokenData)}`);
|
|
103
95
|
if (tokenData == null)
|
|
104
96
|
return false;
|
|
105
|
-
if (!
|
|
97
|
+
if (!_.isEqual(data, tokenData))
|
|
106
98
|
return false;
|
|
107
99
|
return true;
|
|
108
100
|
}
|
|
109
|
-
exports.checkSignedToken = checkSignedToken;
|
|
110
101
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from 'node:crypto';\n\nimport base64url from 'base64url';\nimport debugfn from 'debug';\nimport _ from 'lodash';\n\nconst debug = debugfn('prairielearn:csrf');\nconst sep = '.';\n\ninterface CheckOptions {\n maxAge?: number;\n}\n\nexport function generateSignedToken(data: any, secretKey: string) {\n debug(`generateSignedToken(): data = ${JSON.stringify(data)}`);\n debug(`generateSignedToken(): secretKey = ${secretKey}`);\n const dataJSON = JSON.stringify(data);\n const dataString = base64url.default.encode(dataJSON);\n const dateString = new Date().getTime().toString(36);\n const checkString = dateString + sep + dataString;\n const signature = crypto.createHmac('sha256', secretKey).update(checkString).digest('hex');\n const encodedSignature = base64url.default.encode(signature);\n debug(\n `generateSignedToken(): ${JSON.stringify({\n dataString,\n dateString,\n checkString,\n encodedSignature,\n })}`,\n );\n const token = encodedSignature + sep + checkString;\n debug(`generateSignedToken(): token = ${token}`);\n return token;\n}\n\nexport function getCheckedSignedTokenData(\n token: string,\n secretKey: string,\n options: CheckOptions = {},\n) {\n debug(`getCheckedSignedTokenData(): token = ${token}`);\n debug(`getCheckedSignedTokenData(): secretKey = ${secretKey}`);\n debug(`getCheckedSignedTokenData(): options = ${JSON.stringify(options)}`);\n if (!_.isString(token)) {\n debug(`getCheckedSignedTokenData(): FAIL - token is not string`);\n return null;\n }\n\n // break token apart into the three components\n const match = token.split(sep);\n if (match == null) {\n debug(`getCheckedSignedTokenData(): FAIL - could not split token`);\n return null;\n }\n const tokenSignature = match[0];\n const tokenDateString = match[1];\n const tokenDataString = match[2];\n\n // check the signature\n const checkString = tokenDateString + sep + tokenDataString;\n const checkSignature = crypto.createHmac('sha256', secretKey).update(checkString).digest('hex');\n const encodedCheckSignature = base64url.default.encode(checkSignature);\n if (encodedCheckSignature !== tokenSignature) {\n debug(\n `getCheckedSignedTokenData(): FAIL - signature mismatch: checkSig=${encodedCheckSignature} != tokenSig=${tokenSignature}`,\n );\n return null;\n }\n\n // check the age if we have the maxAge parameter\n if (options.maxAge != null) {\n let tokenDate;\n try {\n tokenDate = new Date(parseInt(tokenDateString, 36));\n } catch (e) {\n debug(`getCheckedSignedTokenData(): FAIL - could not parse date: ${tokenDateString}`);\n return null;\n }\n const currentTime = Date.now();\n const elapsedTime = currentTime - tokenDate.getTime();\n if (elapsedTime > options.maxAge) {\n debug(\n `getCheckedSignedTokenData(): FAIL - too old: elapsedTime=${elapsedTime} > maxAge=${options.maxAge}`,\n );\n return null;\n }\n }\n\n // get the data\n let tokenDataJSON, tokenData;\n try {\n tokenDataJSON = base64url.default.decode(tokenDataString);\n } catch (e) {\n debug(`getCheckedSignedTokenData(): FAIL - could not base64 decode: ${tokenDateString}`);\n return null;\n }\n try {\n tokenData = JSON.parse(tokenDataJSON);\n } catch (e) {\n debug(`getCheckedSignedTokenData(): FAIL - could not parse JSON: ${tokenDataJSON}`);\n return null;\n }\n debug(`getCheckedSignedTokenData(): tokenData = ${tokenData}`);\n return tokenData;\n}\n\nexport function checkSignedToken(\n token: string,\n data: any,\n secretKey: string,\n options: CheckOptions = {},\n) {\n debug(`checkSignedToken(): token = ${token}`);\n debug(`checkSignedToken(): data = ${JSON.stringify(data)}`);\n debug(`checkSignedToken(): secretKey = ${secretKey}`);\n debug(`checkSignedToken(): options = ${JSON.stringify(options)}`);\n debug(`checkSignedToken(): data = ${JSON.stringify(data)}`);\n const tokenData = getCheckedSignedTokenData(token, secretKey, options);\n debug(`checkSignedToken(): tokenData = ${JSON.stringify(tokenData)}`);\n if (tokenData == null) return false;\n if (!_.isEqual(data, tokenData)) return false;\n return true;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/signed-token",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"main": "dist/index.js",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
@@ -14,9 +15,9 @@
|
|
|
14
15
|
"devDependencies": {
|
|
15
16
|
"@prairielearn/tsconfig": "*",
|
|
16
17
|
"@types/debug": "^4.1.12",
|
|
17
|
-
"@types/node": "^20.12.
|
|
18
|
+
"@types/node": "^20.12.11",
|
|
18
19
|
"mocha": "^10.4.0",
|
|
19
|
-
"typescript": "^5.4.
|
|
20
|
+
"typescript": "^5.4.5"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"base64url": "^3.0.1",
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
import base64url from 'base64url';
|
|
2
|
-
import
|
|
4
|
+
import debugfn from 'debug';
|
|
3
5
|
import _ from 'lodash';
|
|
4
|
-
import crypto from 'node:crypto';
|
|
5
6
|
|
|
6
|
-
const debug =
|
|
7
|
+
const debug = debugfn('prairielearn:csrf');
|
|
7
8
|
const sep = '.';
|
|
8
9
|
|
|
9
10
|
interface CheckOptions {
|
|
@@ -14,11 +15,11 @@ export function generateSignedToken(data: any, secretKey: string) {
|
|
|
14
15
|
debug(`generateSignedToken(): data = ${JSON.stringify(data)}`);
|
|
15
16
|
debug(`generateSignedToken(): secretKey = ${secretKey}`);
|
|
16
17
|
const dataJSON = JSON.stringify(data);
|
|
17
|
-
const dataString = base64url.encode(dataJSON);
|
|
18
|
+
const dataString = base64url.default.encode(dataJSON);
|
|
18
19
|
const dateString = new Date().getTime().toString(36);
|
|
19
20
|
const checkString = dateString + sep + dataString;
|
|
20
21
|
const signature = crypto.createHmac('sha256', secretKey).update(checkString).digest('hex');
|
|
21
|
-
const encodedSignature = base64url.encode(signature);
|
|
22
|
+
const encodedSignature = base64url.default.encode(signature);
|
|
22
23
|
debug(
|
|
23
24
|
`generateSignedToken(): ${JSON.stringify({
|
|
24
25
|
dataString,
|
|
@@ -58,7 +59,7 @@ export function getCheckedSignedTokenData(
|
|
|
58
59
|
// check the signature
|
|
59
60
|
const checkString = tokenDateString + sep + tokenDataString;
|
|
60
61
|
const checkSignature = crypto.createHmac('sha256', secretKey).update(checkString).digest('hex');
|
|
61
|
-
const encodedCheckSignature = base64url.encode(checkSignature);
|
|
62
|
+
const encodedCheckSignature = base64url.default.encode(checkSignature);
|
|
62
63
|
if (encodedCheckSignature !== tokenSignature) {
|
|
63
64
|
debug(
|
|
64
65
|
`getCheckedSignedTokenData(): FAIL - signature mismatch: checkSig=${encodedCheckSignature} != tokenSig=${tokenSignature}`,
|
|
@@ -88,7 +89,7 @@ export function getCheckedSignedTokenData(
|
|
|
88
89
|
// get the data
|
|
89
90
|
let tokenDataJSON, tokenData;
|
|
90
91
|
try {
|
|
91
|
-
tokenDataJSON = base64url.decode(tokenDataString);
|
|
92
|
+
tokenDataJSON = base64url.default.decode(tokenDataString);
|
|
92
93
|
} catch (e) {
|
|
93
94
|
debug(`getCheckedSignedTokenData(): FAIL - could not base64 decode: ${tokenDateString}`);
|
|
94
95
|
return null;
|