passport-steam-openid 1.0.0 → 1.0.2

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/.eslintrc.js CHANGED
@@ -15,7 +15,7 @@ module.exports = {
15
15
  node: true,
16
16
  jest: true,
17
17
  },
18
- ignorePatterns: ['.eslintrc.js', 'sample/*'],
18
+ ignorePatterns: ['.eslintrc.js', 'sample/*', 'test/*'],
19
19
  rules: {
20
20
  '@typescript-eslint/interface-name-prefix': 'off',
21
21
  '@typescript-eslint/explicit-function-return-type': 'off',
@@ -21,3 +21,9 @@ jobs:
21
21
 
22
22
  - run: npm run build
23
23
  name: Compile library
24
+
25
+ - run: npm run test
26
+ name: Run unit tests
27
+
28
+ - run: npm run test:integration
29
+ name: Run integration test
@@ -21,6 +21,12 @@ jobs:
21
21
  - run: npm run build
22
22
  name: Compile library
23
23
 
24
+ - run: npm run test
25
+ name: Run unit tests
26
+
27
+ - run: npm run test:integration
28
+ name: Run integration test
29
+
24
30
  - name: Release
25
31
  env:
26
32
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/README.md CHANGED
@@ -3,6 +3,8 @@ Passport strategy for authenticating with steam openid without the use of 3rd pa
3
3
  which have been proved to be source of many exploits of steam openid system, apparently by design.
4
4
  This package only relies on [passport](https://www.passportjs.org/) and [axios](https://axios-http.com/).
5
5
 
6
+ Library is fully covered with tests, both unit and integration tests to make sure everything runs correctly.
7
+
6
8
  ## Usage
7
9
  ```ts
8
10
  import { SteamOpenIdStrategy } from 'passport-steam-openid';
@@ -1 +1 @@
1
- {"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../src/strategy.ts"],"names":[],"mappings":";AAAA,OAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACjD,OAAc,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAUpC,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAGhB,qCAAqC,EACrC,wCAAwC,EACxC,cAAc,EACf,MAAM,QAAQ,CAAC;AAEhB;;;;;;;GAOG;AACH,qBAAa,mBAAmB,CAC9B,QAAQ,SACJ,qCAAqC,GACrC,wCAAwC,EAC5C,KAAK,SACD,eAAe,GACf,sBAAsB,GAAG,QAAQ,SAAS,qCAAqC,GAC/E,sBAAsB,GACtB,eAAe,CACnB,SAAQ,QAAQ;IAChB;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAExC;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAEpC;;OAEG;IACH,SAAS,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAEzC;;;;;;;OAOG;gBACS,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC;IAW7D;;;;OAIG;IACmB,YAAY,CAAC,GAAG,EAAE,GAAG;IA+B3C;;;;;;;;;;;;;OAaG;IACU,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC;IA8BpD;;;;;;;;OAQG;IACH,SAAS,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO;IAOvC;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc;IAQ5C;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,cAAc;IAI5C;;;;OAIG;IACI,gBAAgB;IAYvB;;;;;;;OAOG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,IAAI,gBAAgB;IAcxE;;;;;;OAMG;IACH,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IASpD;;;;;;OAMG;IACH,SAAS,CAAC,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBzE;;;;OAIG;IACH,SAAS,CAAC,8BAA8B,CAAC,KAAK,EAAE,gBAAgB;IAMhE;;;;;;OAMG;IACH,SAAS,CAAC,oBAAoB,CAAC,QAAQ,EAAE,GAAG;IAQ5C;;;;;OAKG;IACH,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,gBAAgB;IAM5C;;;;;;;;OAQG;cACa,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IASxD;;;;;;;;;;OAUG;cACa,kBAAkB,CAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,sBAAsB,CAAC;CA0BnC"}
1
+ {"version":3,"file":"strategy.d.ts","sourceRoot":"","sources":["../src/strategy.ts"],"names":[],"mappings":";AAAA,OAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACjD,OAAc,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAUpC,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAGhB,qCAAqC,EACrC,wCAAwC,EACxC,cAAc,EACf,MAAM,QAAQ,CAAC;AAEhB;;;;;;;GAOG;AACH,qBAAa,mBAAmB,CAC9B,QAAQ,SACJ,qCAAqC,GACrC,wCAAwC,EAC5C,KAAK,SACD,eAAe,GACf,sBAAsB,GAAG,QAAQ,SAAS,qCAAqC,GAC/E,sBAAsB,GACtB,eAAe,CACnB,SAAQ,QAAQ;IAChB;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAExC;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAEpC;;OAEG;IACH,SAAS,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAEzC;;;;;;;OAOG;gBACS,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC;IAW7D;;;;OAIG;IACmB,YAAY,CAAC,GAAG,EAAE,GAAG;IA+B3C;;;;;;;;;;;;;OAaG;IACU,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC;IA8BpD;;;;;;;;OAQG;IACH,SAAS,CAAC,gBAAgB,CAAC,GAAG,EAAE,OAAO;IAOvC;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc;IAQ5C;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,cAAc;IAI5C;;;;OAIG;IACI,gBAAgB;IAYvB;;;;;;;OAOG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,IAAI,gBAAgB;IAcxE;;;;;;OAMG;IACH,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IASpD;;;;;;OAMG;IACH,SAAS,CAAC,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBzE;;;;OAIG;IACH,SAAS,CAAC,8BAA8B,CAAC,KAAK,EAAE,gBAAgB;IAMhE;;;;;;OAMG;IACH,SAAS,CAAC,oBAAoB,CAAC,QAAQ,EAAE,GAAG;IAQ5C;;;;;OAKG;IACH,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,gBAAgB;IAM5C;;;;;;;;OAQG;cACa,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IASxD;;;;;;;;;;OAUG;cACa,kBAAkB,CAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,sBAAsB,CAAC;CAiCnC"}
package/dist/strategy.js CHANGED
@@ -286,7 +286,7 @@ class SteamOpenIdStrategy extends passport_1.Strategy {
286
286
  steamids: steamId,
287
287
  key: this.apiKey,
288
288
  };
289
- return axios_1.default
289
+ return this.axios
290
290
  .get(`${constant_1.PLAYER_SUMMARY_URL}/?${querystring_1.default.stringify(summaryQuery)}`)
291
291
  .then(({ data }) => {
292
292
  var _a;
@@ -297,6 +297,9 @@ class SteamOpenIdStrategy extends passport_1.Strategy {
297
297
  if (!user) {
298
298
  throw new error_1.SteamOpenIdError('Profile was not found on steam.', type_1.SteamOpenIdErrorType.InvalidSteamId);
299
299
  }
300
+ if (user.steamid != steamId) {
301
+ throw new error_1.SteamOpenIdError('API returned invalid user.', type_1.SteamOpenIdErrorType.InvalidSteamId);
302
+ }
300
303
  return user;
301
304
  });
302
305
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passport-steam-openid",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Passport strategy for authenticating with steam openid without the use of 3rd party openid packages.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,9 +8,12 @@
8
8
  "build": "npx tsc",
9
9
  "format": "npx prettier --write \"src/**/*.ts\"",
10
10
  "lint": "npx eslint \"{src,test}/**/*.ts\" --fix",
11
- "test": "echo \"Error: no test specified\" && exit 1",
12
11
  "prepare": "npx husky install",
13
- "semantic-release": "semantic-release"
12
+ "semantic-release": "semantic-release",
13
+ "test": "npx mocha -r ts-node/register 'test/*.ts'",
14
+ "test:integration": "npx mocha -r ts-node/register 'test/integration/*.ts'",
15
+ "test:coverage": "npx nyc --reporter=text \"npm run test:integration && npm run test\"",
16
+ "test:coverage:html": "npx nyc --reporter=html \"npm run test:integration && npm run test\""
14
17
  },
15
18
  "keywords": [],
16
19
  "author": "glencoco",
@@ -22,19 +25,35 @@
22
25
  "devDependencies": {
23
26
  "@semantic-release/changelog": "^6.0.3",
24
27
  "@semantic-release/git": "^10.0.1",
28
+ "@types/chai": "^4.3.5",
29
+ "@types/chai-as-promised": "^7.1.5",
30
+ "@types/express": "^4.17.17",
31
+ "@types/express-session": "^1.17.7",
32
+ "@types/mocha": "^10.0.1",
25
33
  "@types/node": "^20.3.1",
26
34
  "@types/passport": "^1.0.12",
35
+ "@types/sinon": "^10.0.15",
27
36
  "@typescript-eslint/eslint-plugin": "^5.59.11",
28
37
  "@typescript-eslint/parser": "^5.59.11",
38
+ "chai": "^4.3.7",
39
+ "chai-as-promised": "^7.1.1",
40
+ "chai-http": "^4.4.0",
29
41
  "cz-conventional-changelog": "^3.3.0",
30
42
  "eslint": "^8.43.0",
31
43
  "eslint-config-prettier": "^8.8.0",
32
44
  "eslint-plugin-prettier": "^4.2.1",
33
45
  "eslint-plugin-sonarjs": "^0.19.0",
46
+ "express": "^4.18.2",
47
+ "express-session": "^1.17.3",
34
48
  "husky": "^8.0.3",
35
49
  "lint-staged": "^13.2.2",
50
+ "mocha": "^10.2.0",
51
+ "nock": "^13.3.2",
52
+ "nyc": "^15.1.0",
36
53
  "prettier": "^2.8.8",
37
54
  "semantic-release": "^21.0.5",
55
+ "sinon": "^15.2.0",
56
+ "ts-node": "^10.9.1",
38
57
  "typescript": "^5.1.3"
39
58
  },
40
59
  "config": {
@@ -12,7 +12,8 @@
12
12
  "dotenv": "^16.3.1",
13
13
  "express": "^4.18.2",
14
14
  "express-session": "^1.17.3",
15
- "passport": "^0.6.0"
15
+ "passport": "^0.6.0",
16
+ "passport-steam-openid": "^1.0.0"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/express": "^4.17.17",
@@ -688,6 +689,21 @@
688
689
  "node": ">=8"
689
690
  }
690
691
  },
692
+ "node_modules/asynckit": {
693
+ "version": "0.4.0",
694
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
695
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
696
+ },
697
+ "node_modules/axios": {
698
+ "version": "1.4.0",
699
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
700
+ "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
701
+ "dependencies": {
702
+ "follow-redirects": "^1.15.0",
703
+ "form-data": "^4.0.0",
704
+ "proxy-from-env": "^1.1.0"
705
+ }
706
+ },
691
707
  "node_modules/balanced-match": {
692
708
  "version": "1.0.2",
693
709
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -779,6 +795,17 @@
779
795
  "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
780
796
  "dev": true
781
797
  },
798
+ "node_modules/combined-stream": {
799
+ "version": "1.0.8",
800
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
801
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
802
+ "dependencies": {
803
+ "delayed-stream": "~1.0.0"
804
+ },
805
+ "engines": {
806
+ "node": ">= 0.8"
807
+ }
808
+ },
782
809
  "node_modules/concat-map": {
783
810
  "version": "0.0.1",
784
811
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -845,6 +872,14 @@
845
872
  "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
846
873
  "dev": true
847
874
  },
875
+ "node_modules/delayed-stream": {
876
+ "version": "1.0.0",
877
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
878
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
879
+ "engines": {
880
+ "node": ">=0.4.0"
881
+ }
882
+ },
848
883
  "node_modules/depd": {
849
884
  "version": "2.0.0",
850
885
  "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1441,6 +1476,38 @@
1441
1476
  "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
1442
1477
  "dev": true
1443
1478
  },
1479
+ "node_modules/follow-redirects": {
1480
+ "version": "1.15.2",
1481
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
1482
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
1483
+ "funding": [
1484
+ {
1485
+ "type": "individual",
1486
+ "url": "https://github.com/sponsors/RubenVerborgh"
1487
+ }
1488
+ ],
1489
+ "engines": {
1490
+ "node": ">=4.0"
1491
+ },
1492
+ "peerDependenciesMeta": {
1493
+ "debug": {
1494
+ "optional": true
1495
+ }
1496
+ }
1497
+ },
1498
+ "node_modules/form-data": {
1499
+ "version": "4.0.0",
1500
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
1501
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
1502
+ "dependencies": {
1503
+ "asynckit": "^0.4.0",
1504
+ "combined-stream": "^1.0.8",
1505
+ "mime-types": "^2.1.12"
1506
+ },
1507
+ "engines": {
1508
+ "node": ">= 6"
1509
+ }
1510
+ },
1444
1511
  "node_modules/forwarded": {
1445
1512
  "version": "0.2.0",
1446
1513
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2031,6 +2098,15 @@
2031
2098
  "url": "https://github.com/sponsors/jaredhanson"
2032
2099
  }
2033
2100
  },
2101
+ "node_modules/passport-steam-openid": {
2102
+ "version": "1.0.0",
2103
+ "resolved": "https://registry.npmjs.org/passport-steam-openid/-/passport-steam-openid-1.0.0.tgz",
2104
+ "integrity": "sha512-427nGFW0Fo+dtGXmDNLDOIe0b4PqWzEa40P5ovMA1o2W+/bpX2RlE3qzvBdHc4OZq5Bl74+9Wty03GHVZP695Q==",
2105
+ "dependencies": {
2106
+ "axios": "^1.4.0",
2107
+ "passport": "^0.6.0"
2108
+ }
2109
+ },
2034
2110
  "node_modules/passport-strategy": {
2035
2111
  "version": "1.0.0",
2036
2112
  "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
@@ -2146,6 +2222,11 @@
2146
2222
  "node": ">= 0.10"
2147
2223
  }
2148
2224
  },
2225
+ "node_modules/proxy-from-env": {
2226
+ "version": "1.1.0",
2227
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
2228
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
2229
+ },
2149
2230
  "node_modules/punycode": {
2150
2231
  "version": "2.3.0",
2151
2232
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
@@ -2577,9 +2658,9 @@
2577
2658
  }
2578
2659
  },
2579
2660
  "node_modules/word-wrap": {
2580
- "version": "1.2.3",
2581
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
2582
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
2661
+ "version": "1.2.4",
2662
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
2663
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
2583
2664
  "dev": true,
2584
2665
  "engines": {
2585
2666
  "node": ">=0.10.0"
@@ -15,7 +15,8 @@
15
15
  "dotenv": "^16.3.1",
16
16
  "express": "^4.18.2",
17
17
  "express-session": "^1.17.3",
18
- "passport": "^0.6.0"
18
+ "passport": "^0.6.0",
19
+ "passport-steam-openid": "^1.0.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@types/express": "^4.17.17",
package/sample/src/app.ts CHANGED
@@ -2,7 +2,7 @@ import 'dotenv/config';
2
2
  import passport from 'passport';
3
3
  import session from 'express-session';
4
4
  import express, { NextFunction, Request, Response } from 'express';
5
- import { SteamOpenIdError, SteamOpenIdStrategy } from '../../dist'; // TODO: actually install it from npm
5
+ import { SteamOpenIdError, SteamOpenIdStrategy } from 'passport-steam-openid';
6
6
 
7
7
  const PORT = 3000;
8
8
  const URL_BASE =
@@ -1,7 +1,9 @@
1
- declare namespace Express {
2
- import { SteamOpenIdUser } from '../../dist';
1
+ import { SteamOpenIdUser } from 'passport-steam-openid';
3
2
 
4
- export interface Request {
3
+ // For different express versions, look at:
4
+ // https://stackoverflow.com/questions/37377731/extend-express-request-object-using-typescript
5
+ declare module 'express-serve-static-core' {
6
+ interface Request {
5
7
  user?: SteamOpenIdUser | undefined;
6
8
  }
7
9
  }
package/src/strategy.ts CHANGED
@@ -364,7 +364,7 @@ export class SteamOpenIdStrategy<
364
364
  key: this.apiKey,
365
365
  };
366
366
 
367
- return axios
367
+ return this.axios
368
368
  .get<SteamPlayerSummaryResponse>(
369
369
  `${PLAYER_SUMMARY_URL}/?${qs.stringify(summaryQuery)}`,
370
370
  )
@@ -381,6 +381,13 @@ export class SteamOpenIdStrategy<
381
381
  );
382
382
  }
383
383
 
384
+ if (user.steamid != steamId) {
385
+ throw new SteamOpenIdError(
386
+ 'API returned invalid user.',
387
+ SteamOpenIdErrorType.InvalidSteamId,
388
+ );
389
+ }
390
+
384
391
  return user;
385
392
  });
386
393
  }
@@ -0,0 +1,30 @@
1
+ import { VALID_NONCE, VALID_OPENID_ENDPOINT } from '../../../src';
2
+
3
+ export const STEAMID = '76561197960435530';
4
+ export const SUCCESSFUL_QUERY: Record<string, string> = {
5
+ 'openid.mode': 'id_res',
6
+ 'openid.ns': VALID_NONCE,
7
+ 'openid.identity': `https://steamcommunity.com/openid/id/${STEAMID}`,
8
+ 'openid.claimed_id': `https://steamcommunity.com/openid/id/${STEAMID}`,
9
+ 'openid.return_to': '/auth/steam',
10
+ 'openid.op_endpoint': VALID_OPENID_ENDPOINT,
11
+ 'openid.response_nonce': `${new Date().toJSON()}8df86bac92ad1addaf3735a5aabdc6e2a7`,
12
+ 'openid.assoc_handle': '1234567890',
13
+ 'openid.signed':
14
+ 'signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle',
15
+ 'openid.sig': 'dc6e2a79de2c6aceac495ad5f4c6b6e0bfe30',
16
+ };
17
+
18
+ export function validateBody(body: Record<string, string>) {
19
+ const queryKeys = Object.keys(SUCCESSFUL_QUERY);
20
+ const bodyKeys = Object.keys(body);
21
+
22
+ for (let i = 0; i < queryKeys.length; i++) {
23
+ const key: string = queryKeys[i] as string;
24
+ if (key !== bodyKeys[i] && SUCCESSFUL_QUERY[key] != body[key]) {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ return true;
30
+ }
@@ -0,0 +1,89 @@
1
+ import express, { NextFunction, Request, Response } from 'express';
2
+ import passport from 'passport';
3
+ import session from 'express-session';
4
+ import {
5
+ SteamOpenIdError,
6
+ SteamOpenIdStrategy,
7
+ SteamOpenIdUser,
8
+ } from '../../../src';
9
+
10
+ /**
11
+ * Exported server for superagent.
12
+ *
13
+ * This server acts as a basic implementation
14
+ * of express server with passport authentication,
15
+ * using a single endpoint that handles everything
16
+ * via the provided strategy.
17
+ *
18
+ * This configuration does not fetch user information from the API.
19
+ *
20
+ * Error handling hides all important details of this implementation.
21
+ * Other important factors should be handled by helmet.js
22
+ */
23
+ export const server = express();
24
+
25
+ /**
26
+ * Type definition for user property on request object.
27
+ *
28
+ * Only required for the response from successful authentication.
29
+ */
30
+ declare module 'express-serve-static-core' {
31
+ interface Request {
32
+ user?: SteamOpenIdUser | undefined;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Server setup includes:
38
+ * - passport strategy initialization
39
+ * - passport initialization
40
+ * - session middleware
41
+ * - auth endpoint setup
42
+ * - error handling
43
+ */
44
+
45
+ passport.use(
46
+ new SteamOpenIdStrategy(
47
+ {
48
+ profile: false,
49
+ returnURL: `/auth/steam`,
50
+ },
51
+ (_req, _steamId, profile, done) => {
52
+ done(null, profile);
53
+ },
54
+ ),
55
+ );
56
+
57
+ passport.serializeUser((user: any, done) => {
58
+ done(null, user.steamid);
59
+ });
60
+
61
+ passport.deserializeUser((user: any, done) => {
62
+ done(null, { steamid: user });
63
+ });
64
+
65
+ server.use(
66
+ session({
67
+ secret: 'secret',
68
+ resave: true,
69
+ saveUninitialized: true,
70
+ }),
71
+ );
72
+ server.use(passport.initialize());
73
+ server.use(passport.session());
74
+
75
+ server.get('/auth/steam', passport.authenticate('steam-openid'), (req, res) => {
76
+ res.status(200).send(`Authenticated as ${req.user?.steamid}`);
77
+ });
78
+
79
+ server.use(
80
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
81
+ (err: Error, _req: Request, res: Response, _next: NextFunction): void => {
82
+ if (err instanceof SteamOpenIdError) {
83
+ res.status(401).send('Unauthorized');
84
+ return;
85
+ }
86
+
87
+ res.status(500).send('Internal Server Error.');
88
+ },
89
+ );
@@ -0,0 +1,121 @@
1
+ import chai, { expect } from 'chai';
2
+ import chaiHttp from 'chai-http';
3
+ import nock from 'nock';
4
+
5
+ import { server } from './setup/server';
6
+ import { STEAMID, SUCCESSFUL_QUERY, validateBody } from './setup/data';
7
+ import {
8
+ VALID_ID_SELECT,
9
+ VALID_NONCE,
10
+ VALID_OPENID_ENDPOINT,
11
+ } from '../../src/constant';
12
+
13
+ chai.use(chaiHttp);
14
+ chai.should();
15
+
16
+ describe('SteamOpenIdStrategy Integration Test', () => {
17
+ it('Successfully receives a redirect from steam', (done) => {
18
+ const response = '<h1>Successful redirect to steam</h1>';
19
+
20
+ nock('https://steamcommunity.com')
21
+ .get('/openid/login')
22
+ .query((query) => {
23
+ return (
24
+ query['openid.mode'] === 'checkid_setup' &&
25
+ query['openid.ns'] === VALID_NONCE &&
26
+ query['openid.identity'] === VALID_ID_SELECT &&
27
+ query['openid.claimed_id'] === VALID_ID_SELECT &&
28
+ query['openid.return_to'] === '/auth/steam'
29
+ );
30
+ })
31
+ .reply(200, response, {
32
+ 'Content-Type': 'text/plain',
33
+ });
34
+
35
+ chai
36
+ .request(server)
37
+ .get('/auth/steam')
38
+ .redirects(1)
39
+ .end((err, res) => {
40
+ if (err) {
41
+ done(err);
42
+ return;
43
+ }
44
+
45
+ res.should.redirectTo(new RegExp(`^${VALID_OPENID_ENDPOINT}`));
46
+ res.should.have.status(200);
47
+ expect(res.type).equal('text/plain');
48
+ expect(res.text).equal(response);
49
+
50
+ done();
51
+ });
52
+ });
53
+
54
+ it('Successfully authenticates a valid user', (done) => {
55
+ nock('https://steamcommunity.com')
56
+ .post('/openid/login', validateBody)
57
+ .matchHeader('Content-Type', 'application/x-www-form-urlencoded')
58
+ .reply(200, 'ns:http://specs.openid.net/auth/2.0\nis_valid:true\n');
59
+
60
+ chai
61
+ .request(server)
62
+ .get('/auth/steam')
63
+ .query(SUCCESSFUL_QUERY)
64
+ .redirects(0)
65
+ .end((err, res) => {
66
+ if (err) {
67
+ done(err);
68
+ return;
69
+ }
70
+
71
+ res.should.have.status(200);
72
+ expect(res.text).equal(`Authenticated as ${STEAMID}`);
73
+
74
+ done();
75
+ });
76
+ });
77
+
78
+ describe('Fails due to invalid response', () => {
79
+ it('Has invalid response nonce', (done) => {
80
+ nock('https://steamcommunity.com')
81
+ .post('/openid/login', validateBody)
82
+ .reply(200, 'ns:http://specs.openid.net/auth/1.0\nis_valid:true\n');
83
+
84
+ chai
85
+ .request(server)
86
+ .get('/auth/steam')
87
+ .query(SUCCESSFUL_QUERY)
88
+ .redirects(0)
89
+ .end((err, res) => {
90
+ if (err) {
91
+ done(err);
92
+ return;
93
+ }
94
+
95
+ res.should.have.status(401);
96
+ done();
97
+ });
98
+ });
99
+
100
+ it('Has is_valid set to false', (done) => {
101
+ nock('https://steamcommunity.com')
102
+ .post('/openid/login', validateBody)
103
+ .reply(200, 'ns:http://specs.openid.net/auth/2.0\nis_valid:false\n');
104
+
105
+ chai
106
+ .request(server)
107
+ .get('/auth/steam')
108
+ .query(SUCCESSFUL_QUERY)
109
+ .redirects(0)
110
+ .end((err, res) => {
111
+ if (err) {
112
+ done(err);
113
+ return;
114
+ }
115
+
116
+ res.should.have.status(401);
117
+ done();
118
+ });
119
+ });
120
+ });
121
+ });
@@ -0,0 +1,41 @@
1
+ import { VALID_NONCE, VALID_OPENID_ENDPOINT } from '../../src';
2
+
3
+ export const RETURN_URL = '/auth/steam';
4
+
5
+ export const query: {
6
+ properties: Record<string, string>;
7
+ get(): Record<string, string>;
8
+ change(change: Record<string, string>): Record<string, string>;
9
+ remove(property: string): Record<string, string>;
10
+ } = {
11
+ /**
12
+ * Valid query properties
13
+ */
14
+ properties: {
15
+ 'openid.mode': 'id_res',
16
+ 'openid.ns': VALID_NONCE,
17
+ 'openid.identity': `https://steamcommunity.com/openid/id/76561197960435530`,
18
+ 'openid.claimed_id': `https://steamcommunity.com/openid/id/76561197960435530`,
19
+ 'openid.return_to': RETURN_URL,
20
+ 'openid.op_endpoint': VALID_OPENID_ENDPOINT,
21
+ 'openid.response_nonce': `${new Date().toJSON()}8df86bac92ad1addaf3735a5aabdc6e2a7`,
22
+ 'openid.assoc_handle': '1234567890',
23
+ 'openid.signed':
24
+ 'signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle',
25
+ 'openid.sig': 'dc6e2a79de2c6aceac495ad5f4c6b6e0bfe30',
26
+ },
27
+
28
+ get() {
29
+ return { ...this.properties };
30
+ },
31
+
32
+ change(change: Record<string, string>) {
33
+ return { ...this.get(), ...change };
34
+ },
35
+
36
+ remove(property: string) {
37
+ const properties = this.get();
38
+ delete properties[property];
39
+ return properties;
40
+ },
41
+ };
@@ -0,0 +1,725 @@
1
+ import qs from 'querystring';
2
+ import sinon from 'sinon';
3
+ import chaiAsPromised from 'chai-as-promised';
4
+ import chai, { expect } from 'chai';
5
+ import { URL } from 'url';
6
+ import {
7
+ SteamOpenIdError,
8
+ SteamOpenIdErrorType,
9
+ SteamOpenIdStrategy,
10
+ SteamOpenIdStrategyOptionsWithoutProfile,
11
+ SteamOpenIdUser,
12
+ VALID_ID_SELECT,
13
+ VALID_NONCE,
14
+ OPENID_QUERY_PROPS,
15
+ VALID_OPENID_ENDPOINT,
16
+ PLAYER_SUMMARY_URL,
17
+ } from '../src';
18
+ import { RETURN_URL, query } from './setup/data';
19
+
20
+ chai.use(chaiAsPromised);
21
+
22
+ describe('SteamOpenIdStrategy Unit Test', () => {
23
+ let strategy: SteamOpenIdStrategy<
24
+ SteamOpenIdStrategyOptionsWithoutProfile,
25
+ SteamOpenIdUser
26
+ >;
27
+
28
+ beforeEach(() => {
29
+ strategy = new SteamOpenIdStrategy({
30
+ apiKey: 'an api key',
31
+ profile: false,
32
+ returnURL: RETURN_URL,
33
+ });
34
+ });
35
+
36
+ describe('authenticate', () => {
37
+ let successStub: sinon.SinonStub;
38
+ let failStub: sinon.SinonStub;
39
+ let errorStub: sinon.SinonStub;
40
+ let redirectStub: sinon.SinonStub;
41
+
42
+ beforeEach(() => {
43
+ strategy.success = () => null;
44
+ strategy.fail = () => null;
45
+ strategy.error = () => null;
46
+ strategy.redirect = () => null;
47
+
48
+ successStub = sinon.stub(strategy, 'success');
49
+ failStub = sinon.stub(strategy, 'fail');
50
+ errorStub = sinon.stub(strategy, 'error');
51
+ redirectStub = sinon.stub(strategy, 'redirect');
52
+ });
53
+
54
+ it('Authenticates user without verify function', async () => {
55
+ const request = {};
56
+ const user = { steamid: '76561197960435530' };
57
+
58
+ const handleRequestStub = sinon
59
+ .stub(strategy, 'handleRequest')
60
+ .resolves(user);
61
+
62
+ await strategy.authenticate(request);
63
+
64
+ expect(handleRequestStub.callCount).equal(1);
65
+ expect(handleRequestStub.calledWithExactly(request)).equal(true);
66
+ expect(successStub.callCount).equal(1);
67
+ expect(successStub.calledWithExactly(user)).equal(true);
68
+ expect(failStub.callCount).equal(0);
69
+ expect(errorStub.callCount).equal(0);
70
+ expect(redirectStub.callCount).equal(0);
71
+ });
72
+
73
+ it('Authenticates and calls verify function', async () => {
74
+ strategy['verify'] = (_, __, user, callback) => {
75
+ callback(null, user);
76
+ };
77
+
78
+ const request = {};
79
+ const user = { steamid: '76561197960435530' };
80
+
81
+ const verifySpy = sinon.spy(strategy, 'verify' as any);
82
+ const handleRequestStub = sinon
83
+ .stub(strategy, 'handleRequest')
84
+ .resolves(user);
85
+
86
+ await strategy.authenticate(request);
87
+
88
+ expect(handleRequestStub.callCount).equal(1);
89
+ expect(handleRequestStub.calledWithExactly(request)).equal(true);
90
+ expect(verifySpy.callCount).equal(1);
91
+ expect(verifySpy.calledWithMatch(request, user.steamid, user)).equal(
92
+ true,
93
+ );
94
+ expect(successStub.callCount).equal(1);
95
+ expect(successStub.calledWithExactly(user)).equal(true);
96
+ expect(failStub.callCount).equal(0);
97
+ expect(errorStub.callCount).equal(0);
98
+ expect(redirectStub.callCount).equal(0);
99
+ });
100
+
101
+ it('Fails to handle request but gets redirected', async () => {
102
+ const request = {};
103
+ const error = new SteamOpenIdError(
104
+ 'Invalid mode',
105
+ SteamOpenIdErrorType.InvalidMode,
106
+ );
107
+ const url = 'redirect url';
108
+
109
+ const isRetryableErrorStub = sinon
110
+ .stub(strategy as any, 'isRetryableError')
111
+ .returns(true);
112
+ const buildRedirectUrlStub = sinon
113
+ .stub(strategy as any, 'buildRedirectUrl')
114
+ .returns(url);
115
+ const handleRequestStub = sinon
116
+ .stub(strategy, 'handleRequest')
117
+ .rejects(error);
118
+
119
+ await strategy.authenticate(request);
120
+
121
+ expect(isRetryableErrorStub.callCount).equal(1);
122
+ expect(isRetryableErrorStub.calledWithExactly(error)).equal(true);
123
+ expect(buildRedirectUrlStub.callCount).equal(1);
124
+ expect(buildRedirectUrlStub.calledWithExactly()).equal(true);
125
+ expect(handleRequestStub.callCount).equal(1);
126
+ expect(handleRequestStub.calledWithExactly(request)).equal(true);
127
+ expect(redirectStub.callCount).equal(1);
128
+ expect(redirectStub.calledWithExactly(url)).equal(true);
129
+ expect(errorStub.callCount).equal(0);
130
+ expect(failStub.callCount).equal(0);
131
+ expect(successStub.callCount).equal(0);
132
+ });
133
+
134
+ it('Fails to handle request and terminates', async () => {
135
+ const request = {};
136
+ const error = new Error('Not retryable');
137
+
138
+ const handleRequestStub = sinon
139
+ .stub(strategy, 'handleRequest')
140
+ .rejects(error);
141
+
142
+ await strategy.authenticate(request);
143
+
144
+ expect(handleRequestStub.callCount).equal(1);
145
+ expect(handleRequestStub.calledWithExactly(request)).equal(true);
146
+ expect(errorStub.callCount).equal(1);
147
+ expect(errorStub.calledWithExactly(error)).equal(true);
148
+ expect(failStub.callCount).equal(0);
149
+ expect(successStub.callCount).equal(0);
150
+ expect(redirectStub.callCount).equal(0);
151
+ });
152
+
153
+ it('Verify function fails', async () => {
154
+ const error = new Error('Verify error');
155
+ strategy['verify'] = (_, __, ___, callback) => {
156
+ callback(error);
157
+ };
158
+
159
+ const request = {};
160
+ const user = { steamid: '76561197960435530' };
161
+
162
+ const verifySpy = sinon.spy(strategy, 'verify' as any);
163
+ const handleRequestStub = sinon
164
+ .stub(strategy, 'handleRequest')
165
+ .resolves(user);
166
+
167
+ await strategy.authenticate(request);
168
+
169
+ expect(handleRequestStub.callCount).equal(1);
170
+ expect(handleRequestStub.calledWithExactly(request)).equal(true);
171
+ expect(verifySpy.callCount).equal(1);
172
+ expect(verifySpy.calledWithMatch(request, user.steamid, user)).equal(
173
+ true,
174
+ );
175
+ expect(errorStub.callCount).equal(1);
176
+ expect(errorStub.calledWithExactly(error)).equal(true);
177
+ expect(failStub.callCount).equal(0);
178
+ expect(successStub.callCount).equal(0);
179
+ expect(redirectStub.callCount).equal(0);
180
+ });
181
+
182
+ it('Verify returns no user', async () => {
183
+ strategy['verify'] = (_, __, ___, callback) => {
184
+ callback(null, null);
185
+ };
186
+
187
+ const request = {};
188
+ const user = { steamid: '76561197960435530' };
189
+
190
+ const verifySpy = sinon.spy(strategy, 'verify' as any);
191
+ const handleRequestStub = sinon
192
+ .stub(strategy, 'handleRequest')
193
+ .resolves(user);
194
+
195
+ await strategy.authenticate(request);
196
+
197
+ expect(handleRequestStub.callCount).equal(1);
198
+ expect(handleRequestStub.calledWithExactly(request)).equal(true);
199
+ expect(verifySpy.callCount).equal(1);
200
+ expect(verifySpy.calledWithMatch(request, user.steamid, user)).equal(
201
+ true,
202
+ );
203
+ expect(errorStub.callCount).equal(1);
204
+ expect(failStub.callCount).equal(0);
205
+ expect(successStub.callCount).equal(0);
206
+ expect(redirectStub.callCount).equal(0);
207
+ });
208
+ });
209
+
210
+ describe('handleRequest', () => {
211
+ const request = {};
212
+ const query = {};
213
+
214
+ let getQueryStub: sinon.SinonStub;
215
+ let hasAuthQueryStub: sinon.SinonStub;
216
+ let isQueryValidStub: sinon.SinonStub;
217
+ let validateAgainstSteamStub: sinon.SinonStub;
218
+
219
+ beforeEach(() => {
220
+ getQueryStub = sinon.stub(strategy as any, 'getQuery').returns(query);
221
+ hasAuthQueryStub = sinon.stub(strategy as any, 'hasAuthQuery');
222
+ isQueryValidStub = sinon.stub(strategy as any, 'isQueryValid');
223
+ validateAgainstSteamStub = sinon.stub(
224
+ strategy as any,
225
+ 'validateAgainstSteam',
226
+ );
227
+ });
228
+
229
+ afterEach(() => {
230
+ expect(getQueryStub.callCount).equal(1);
231
+ expect(getQueryStub.calledWithExactly(request)).equal(true);
232
+ expect(hasAuthQueryStub.callCount).equal(1);
233
+ expect(hasAuthQueryStub.calledWithExactly(query)).equal(true);
234
+ });
235
+
236
+ it('Handles request correctly', async () => {
237
+ hasAuthQueryStub.returns(true);
238
+ isQueryValidStub.returns(true);
239
+ validateAgainstSteamStub.resolves(true);
240
+
241
+ const steamid = '76561197960435530';
242
+ const user = { steamid: '76561197960435530' };
243
+
244
+ const getSteamIdStub = sinon
245
+ .stub(strategy as any, 'getSteamId')
246
+ .returns(steamid);
247
+ const getUserStub = sinon.stub(strategy as any, 'getUser').resolves(user);
248
+
249
+ expect(await strategy.handleRequest(request)).equal(user);
250
+ expect(getSteamIdStub.callCount).equal(1);
251
+ expect(getSteamIdStub.calledWithExactly(query)).equal(true);
252
+ expect(getUserStub.callCount).equal(1);
253
+ expect(getUserStub.calledWithExactly(steamid)).equal(true);
254
+ expect(isQueryValidStub.callCount).equal(1);
255
+ expect(isQueryValidStub.calledWithExactly(query)).equal(true);
256
+ expect(validateAgainstSteamStub.callCount).equal(1);
257
+ expect(validateAgainstSteamStub.calledWithExactly(query)).equal(true);
258
+ });
259
+
260
+ it('Does not have correct mode', async () => {
261
+ hasAuthQueryStub.returns(false);
262
+
263
+ let err: any;
264
+ try {
265
+ await strategy.handleRequest(request);
266
+ } catch (e) {
267
+ err = e;
268
+ }
269
+
270
+ expect(err).to.be.instanceOf(SteamOpenIdError);
271
+ expect(err).to.have.property('code', SteamOpenIdErrorType.InvalidMode);
272
+ });
273
+
274
+ it('Query is invalid', async () => {
275
+ hasAuthQueryStub.returns(true);
276
+ isQueryValidStub.returns(false);
277
+
278
+ let err: any;
279
+ try {
280
+ await strategy.handleRequest(request);
281
+ } catch (e) {
282
+ err = e;
283
+ }
284
+
285
+ expect(err).to.be.instanceOf(SteamOpenIdError);
286
+ expect(err).to.have.property('code', SteamOpenIdErrorType.InvalidQuery);
287
+ expect(isQueryValidStub.callCount).equal(1);
288
+ expect(isQueryValidStub.calledWithExactly(query)).equal(true);
289
+ });
290
+
291
+ it('Steam rejects this authentication request', async () => {
292
+ hasAuthQueryStub.returns(true);
293
+ isQueryValidStub.returns(true);
294
+ validateAgainstSteamStub.resolves(false);
295
+
296
+ let err: any;
297
+ try {
298
+ await strategy.handleRequest(request);
299
+ } catch (e) {
300
+ err = e;
301
+ }
302
+
303
+ expect(err).to.be.instanceOf(SteamOpenIdError);
304
+ expect(err).to.have.property('code', SteamOpenIdErrorType.Unauthorized);
305
+ expect(isQueryValidStub.callCount).equal(1);
306
+ expect(isQueryValidStub.calledWithExactly(query)).equal(true);
307
+ expect(validateAgainstSteamStub.callCount).equal(1);
308
+ expect(validateAgainstSteamStub.calledWithExactly(query)).equal(true);
309
+ });
310
+ });
311
+
312
+ describe('isRetryableError', () => {
313
+ it('Is retriable error', () => {
314
+ const error = new SteamOpenIdError(
315
+ 'Message',
316
+ SteamOpenIdErrorType.InvalidMode,
317
+ );
318
+ expect(strategy['isRetryableError'](error)).equal(true);
319
+ });
320
+
321
+ it('Not an instance of SteamOpenIdError', () => {
322
+ const error = new Error('Message');
323
+ expect(strategy['isRetryableError'](error)).equal(false);
324
+ });
325
+
326
+ it('Wrong error code', () => {
327
+ const error = new SteamOpenIdError(
328
+ 'Message',
329
+ SteamOpenIdErrorType.InvalidSteamId,
330
+ );
331
+ expect(strategy['isRetryableError'](error)).equal(false);
332
+ });
333
+ });
334
+
335
+ describe('getQuery', () => {
336
+ it('Returns query from request', () => {
337
+ const request = { query: {} };
338
+ expect(strategy['getQuery'](request)).equal(request.query);
339
+ });
340
+
341
+ it('Fails because query is not present', () => {
342
+ const request = {};
343
+ expect(strategy['getQuery'].bind(strategy, request)).to.throw(Error);
344
+ });
345
+
346
+ it('Fails because query is not an object', () => {
347
+ const request = { query: 'string' };
348
+ expect(strategy['getQuery'].bind(strategy, request)).to.throw(Error);
349
+ });
350
+ });
351
+
352
+ describe('hasAuthQuery', () => {
353
+ it('Is an auth query', () => {
354
+ const query = { 'openid.mode': 'id_res' };
355
+ expect(strategy['hasAuthQuery'](query)).equal(true);
356
+ });
357
+
358
+ it('Mode is not present', () => {
359
+ const query = {};
360
+ expect(strategy['hasAuthQuery'](query)).equal(false);
361
+ });
362
+
363
+ it('Wrong mode', () => {
364
+ const query = { 'openid.mode': 'checkid_setup' };
365
+ expect(strategy['hasAuthQuery'](query)).equal(false);
366
+ });
367
+ });
368
+
369
+ describe('buildRedirectUrl', () => {
370
+ it('Builds a redirect url to steam', () => {
371
+ const urlString = strategy['buildRedirectUrl']();
372
+ const url = new URL(urlString);
373
+
374
+ expect(url.origin).equal('https://steamcommunity.com');
375
+ expect(url.pathname).equal('/openid/login');
376
+
377
+ const VALID_PARAMS: Record<string, string> = {
378
+ 'openid.mode': 'checkid_setup',
379
+ 'openid.ns': VALID_NONCE,
380
+ 'openid.identity': VALID_ID_SELECT,
381
+ 'openid.claimed_id': VALID_ID_SELECT,
382
+ 'openid.return_to': RETURN_URL,
383
+ };
384
+
385
+ url.searchParams.forEach((value, name) => {
386
+ if (VALID_PARAMS[name] !== value) {
387
+ throw new Error(`Unknown param: ${name} = ${value}.`);
388
+ }
389
+ });
390
+ });
391
+ });
392
+
393
+ describe('isQueryValid', () => {
394
+ let isValidIdentityStub: sinon.SinonStub;
395
+
396
+ beforeEach(() => {
397
+ isValidIdentityStub = sinon.stub(strategy as any, 'isValidIdentity');
398
+ });
399
+
400
+ it('Is valid query', () => {
401
+ isValidIdentityStub.returns(true);
402
+ expect(strategy['isQueryValid'](query.get())).equal(true);
403
+ expect(isValidIdentityStub.callCount).equal(1);
404
+ expect(
405
+ isValidIdentityStub.calledWithExactly(
406
+ query.properties['openid.claimed_id'],
407
+ ),
408
+ );
409
+ });
410
+
411
+ it('Property is missing', () => {
412
+ for (const property of OPENID_QUERY_PROPS) {
413
+ expect(strategy['isQueryValid'](query.remove(property))).equal(false);
414
+ }
415
+ });
416
+
417
+ it('Invalid nonce', () => {
418
+ expect(
419
+ strategy['isQueryValid'](query.change({ 'openid.ns': 'content' })),
420
+ ).equal(false);
421
+ });
422
+
423
+ it('Invalid endpoint', () => {
424
+ expect(
425
+ strategy['isQueryValid'](
426
+ query.change({ 'openid.op_endpoint': 'content' }),
427
+ ),
428
+ ).equal(false);
429
+ });
430
+
431
+ it("ClaimedId and Identity don' match", () => {
432
+ expect(
433
+ strategy['isQueryValid'](
434
+ query.change({ 'openid.claimed_id': 'content' }),
435
+ ),
436
+ ).equal(false);
437
+ expect(isValidIdentityStub.callCount).equal(0);
438
+ });
439
+
440
+ it('ClaimedId is invalid', () => {
441
+ isValidIdentityStub.returns(false);
442
+ const claimedIdValue = 'content';
443
+ expect(
444
+ strategy['isQueryValid'](
445
+ query.change({
446
+ 'openid.claimed_id': claimedIdValue,
447
+ 'openid.identity': claimedIdValue,
448
+ }),
449
+ ),
450
+ ).equal(false);
451
+ expect(isValidIdentityStub.callCount).equal(1);
452
+ expect(isValidIdentityStub.calledWithExactly(claimedIdValue));
453
+ });
454
+
455
+ it('Return does not match', () => {
456
+ isValidIdentityStub.returns(true);
457
+ expect(
458
+ strategy['isQueryValid'](
459
+ query.change({ 'openid.return_to': 'content' }),
460
+ ),
461
+ ).equal(false);
462
+ expect(isValidIdentityStub.callCount).equal(1);
463
+ expect(
464
+ isValidIdentityStub.calledWithExactly(
465
+ query.properties['openid.claimed_id'],
466
+ ),
467
+ );
468
+ });
469
+ });
470
+
471
+ describe('isValidIdentity', () => {
472
+ it('Is valid identity', () => {
473
+ const identity = 'https://steamcommunity.com/openid/id/76561197960435530';
474
+ expect(strategy['isValidIdentity'](identity)).equal(true);
475
+ });
476
+
477
+ it('Is not a string', () => {
478
+ const identity = null;
479
+ expect(strategy['isValidIdentity'](identity)).equal(false);
480
+ });
481
+
482
+ it('Does not match regex', () => {
483
+ const identity =
484
+ 'https://localhost:3000?x=https://steamcommunity.com/openid/id/76561197960435530';
485
+ expect(strategy['isValidIdentity'](identity)).equal(false);
486
+ });
487
+ });
488
+
489
+ describe('validateAgainstSteam', () => {
490
+ let axiosPostStub: sinon.SinonStub;
491
+ let getOpenIdValidationRequestBodyStub: sinon.SinonStub;
492
+ let isSteamResponseValidStub: sinon.SinonStub;
493
+
494
+ const query = {};
495
+ const data = {};
496
+ const body = 'body';
497
+
498
+ beforeEach(() => {
499
+ axiosPostStub = sinon.stub(strategy['axios'], 'post');
500
+ getOpenIdValidationRequestBodyStub = sinon
501
+ .stub(strategy as any, 'getOpenIdValidationRequestBody')
502
+ .returns(body);
503
+ isSteamResponseValidStub = sinon.stub(
504
+ strategy as any,
505
+ 'isSteamResponseValid',
506
+ );
507
+ });
508
+
509
+ afterEach(() => {
510
+ expect(getOpenIdValidationRequestBodyStub.callCount).equal(1);
511
+ expect(getOpenIdValidationRequestBodyStub.calledWithExactly(query)).equal(
512
+ true,
513
+ );
514
+ expect(axiosPostStub.callCount).equal(1);
515
+ expect(
516
+ axiosPostStub.calledWithExactly(VALID_OPENID_ENDPOINT, body, {
517
+ maxRedirects: 0,
518
+ headers: {
519
+ 'Content-Type': 'application/x-www-form-urlencoded',
520
+ },
521
+ }),
522
+ );
523
+ });
524
+
525
+ it('Validates successfully', async () => {
526
+ axiosPostStub.resolves({ status: 200, data });
527
+ isSteamResponseValidStub.returns(true);
528
+
529
+ expect(await strategy['validateAgainstSteam'](query as any)).equal(true);
530
+ expect(isSteamResponseValidStub.callCount).equal(1);
531
+ expect(isSteamResponseValidStub.calledWith(data)).equal(true);
532
+ });
533
+
534
+ it('Wrong status code', async () => {
535
+ axiosPostStub.resolves({ status: 100, data });
536
+
537
+ expect(await strategy['validateAgainstSteam'](query as any)).equal(false);
538
+ expect(isSteamResponseValidStub.callCount).equal(0);
539
+ });
540
+
541
+ it('Response is invalid', async () => {
542
+ axiosPostStub.resolves({ status: 200, data });
543
+ isSteamResponseValidStub.returns(false);
544
+
545
+ expect(await strategy['validateAgainstSteam'](query as any)).equal(false);
546
+ expect(isSteamResponseValidStub.callCount).equal(1);
547
+ expect(isSteamResponseValidStub.calledWith(data)).equal(true);
548
+ });
549
+
550
+ it('Axios rejects', async () => {
551
+ axiosPostStub.rejects(new Error('Malformed.'));
552
+
553
+ expect(await strategy['validateAgainstSteam'](query as any)).equal(false);
554
+ expect(isSteamResponseValidStub.callCount).equal(0);
555
+ });
556
+ });
557
+
558
+ describe('getOpenIdValidationRequestBody', () => {
559
+ it('Stringifies query', () => {
560
+ const query = {
561
+ 'openid.op_endpoint': VALID_OPENID_ENDPOINT,
562
+ 'openid.mode': 'id_res',
563
+ };
564
+ expect(strategy['getOpenIdValidationRequestBody'](query as any)).equal(
565
+ qs.stringify({ ...query, 'openid.mode': 'check_authentication' }),
566
+ );
567
+ });
568
+ });
569
+
570
+ it('isSteamResponseValid', () => {
571
+ describe('Is valid steam response', () => {
572
+ const response = `ns:${VALID_NONCE}\nis_valid:true\n`;
573
+ expect(strategy['isSteamResponseValid'](response)).equal(true);
574
+ });
575
+
576
+ describe('Response is not a string', () => {
577
+ const response = {};
578
+ expect(strategy['isSteamResponseValid'](response)).equal(false);
579
+ });
580
+
581
+ describe('Response does not match', () => {
582
+ const response = `ns:${VALID_NONCE}\nis_valid:true`;
583
+ expect(strategy['isSteamResponseValid'](response)).equal(false);
584
+ });
585
+
586
+ describe('Invalid nonce', () => {
587
+ const response = `ns:xddd\nis_valid:true\n`;
588
+ expect(strategy['isSteamResponseValid'](response)).equal(false);
589
+ });
590
+
591
+ describe('Marked invalid', () => {
592
+ const response = `ns:${VALID_NONCE}\nis_valid:false\n`;
593
+ expect(strategy['isSteamResponseValid'](response)).equal(false);
594
+ });
595
+
596
+ describe('No match', () => {
597
+ const response = `invalid`;
598
+ expect(strategy['isSteamResponseValid'](response)).equal(false);
599
+ });
600
+ });
601
+
602
+ describe('getSteamId', () => {
603
+ it('Retrieves steamid from claimed_id property', () => {
604
+ const steamid = '76561197960435530';
605
+ const q = {
606
+ 'openid.claimed_id': `https://steamcommunity.com/openid/id/${steamid}`,
607
+ };
608
+ expect(strategy['getSteamId'](q as any)).equal(steamid);
609
+ });
610
+ });
611
+
612
+ describe('getUser', () => {
613
+ let fetchPlayerSummaryStub: sinon.SinonStub;
614
+
615
+ beforeEach(() => {
616
+ fetchPlayerSummaryStub = sinon.stub(
617
+ strategy as any,
618
+ 'fetchPlayerSummary',
619
+ );
620
+ });
621
+
622
+ const steamid = '76561197960435530';
623
+
624
+ it('Gets user without profile', async () => {
625
+ expect(await strategy['getUser'](steamid)).deep.equal({ steamid });
626
+ expect(fetchPlayerSummaryStub.callCount).equal(0);
627
+ });
628
+
629
+ it('Gets user with profile', async () => {
630
+ // @ts-expect-error
631
+ strategy['profile'] = true;
632
+
633
+ const profile = { steamid, avatar: 'url' };
634
+ fetchPlayerSummaryStub.resolves(profile);
635
+
636
+ expect(await strategy['getUser'](steamid)).equal(profile);
637
+ expect(fetchPlayerSummaryStub.callCount).equal(1);
638
+ expect(fetchPlayerSummaryStub.calledWithExactly(steamid)).equal(true);
639
+ });
640
+ });
641
+
642
+ describe('fetchPlayerSummary', async () => {
643
+ let axiosGetStub: sinon.SinonStub;
644
+
645
+ const steamid = '76561197960435530';
646
+ const apiKey = 'xxx';
647
+
648
+ beforeEach(() => {
649
+ strategy = new SteamOpenIdStrategy({
650
+ apiKey,
651
+ profile: true,
652
+ returnURL: RETURN_URL,
653
+ });
654
+
655
+ axiosGetStub = sinon.stub(strategy['axios'], 'get');
656
+ });
657
+
658
+ afterEach(() => {
659
+ expect(axiosGetStub.callCount).equal(1);
660
+ expect(
661
+ axiosGetStub.calledWithExactly(
662
+ `${PLAYER_SUMMARY_URL}/?${qs.stringify({
663
+ steamids: steamid,
664
+ key: apiKey,
665
+ })}`,
666
+ ),
667
+ ).equal(true);
668
+ });
669
+
670
+ it('Successfully fetches valid user profile', async () => {
671
+ const profile = { steamid };
672
+ axiosGetStub.resolves({ data: { response: { players: [profile] } } });
673
+
674
+ expect(await strategy['fetchPlayerSummary'](steamid)).equal(profile);
675
+ });
676
+
677
+ it('Malformed response, players is not an array', async () => {
678
+ axiosGetStub.resolves({ data: { response: { players: null } } });
679
+
680
+ expect(strategy['fetchPlayerSummary'](steamid)).to.rejectedWith(Error);
681
+ });
682
+
683
+ it('Malformed response, response is missing', async () => {
684
+ axiosGetStub.resolves({ data: {} });
685
+
686
+ expect(strategy['fetchPlayerSummary'](steamid)).to.rejectedWith(Error);
687
+ });
688
+
689
+ it('Malformed response, data is missing', async () => {
690
+ axiosGetStub.resolves({});
691
+
692
+ expect(strategy['fetchPlayerSummary'](steamid)).to.rejectedWith(Error);
693
+ });
694
+
695
+ it('No user returned', async () => {
696
+ axiosGetStub.resolves({ data: { response: { players: [] } } });
697
+
698
+ let err: any;
699
+ try {
700
+ await strategy['fetchPlayerSummary'](steamid);
701
+ } catch (e) {
702
+ err = e;
703
+ }
704
+
705
+ expect(err).instanceOf(SteamOpenIdError);
706
+ expect(err).to.have.property('code', SteamOpenIdErrorType.InvalidSteamId);
707
+ });
708
+
709
+ it('Wrong user returned', async () => {
710
+ axiosGetStub.resolves({
711
+ data: { response: { players: [{ steamid: 'x' }] } },
712
+ });
713
+
714
+ let err: any;
715
+ try {
716
+ await strategy['fetchPlayerSummary'](steamid);
717
+ } catch (e) {
718
+ err = e;
719
+ }
720
+
721
+ expect(err).instanceOf(SteamOpenIdError);
722
+ expect(err).to.have.property('code', SteamOpenIdErrorType.InvalidSteamId);
723
+ });
724
+ });
725
+ });
package/tsconfig.json CHANGED
@@ -37,5 +37,5 @@
37
37
  "skipDefaultLibCheck": true,
38
38
  "skipLibCheck": false
39
39
  },
40
- "exclude": ["./sample", "./dist"]
40
+ "exclude": ["./sample", "./dist", "./test"]
41
41
  }