@mnbroatch/boardgame.io 0.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/LICENSE +21 -0
- package/README.md +102 -0
- package/ai/package.json +7 -0
- package/client/package.json +7 -0
- package/core/package.json +7 -0
- package/debug/package.json +7 -0
- package/dist/boardgameio.es.js +14238 -0
- package/dist/boardgameio.js +14277 -0
- package/dist/boardgameio.min.js +16 -0
- package/dist/cjs/Debug-9d141c06.js +9586 -0
- package/dist/cjs/ai-e0e8a768.js +377 -0
- package/dist/cjs/ai.js +20 -0
- package/dist/cjs/client-76dec77b.js +258 -0
- package/dist/cjs/client-a22d7500.js +524 -0
- package/dist/cjs/client.js +26 -0
- package/dist/cjs/core.js +52 -0
- package/dist/cjs/debug.js +18 -0
- package/dist/cjs/filter-player-view-bb02e2f6.js +89 -0
- package/dist/cjs/initialize-267fcd69.js +61 -0
- package/dist/cjs/internal.js +25 -0
- package/dist/cjs/master-2904879d.js +320 -0
- package/dist/cjs/master.js +18 -0
- package/dist/cjs/multiplayer.js +23 -0
- package/dist/cjs/plugin-random-7425844d.js +229 -0
- package/dist/cjs/plugins.js +59 -0
- package/dist/cjs/react-native.js +182 -0
- package/dist/cjs/react.js +727 -0
- package/dist/cjs/reducer-16eec232.js +1203 -0
- package/dist/cjs/server.js +4087 -0
- package/dist/cjs/socketio-7a0837eb.js +478 -0
- package/dist/cjs/testing.js +30 -0
- package/dist/cjs/transport-b1874dfa.js +37 -0
- package/dist/cjs/turn-order-b2ff8740.js +1136 -0
- package/dist/cjs/util-fcfd8fb8.js +140 -0
- package/dist/esm/Debug-0141fe2d.js +9577 -0
- package/dist/esm/ai-5c06e761.js +371 -0
- package/dist/esm/ai.js +8 -0
- package/dist/esm/client-2e653027.js +522 -0
- package/dist/esm/client-5f57c3f2.js +255 -0
- package/dist/esm/client.js +16 -0
- package/dist/esm/core.js +40 -0
- package/dist/esm/debug.js +10 -0
- package/dist/esm/filter-player-view-2c6cc96f.js +87 -0
- package/dist/esm/initialize-11d626ca.js +59 -0
- package/dist/esm/internal.js +10 -0
- package/dist/esm/master-fa8f2e43.js +318 -0
- package/dist/esm/master.js +10 -0
- package/dist/esm/multiplayer.js +14 -0
- package/dist/esm/plugin-random-087f861e.js +226 -0
- package/dist/esm/plugins.js +55 -0
- package/dist/esm/react-native.js +173 -0
- package/dist/esm/react.js +716 -0
- package/dist/esm/reducer-c46da7e5.js +1198 -0
- package/dist/esm/socketio-c22ffa65.js +455 -0
- package/dist/esm/testing.js +26 -0
- package/dist/esm/transport-ce07b771.js +35 -0
- package/dist/esm/turn-order-376d315e.js +1091 -0
- package/dist/esm/util-b6147cef.js +135 -0
- package/dist/types/packages/ai.d.ts +5 -0
- package/dist/types/packages/client.d.ts +3 -0
- package/dist/types/packages/core.d.ts +5 -0
- package/dist/types/packages/debug.d.ts +2 -0
- package/dist/types/packages/internal.d.ts +8 -0
- package/dist/types/packages/master.d.ts +2 -0
- package/dist/types/packages/multiplayer.d.ts +3 -0
- package/dist/types/packages/plugins.d.ts +3 -0
- package/dist/types/packages/react-native.d.ts +2 -0
- package/dist/types/packages/react.d.ts +3 -0
- package/dist/types/packages/server.d.ts +6 -0
- package/dist/types/packages/testing.d.ts +1 -0
- package/dist/types/src/ai/ai.d.ts +53 -0
- package/dist/types/src/ai/ai.test.d.ts +1 -0
- package/dist/types/src/ai/bot.d.ts +40 -0
- package/dist/types/src/ai/mcts-bot.d.ts +60 -0
- package/dist/types/src/ai/random-bot.d.ts +27 -0
- package/dist/types/src/client/client.d.ts +104 -0
- package/dist/types/src/client/client.test.d.ts +1 -0
- package/dist/types/src/client/debug/tests/debug.test.d.ts +1 -0
- package/dist/types/src/client/manager.d.ts +61 -0
- package/dist/types/src/client/react.d.ts +75 -0
- package/dist/types/src/client/react.ssr.test.d.ts +4 -0
- package/dist/types/src/client/react.test.d.ts +1 -0
- package/dist/types/src/client/transport/dummy.d.ts +18 -0
- package/dist/types/src/client/transport/local.d.ts +59 -0
- package/dist/types/src/client/transport/local.test.d.ts +1 -0
- package/dist/types/src/client/transport/socketio.d.ts +45 -0
- package/dist/types/src/client/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/client/transport/transport.d.ts +50 -0
- package/dist/types/src/client/transport/transport.test.d.ts +1 -0
- package/dist/types/src/core/action-creators.d.ts +144 -0
- package/dist/types/src/core/action-types.d.ts +10 -0
- package/dist/types/src/core/backwards-compatibility.d.ts +12 -0
- package/dist/types/src/core/constants.d.ts +6 -0
- package/dist/types/src/core/errors.d.ts +15 -0
- package/dist/types/src/core/flow.d.ts +28 -0
- package/dist/types/src/core/flow.test.d.ts +1 -0
- package/dist/types/src/core/game-methods.d.ts +9 -0
- package/dist/types/src/core/game.d.ts +26 -0
- package/dist/types/src/core/game.test.d.ts +1 -0
- package/dist/types/src/core/initialize.d.ts +9 -0
- package/dist/types/src/core/logger.d.ts +2 -0
- package/dist/types/src/core/player-view.d.ts +7 -0
- package/dist/types/src/core/player-view.test.d.ts +1 -0
- package/dist/types/src/core/reducer.d.ts +155 -0
- package/dist/types/src/core/reducer.test.d.ts +1 -0
- package/dist/types/src/core/turn-order.d.ts +179 -0
- package/dist/types/src/core/turn-order.test.d.ts +8 -0
- package/dist/types/src/lobby/client.d.ts +194 -0
- package/dist/types/src/lobby/client.test.d.ts +1 -0
- package/dist/types/src/lobby/connection.d.ts +44 -0
- package/dist/types/src/lobby/connection.test.d.ts +1 -0
- package/dist/types/src/lobby/create-match-form.d.ts +26 -0
- package/dist/types/src/lobby/login-form.d.ts +23 -0
- package/dist/types/src/lobby/match-instance.d.ts +31 -0
- package/dist/types/src/lobby/react.d.ts +113 -0
- package/dist/types/src/lobby/react.ssr.test.d.ts +4 -0
- package/dist/types/src/lobby/react.test.d.ts +1 -0
- package/dist/types/src/master/filter-player-view.d.ts +96 -0
- package/dist/types/src/master/filter-player-view.test.d.ts +1 -0
- package/dist/types/src/master/master.d.ts +94 -0
- package/dist/types/src/master/master.test.d.ts +1 -0
- package/dist/types/src/plugins/events/events.d.ts +54 -0
- package/dist/types/src/plugins/events/events.test.d.ts +1 -0
- package/dist/types/src/plugins/main.d.ts +75 -0
- package/dist/types/src/plugins/main.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-events.d.ts +5 -0
- package/dist/types/src/plugins/plugin-immer.d.ts +7 -0
- package/dist/types/src/plugins/plugin-immer.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-log.d.ts +14 -0
- package/dist/types/src/plugins/plugin-log.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-player.d.ts +29 -0
- package/dist/types/src/plugins/plugin-player.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-random.d.ts +4 -0
- package/dist/types/src/plugins/plugin-serializable.d.ts +7 -0
- package/dist/types/src/plugins/plugin-serializable.test.d.ts +1 -0
- package/dist/types/src/plugins/random/random.alea.d.ts +19 -0
- package/dist/types/src/plugins/random/random.d.ts +54 -0
- package/dist/types/src/plugins/random/random.test.d.ts +1 -0
- package/dist/types/src/server/api.d.ts +13 -0
- package/dist/types/src/server/api.test.d.ts +1 -0
- package/dist/types/src/server/auth.d.ts +38 -0
- package/dist/types/src/server/auth.test.d.ts +1 -0
- package/dist/types/src/server/cors.d.ts +4 -0
- package/dist/types/src/server/cors.test.d.ts +1 -0
- package/dist/types/src/server/db/base.d.ts +192 -0
- package/dist/types/src/server/db/flatfile.d.ts +44 -0
- package/dist/types/src/server/db/flatfile.test.d.ts +1 -0
- package/dist/types/src/server/db/index.d.ts +4 -0
- package/dist/types/src/server/db/index.test.d.ts +1 -0
- package/dist/types/src/server/db/inmemory.d.ts +43 -0
- package/dist/types/src/server/db/inmemory.test.d.ts +1 -0
- package/dist/types/src/server/db/localstorage.d.ts +7 -0
- package/dist/types/src/server/db/localstorage.test.d.ts +1 -0
- package/dist/types/src/server/index.d.ts +68 -0
- package/dist/types/src/server/index.test.d.ts +1 -0
- package/dist/types/src/server/transport/pubsub/generic-pub-sub.d.ts +6 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.d.ts +7 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio-simultaneous.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio.d.ts +65 -0
- package/dist/types/src/server/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/server/util.d.ts +35 -0
- package/dist/types/src/testing/mock-random.d.ts +15 -0
- package/dist/types/src/testing/mock-random.test.d.ts +1 -0
- package/dist/types/src/types.d.ts +387 -0
- package/internal/package.json +7 -0
- package/master/package.json +7 -0
- package/multiplayer/package.json +7 -0
- package/package.json +211 -0
- package/plugins/package.json +7 -0
- package/react/package.json +7 -0
- package/react-native/package.json +7 -0
- package/server/package.json +6 -0
- package/src/ai/ai.test.ts +433 -0
- package/src/ai/ai.ts +84 -0
- package/src/ai/bot.ts +122 -0
- package/src/ai/mcts-bot.ts +331 -0
- package/src/ai/random-bot.ts +20 -0
- package/src/client/client.test.ts +993 -0
- package/src/client/client.ts +588 -0
- package/src/client/debug/Debug.svelte +239 -0
- package/src/client/debug/Menu.svelte +65 -0
- package/src/client/debug/ai/AI.svelte +215 -0
- package/src/client/debug/ai/Options.svelte +48 -0
- package/src/client/debug/info/Info.svelte +22 -0
- package/src/client/debug/info/Item.svelte +24 -0
- package/src/client/debug/log/Log.svelte +157 -0
- package/src/client/debug/log/LogEvent.svelte +149 -0
- package/src/client/debug/log/LogMetadata.svelte +7 -0
- package/src/client/debug/log/PhaseMarker.svelte +27 -0
- package/src/client/debug/log/TurnMarker.svelte +23 -0
- package/src/client/debug/main/ClientSwitcher.svelte +59 -0
- package/src/client/debug/main/Controls.svelte +58 -0
- package/src/client/debug/main/Hotkey.svelte +84 -0
- package/src/client/debug/main/InteractiveFunction.svelte +85 -0
- package/src/client/debug/main/Main.svelte +121 -0
- package/src/client/debug/main/Move.svelte +68 -0
- package/src/client/debug/main/PlayerInfo.svelte +70 -0
- package/src/client/debug/mcts/Action.svelte +22 -0
- package/src/client/debug/mcts/MCTS.svelte +78 -0
- package/src/client/debug/mcts/Table.svelte +98 -0
- package/src/client/debug/tests/JSONTree.mock.svelte +3 -0
- package/src/client/debug/tests/debug.test.ts +183 -0
- package/src/client/debug/utils/shortcuts.js +50 -0
- package/src/client/debug/utils/shortcuts.test.js +49 -0
- package/src/client/manager.ts +177 -0
- package/src/client/react-native.js +136 -0
- package/src/client/react-native.test.js +229 -0
- package/src/client/react.ssr.test.tsx +24 -0
- package/src/client/react.test.tsx +213 -0
- package/src/client/react.tsx +192 -0
- package/src/client/transport/dummy.ts +19 -0
- package/src/client/transport/local.test.ts +353 -0
- package/src/client/transport/local.ts +230 -0
- package/src/client/transport/socketio.test.ts +328 -0
- package/src/client/transport/socketio.ts +210 -0
- package/src/client/transport/transport.test.ts +27 -0
- package/src/client/transport/transport.ts +95 -0
- package/src/core/action-creators.ts +159 -0
- package/src/core/action-types.ts +18 -0
- package/src/core/backwards-compatibility.ts +23 -0
- package/src/core/constants.ts +6 -0
- package/src/core/errors.ts +35 -0
- package/src/core/flow.test.ts +2433 -0
- package/src/core/flow.ts +897 -0
- package/src/core/game-methods.ts +9 -0
- package/src/core/game.test.ts +286 -0
- package/src/core/game.ts +114 -0
- package/src/core/initialize.ts +77 -0
- package/src/core/logger.test.js +90 -0
- package/src/core/logger.ts +18 -0
- package/src/core/player-view.test.ts +50 -0
- package/src/core/player-view.ts +39 -0
- package/src/core/reducer.test.ts +991 -0
- package/src/core/reducer.ts +532 -0
- package/src/core/turn-order.test.ts +1123 -0
- package/src/core/turn-order.ts +473 -0
- package/src/lobby/client.test.ts +385 -0
- package/src/lobby/client.ts +358 -0
- package/src/lobby/connection.test.ts +207 -0
- package/src/lobby/connection.ts +162 -0
- package/src/lobby/create-match-form.tsx +122 -0
- package/src/lobby/login-form.tsx +75 -0
- package/src/lobby/match-instance.tsx +135 -0
- package/src/lobby/react.ssr.test.tsx +22 -0
- package/src/lobby/react.test.tsx +594 -0
- package/src/lobby/react.tsx +402 -0
- package/src/master/filter-player-view.test.ts +381 -0
- package/src/master/filter-player-view.ts +102 -0
- package/src/master/master.test.ts +1068 -0
- package/src/master/master.ts +492 -0
- package/src/plugins/events/events.test.ts +108 -0
- package/src/plugins/events/events.ts +209 -0
- package/src/plugins/main.test.ts +411 -0
- package/src/plugins/main.ts +314 -0
- package/src/plugins/plugin-events.ts +40 -0
- package/src/plugins/plugin-immer.test.ts +86 -0
- package/src/plugins/plugin-immer.ts +37 -0
- package/src/plugins/plugin-log.test.ts +37 -0
- package/src/plugins/plugin-log.ts +40 -0
- package/src/plugins/plugin-player.test.ts +172 -0
- package/src/plugins/plugin-player.ts +100 -0
- package/src/plugins/plugin-random.ts +40 -0
- package/src/plugins/plugin-serializable.test.ts +40 -0
- package/src/plugins/plugin-serializable.ts +55 -0
- package/src/plugins/random/random.alea.ts +109 -0
- package/src/plugins/random/random.test.ts +167 -0
- package/src/plugins/random/random.ts +198 -0
- package/src/server/api.test.ts +1699 -0
- package/src/server/api.ts +527 -0
- package/src/server/auth.test.ts +275 -0
- package/src/server/auth.ts +89 -0
- package/src/server/cors.test.ts +121 -0
- package/src/server/cors.ts +7 -0
- package/src/server/db/base.ts +296 -0
- package/src/server/db/flatfile.test.ts +221 -0
- package/src/server/db/flatfile.ts +228 -0
- package/src/server/db/index.test.ts +8 -0
- package/src/server/db/index.ts +12 -0
- package/src/server/db/inmemory.test.ts +143 -0
- package/src/server/db/inmemory.ts +143 -0
- package/src/server/db/localstorage.test.ts +73 -0
- package/src/server/db/localstorage.ts +44 -0
- package/src/server/index.test.ts +265 -0
- package/src/server/index.ts +175 -0
- package/src/server/transport/pubsub/generic-pub-sub.ts +11 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.test.ts +47 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.ts +28 -0
- package/src/server/transport/socketio-simultaneous.test.ts +603 -0
- package/src/server/transport/socketio.test.ts +303 -0
- package/src/server/transport/socketio.ts +279 -0
- package/src/server/util.ts +85 -0
- package/src/testing/mock-random.test.ts +45 -0
- package/src/testing/mock-random.ts +27 -0
- package/src/types.ts +511 -0
- package/testing/package.json +7 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractPlayerMetadata,
|
|
3
|
+
doesMatchRequireAuthentication,
|
|
4
|
+
areCredentialsAuthentic,
|
|
5
|
+
Auth,
|
|
6
|
+
} from './auth';
|
|
7
|
+
|
|
8
|
+
import type { Server } from '../types';
|
|
9
|
+
|
|
10
|
+
describe('extractPlayerMetadata', () => {
|
|
11
|
+
describe('when metadata is not found', () => {
|
|
12
|
+
test('then playerMetadata is undefined', () => {
|
|
13
|
+
expect(extractPlayerMetadata(undefined, '0')).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('when metadata does not contain players field', () => {
|
|
18
|
+
test('then playerMetadata is undefined', () => {
|
|
19
|
+
expect(
|
|
20
|
+
extractPlayerMetadata({} as Server.MatchData, '0')
|
|
21
|
+
).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('when metadata does not contain playerID', () => {
|
|
26
|
+
test('then playerMetadata is undefined', () => {
|
|
27
|
+
expect(
|
|
28
|
+
extractPlayerMetadata(
|
|
29
|
+
{
|
|
30
|
+
gameName: '',
|
|
31
|
+
setupData: {},
|
|
32
|
+
players: { '1': { id: 1 } },
|
|
33
|
+
createdAt: 0,
|
|
34
|
+
updatedAt: 0,
|
|
35
|
+
},
|
|
36
|
+
'0'
|
|
37
|
+
)
|
|
38
|
+
).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('when metadata contains playerID', () => {
|
|
43
|
+
test('then playerMetadata is returned', () => {
|
|
44
|
+
const playerMetadata = { id: 0, credentials: 'SECRET' };
|
|
45
|
+
const result = extractPlayerMetadata(
|
|
46
|
+
{
|
|
47
|
+
gameName: '',
|
|
48
|
+
setupData: {},
|
|
49
|
+
players: { '0': playerMetadata },
|
|
50
|
+
createdAt: 0,
|
|
51
|
+
updatedAt: 0,
|
|
52
|
+
},
|
|
53
|
+
'0'
|
|
54
|
+
);
|
|
55
|
+
expect(result).toBe(playerMetadata);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('doesMatchRequireAuthentication', () => {
|
|
61
|
+
describe('when game metadata is not found', () => {
|
|
62
|
+
test('then authentication is not required', () => {
|
|
63
|
+
const result = doesMatchRequireAuthentication();
|
|
64
|
+
expect(result).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('when match has no credentials', () => {
|
|
69
|
+
test('then authentication is not required', () => {
|
|
70
|
+
const matchData = {
|
|
71
|
+
gameName: '',
|
|
72
|
+
setupData: {},
|
|
73
|
+
players: {
|
|
74
|
+
'0': { id: 1 },
|
|
75
|
+
},
|
|
76
|
+
createdAt: 0,
|
|
77
|
+
updatedAt: 0,
|
|
78
|
+
};
|
|
79
|
+
const result = doesMatchRequireAuthentication(matchData);
|
|
80
|
+
expect(result).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('when match has credentials', () => {
|
|
85
|
+
test('then authentication is required', () => {
|
|
86
|
+
const matchData = {
|
|
87
|
+
gameName: '',
|
|
88
|
+
setupData: {},
|
|
89
|
+
players: {
|
|
90
|
+
'0': {
|
|
91
|
+
id: 0,
|
|
92
|
+
credentials: 'SECRET',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
createdAt: 0,
|
|
96
|
+
updatedAt: 0,
|
|
97
|
+
};
|
|
98
|
+
const result = doesMatchRequireAuthentication(matchData);
|
|
99
|
+
expect(result).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('areCredentialsAuthentic', () => {
|
|
105
|
+
let action;
|
|
106
|
+
let playerID;
|
|
107
|
+
let matchData;
|
|
108
|
+
let credentials;
|
|
109
|
+
let playerMetadata;
|
|
110
|
+
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
playerID = '0';
|
|
113
|
+
|
|
114
|
+
action = {
|
|
115
|
+
payload: { credentials: 'SECRET' },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
matchData = {
|
|
119
|
+
players: {
|
|
120
|
+
'0': { credentials: 'SECRET' },
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
playerMetadata = matchData.players[playerID];
|
|
125
|
+
({ credentials } = action.payload || {});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('when game has credentials', () => {
|
|
129
|
+
describe('when action contains no payload', () => {
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
action = {};
|
|
132
|
+
({ credentials } = action.payload || {});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('the action is not authentic', async () => {
|
|
136
|
+
const result = areCredentialsAuthentic(credentials, playerMetadata);
|
|
137
|
+
expect(result).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('when action contains no credentials', () => {
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
action = {
|
|
144
|
+
payload: { someStuff: 'foo' },
|
|
145
|
+
};
|
|
146
|
+
({ credentials } = action.payload || {});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('then action is not authentic', async () => {
|
|
150
|
+
const result = areCredentialsAuthentic(credentials, playerMetadata);
|
|
151
|
+
expect(result).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('when action credentials do not match game credentials', () => {
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
action = {
|
|
158
|
+
payload: { credentials: 'WRONG' },
|
|
159
|
+
};
|
|
160
|
+
({ credentials } = action.payload || {});
|
|
161
|
+
});
|
|
162
|
+
test('then action is not authentic', async () => {
|
|
163
|
+
const result = areCredentialsAuthentic(credentials, playerMetadata);
|
|
164
|
+
expect(result).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('when playerMetadata is not found', () => {
|
|
169
|
+
test('then action is not authentic', () => {
|
|
170
|
+
const result = areCredentialsAuthentic(credentials, undefined);
|
|
171
|
+
expect(result).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('when action credentials do match game credentials', () => {
|
|
176
|
+
test('then action is authentic', async () => {
|
|
177
|
+
const result = areCredentialsAuthentic(credentials, playerMetadata);
|
|
178
|
+
expect(result).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('Auth', () => {
|
|
185
|
+
const credentials = 'credentials';
|
|
186
|
+
const playerData = { id: 0, credentials };
|
|
187
|
+
const metadata = {
|
|
188
|
+
gameName: '',
|
|
189
|
+
players: { '0': playerData },
|
|
190
|
+
createdAt: 0,
|
|
191
|
+
updatedAt: 0,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
describe('defaults', () => {
|
|
195
|
+
const auth = new Auth();
|
|
196
|
+
|
|
197
|
+
test('generateCredentials', () => {
|
|
198
|
+
expect(typeof auth.generateCredentials({})).toBe('string');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('authenticateCredentials', () => {
|
|
202
|
+
expect(
|
|
203
|
+
auth.authenticateCredentials({ playerID: '0', metadata, credentials })
|
|
204
|
+
).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('ignores bad options', () => {
|
|
209
|
+
const auth = new Auth({
|
|
210
|
+
generateCredentials: 'foo',
|
|
211
|
+
authenticateCredentials: 'bar',
|
|
212
|
+
} as any);
|
|
213
|
+
|
|
214
|
+
test('generateCredentials', () => {
|
|
215
|
+
expect(typeof auth.generateCredentials({})).toBe('string');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('authenticateCredentials', () => {
|
|
219
|
+
expect(
|
|
220
|
+
auth.authenticateCredentials({ playerID: '0', metadata, credentials })
|
|
221
|
+
).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('custom methods', () => {
|
|
226
|
+
const generateCredentials = jest.fn(() => credentials);
|
|
227
|
+
const authenticateCredentials = jest.fn(() => true);
|
|
228
|
+
const auth = new Auth({ generateCredentials, authenticateCredentials });
|
|
229
|
+
|
|
230
|
+
test('generateCredentials', () => {
|
|
231
|
+
const ctx = {};
|
|
232
|
+
expect(auth.generateCredentials(ctx)).toBe(credentials);
|
|
233
|
+
expect(generateCredentials).toHaveBeenCalledWith(ctx);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('authenticateCredentials', () => {
|
|
237
|
+
expect(
|
|
238
|
+
auth.authenticateCredentials({ playerID: '0', metadata, credentials })
|
|
239
|
+
).toBe(true);
|
|
240
|
+
expect(authenticateCredentials).toHaveBeenCalledWith(
|
|
241
|
+
credentials,
|
|
242
|
+
playerData
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('async', () => {
|
|
248
|
+
const generateCredentials = jest.fn(async () => credentials);
|
|
249
|
+
const authenticateCredentials = jest.fn(async () => true);
|
|
250
|
+
const auth = new Auth({ generateCredentials, authenticateCredentials });
|
|
251
|
+
|
|
252
|
+
test('generateCredentials', async () => {
|
|
253
|
+
const ctx = {};
|
|
254
|
+
const promise = auth.generateCredentials(ctx);
|
|
255
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
256
|
+
expect(generateCredentials).toHaveBeenCalledWith(ctx);
|
|
257
|
+
expect(await promise).toBe(credentials);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('authenticateCredentials', async () => {
|
|
261
|
+
const promise = auth.authenticateCredentials({
|
|
262
|
+
playerID: '0',
|
|
263
|
+
metadata,
|
|
264
|
+
credentials,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
268
|
+
expect(authenticateCredentials).toHaveBeenCalledWith(
|
|
269
|
+
credentials,
|
|
270
|
+
playerData
|
|
271
|
+
);
|
|
272
|
+
expect(await promise).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import type { Server, PlayerID } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Verifies that a match has metadata and is using credentials.
|
|
6
|
+
*/
|
|
7
|
+
export const doesMatchRequireAuthentication = (
|
|
8
|
+
matchData?: Server.MatchData
|
|
9
|
+
) => {
|
|
10
|
+
if (!matchData) return false;
|
|
11
|
+
const { players } = matchData;
|
|
12
|
+
const hasCredentials = Object.values(players).some(
|
|
13
|
+
(player) => !!(player && player.credentials)
|
|
14
|
+
);
|
|
15
|
+
return hasCredentials;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The default `authenticateCredentials` method.
|
|
20
|
+
* Verifies that the provided credentials match the player’s metadata.
|
|
21
|
+
*/
|
|
22
|
+
export const areCredentialsAuthentic: Server.AuthenticateCredentials = (
|
|
23
|
+
actionCredentials: string,
|
|
24
|
+
playerMetadata?: Server.PlayerMetadata
|
|
25
|
+
) => {
|
|
26
|
+
if (!actionCredentials) return false;
|
|
27
|
+
if (!playerMetadata) return false;
|
|
28
|
+
return actionCredentials === playerMetadata.credentials;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extracts a player’s metadata from the match data object.
|
|
33
|
+
*/
|
|
34
|
+
export const extractPlayerMetadata = (
|
|
35
|
+
matchData: Server.MatchData,
|
|
36
|
+
playerID: PlayerID
|
|
37
|
+
): Server.PlayerMetadata => {
|
|
38
|
+
if (matchData && matchData.players) {
|
|
39
|
+
return matchData.players[playerID];
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Class that provides authentication methods to the lobby server & transport.
|
|
45
|
+
*/
|
|
46
|
+
export class Auth {
|
|
47
|
+
private readonly shouldAuthenticate = doesMatchRequireAuthentication;
|
|
48
|
+
private readonly authenticate = areCredentialsAuthentic;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate credentials string from the Koa context.
|
|
52
|
+
*/
|
|
53
|
+
public readonly generateCredentials: Server.GenerateCredentials = () =>
|
|
54
|
+
nanoid();
|
|
55
|
+
|
|
56
|
+
constructor(
|
|
57
|
+
opts: {
|
|
58
|
+
authenticateCredentials?: Server.AuthenticateCredentials;
|
|
59
|
+
generateCredentials?: Server.GenerateCredentials;
|
|
60
|
+
} = {}
|
|
61
|
+
) {
|
|
62
|
+
if (typeof opts.authenticateCredentials === 'function') {
|
|
63
|
+
this.authenticate = opts.authenticateCredentials;
|
|
64
|
+
this.shouldAuthenticate = () => true;
|
|
65
|
+
}
|
|
66
|
+
if (typeof opts.generateCredentials === 'function') {
|
|
67
|
+
this.generateCredentials = opts.generateCredentials;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolves to true if the provided credentials are valid for the given
|
|
73
|
+
* metadata and player IDs, or if the match does not require authentication.
|
|
74
|
+
*/
|
|
75
|
+
public authenticateCredentials({
|
|
76
|
+
playerID,
|
|
77
|
+
credentials,
|
|
78
|
+
metadata,
|
|
79
|
+
}: {
|
|
80
|
+
playerID: string;
|
|
81
|
+
credentials: string | undefined;
|
|
82
|
+
metadata: Server.MatchData;
|
|
83
|
+
}) {
|
|
84
|
+
const playerMetadata = extractPlayerMetadata(metadata, playerID);
|
|
85
|
+
return this.shouldAuthenticate(metadata)
|
|
86
|
+
? this.authenticate(credentials, playerMetadata)
|
|
87
|
+
: true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import type { Origins as OriginsTS } from './cors';
|
|
4
|
+
let { Origins } = require('./cors') as { Origins: typeof OriginsTS };
|
|
5
|
+
|
|
6
|
+
describe('localhost origin', () => {
|
|
7
|
+
const middleware = cors({ origin: [Origins.LOCALHOST] });
|
|
8
|
+
|
|
9
|
+
test('allows localhost', () => {
|
|
10
|
+
const origin = 'http://localhost:8000/';
|
|
11
|
+
const req = createMockRequest(origin);
|
|
12
|
+
const res = new MockResponse();
|
|
13
|
+
middleware(req, res, () => {});
|
|
14
|
+
expect(res._headers['access-control-allow-origin']).toBe(origin);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('disallows other origins', () => {
|
|
18
|
+
const req = createMockRequest('http://example.com/');
|
|
19
|
+
const res = new MockResponse();
|
|
20
|
+
middleware(req, res, () => {});
|
|
21
|
+
expect('access-control-allow-origin' in res._headers).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('development-only localhost origin', () => {
|
|
26
|
+
const reloadOriginsModule = () => {
|
|
27
|
+
jest.resetModules();
|
|
28
|
+
({ Origins } = require('./cors'));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const oldNodeEnv = process.env.NODE_ENV;
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
process.env.NODE_ENV = oldNodeEnv;
|
|
35
|
+
reloadOriginsModule();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const origin = 'http://localhost:8000/';
|
|
39
|
+
|
|
40
|
+
describe('development environment', () => {
|
|
41
|
+
test('allows localhost', () => {
|
|
42
|
+
const middleware = cors({ origin: [Origins.LOCALHOST_IN_DEVELOPMENT] });
|
|
43
|
+
const req = createMockRequest(origin);
|
|
44
|
+
const res = new MockResponse();
|
|
45
|
+
middleware(req, res, () => {});
|
|
46
|
+
expect(res._headers['access-control-allow-origin']).toBe(origin);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('production environment', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
process.env.NODE_ENV = 'production';
|
|
53
|
+
reloadOriginsModule();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('disallows localhost', () => {
|
|
57
|
+
const middleware = cors({ origin: [Origins.LOCALHOST_IN_DEVELOPMENT] });
|
|
58
|
+
const req = createMockRequest(origin);
|
|
59
|
+
const res = new MockResponse();
|
|
60
|
+
middleware(req, res, () => {});
|
|
61
|
+
expect('access-control-allow-origin' in res._headers).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('disallows random URL', () => {
|
|
65
|
+
const middleware = cors({ origin: [Origins.LOCALHOST_IN_DEVELOPMENT] });
|
|
66
|
+
const req = createMockRequest('http://example.com/');
|
|
67
|
+
const res = new MockResponse();
|
|
68
|
+
middleware(req, res, () => {});
|
|
69
|
+
expect('access-control-allow-origin' in res._headers).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('allows URL when set alongside localhost', () => {
|
|
73
|
+
const origin = 'http://example.com/';
|
|
74
|
+
const middleware = cors({
|
|
75
|
+
origin: [Origins.LOCALHOST_IN_DEVELOPMENT, origin],
|
|
76
|
+
});
|
|
77
|
+
const req = createMockRequest(origin);
|
|
78
|
+
const res = new MockResponse();
|
|
79
|
+
middleware(req, res, () => {});
|
|
80
|
+
expect(res._headers['access-control-allow-origin']).toBe(origin);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create a fake request object from the given origin.
|
|
87
|
+
*/
|
|
88
|
+
function createMockRequest(origin: string): cors.CorsRequest {
|
|
89
|
+
return { headers: { origin } };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Class mocking the interface of the request object expected by the
|
|
94
|
+
* CORS middleware package.
|
|
95
|
+
*/
|
|
96
|
+
class MockResponse extends EventEmitter {
|
|
97
|
+
_headers: Record<string, string>;
|
|
98
|
+
statusCode: number;
|
|
99
|
+
|
|
100
|
+
constructor() {
|
|
101
|
+
super();
|
|
102
|
+
this._headers = {};
|
|
103
|
+
this.statusCode = 200;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
end() {
|
|
107
|
+
process.nextTick(
|
|
108
|
+
function () {
|
|
109
|
+
this.emit('finish');
|
|
110
|
+
}.bind(this)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getHeader(name: string) {
|
|
115
|
+
return this._headers[name.toLowerCase()];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setHeader(name: string, value: string) {
|
|
119
|
+
this._headers[name.toLowerCase()] = value;
|
|
120
|
+
}
|
|
121
|
+
}
|