@babylonjs-toolkit/server 0.0.0-stage → 9.35.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/CUSTOM.md +1690 -0
- package/DEPLOY.md +2012 -0
- package/README.md +1081 -2
- package/lib/AbuseLimits.d.ts +64 -0
- package/lib/AbuseLimits.js +121 -0
- package/lib/Authentication.d.ts +86 -0
- package/lib/Authentication.js +272 -0
- package/lib/BootSessionOwner.d.ts +86 -0
- package/lib/BootSessionOwner.js +116 -0
- package/lib/ColyseusLoggerAdapter.d.ts +32 -0
- package/lib/ColyseusLoggerAdapter.js +73 -0
- package/lib/DedicatedSceneRegistry.d.ts +52 -0
- package/lib/DedicatedSceneRegistry.js +74 -0
- package/lib/DedicatedSession.d.ts +152 -0
- package/lib/DedicatedSession.js +173 -0
- package/lib/DualTransport.d.ts +112 -0
- package/lib/DualTransport.js +197 -0
- package/lib/GameServer.d.ts +64 -0
- package/lib/GameServer.js +647 -0
- package/lib/GameServerConfig.d.ts +387 -0
- package/lib/GameServerConfig.js +1059 -0
- package/lib/GameServerLobbyRoom.d.ts +33 -0
- package/lib/GameServerLobbyRoom.js +56 -0
- package/lib/GameServerLogger.d.ts +111 -0
- package/lib/GameServerLogger.js +190 -0
- package/lib/GameServerMetrics.d.ts +158 -0
- package/lib/GameServerMetrics.js +276 -0
- package/lib/GameServerState.d.ts +164 -0
- package/lib/GameServerState.js +26 -0
- package/lib/HeadlessRuntime.d.ts +316 -0
- package/lib/HeadlessRuntime.js +380 -0
- package/lib/JoinGate.d.ts +34 -0
- package/lib/JoinGate.js +221 -0
- package/lib/NetworkAddresses.d.ts +33 -0
- package/lib/NetworkAddresses.js +90 -0
- package/lib/PlayerTokens.d.ts +17 -0
- package/lib/PlayerTokens.js +24 -0
- package/lib/RoomServerTransport.d.ts +73 -0
- package/lib/RoomServerTransport.js +90 -0
- package/lib/ServerEndpoints.d.ts +133 -0
- package/lib/ServerEndpoints.js +247 -0
- package/lib/ServerInit.d.ts +28 -0
- package/lib/ServerInit.js +113 -0
- package/lib/SessionRoom.d.ts +391 -0
- package/lib/SessionRoom.js +930 -0
- package/lib/TlsCertificates.d.ts +35 -0
- package/lib/TlsCertificates.js +130 -0
- package/lib/admin/AccountRoutes.d.ts +8 -0
- package/lib/admin/AccountRoutes.js +502 -0
- package/lib/admin/AdminAccounts.d.ts +329 -0
- package/lib/admin/AdminAccounts.js +607 -0
- package/lib/admin/AdminApiRouter.d.ts +202 -0
- package/lib/admin/AdminApiRouter.js +465 -0
- package/lib/admin/AdminApiTypes.d.ts +1782 -0
- package/lib/admin/AdminApiTypes.js +4 -0
- package/lib/admin/AdminAudit.d.ts +113 -0
- package/lib/admin/AdminAudit.js +266 -0
- package/lib/admin/AdminCli.d.ts +7 -0
- package/lib/admin/AdminCli.js +351 -0
- package/lib/admin/AdminClock.d.ts +51 -0
- package/lib/admin/AdminClock.js +10 -0
- package/lib/admin/AdminConsole.d.ts +39 -0
- package/lib/admin/AdminConsole.js +325 -0
- package/lib/admin/AdminConstants.d.ts +147 -0
- package/lib/admin/AdminConstants.js +167 -0
- package/lib/admin/AdminCrypto.d.ts +19 -0
- package/lib/admin/AdminCrypto.js +29 -0
- package/lib/admin/AdminExtensionsHost.d.ts +176 -0
- package/lib/admin/AdminExtensionsHost.js +307 -0
- package/lib/admin/AdminPasswords.d.ts +33 -0
- package/lib/admin/AdminPasswords.js +92 -0
- package/lib/admin/AdminPermissions.d.ts +28 -0
- package/lib/admin/AdminPermissions.js +74 -0
- package/lib/admin/AdminRecords.d.ts +196 -0
- package/lib/admin/AdminRecords.js +3 -0
- package/lib/admin/AdminRequestBodies.d.ts +47 -0
- package/lib/admin/AdminRequestBodies.js +84 -0
- package/lib/admin/AdminRequestGate.d.ts +38 -0
- package/lib/admin/AdminRequestGate.js +59 -0
- package/lib/admin/AdminResponses.d.ts +103 -0
- package/lib/admin/AdminResponses.js +164 -0
- package/lib/admin/AdminServices.d.ts +99 -0
- package/lib/admin/AdminServices.js +4 -0
- package/lib/admin/AdminStaticFiles.d.ts +29 -0
- package/lib/admin/AdminStaticFiles.js +97 -0
- package/lib/admin/AdminStore.d.ts +152 -0
- package/lib/admin/AdminStore.js +46 -0
- package/lib/admin/AlertChannels.d.ts +43 -0
- package/lib/admin/AlertChannels.js +120 -0
- package/lib/admin/AlertEngine.d.ts +231 -0
- package/lib/admin/AlertEngine.js +681 -0
- package/lib/admin/AlertRoutes.d.ts +8 -0
- package/lib/admin/AlertRoutes.js +180 -0
- package/lib/admin/ApiKeyRoutes.d.ts +8 -0
- package/lib/admin/ApiKeyRoutes.js +53 -0
- package/lib/admin/ApiKeys.d.ts +63 -0
- package/lib/admin/ApiKeys.js +167 -0
- package/lib/admin/AuditRoutes.d.ts +8 -0
- package/lib/admin/AuditRoutes.js +106 -0
- package/lib/admin/AuthToolRoutes.d.ts +13 -0
- package/lib/admin/AuthToolRoutes.js +164 -0
- package/lib/admin/BanList.d.ts +87 -0
- package/lib/admin/BanList.js +152 -0
- package/lib/admin/CborDecoder.d.ts +18 -0
- package/lib/admin/CborDecoder.js +201 -0
- package/lib/admin/ColyseusCloudClient.d.ts +313 -0
- package/lib/admin/ColyseusCloudClient.js +272 -0
- package/lib/admin/ColyseusCloudRoutes.d.ts +8 -0
- package/lib/admin/ColyseusCloudRoutes.js +91 -0
- package/lib/admin/ColyseusCloudService.d.ts +180 -0
- package/lib/admin/ColyseusCloudService.js +476 -0
- package/lib/admin/ColyseusCloudSettings.d.ts +16 -0
- package/lib/admin/ColyseusCloudSettings.js +29 -0
- package/lib/admin/ColyseusCloudViews.d.ts +105 -0
- package/lib/admin/ColyseusCloudViews.js +371 -0
- package/lib/admin/CommonPasswords.d.ts +6 -0
- package/lib/admin/CommonPasswords.js +19 -0
- package/lib/admin/ConfigOverrides.d.ts +174 -0
- package/lib/admin/ConfigOverrides.js +406 -0
- package/lib/admin/ConfigRoutes.d.ts +8 -0
- package/lib/admin/ConfigRoutes.js +91 -0
- package/lib/admin/ConsoleSessions.d.ts +112 -0
- package/lib/admin/ConsoleSessions.js +201 -0
- package/lib/admin/DedicatedRoutes.d.ts +21 -0
- package/lib/admin/DedicatedRoutes.js +254 -0
- package/lib/admin/ExtensionRoutes.d.ts +8 -0
- package/lib/admin/ExtensionRoutes.js +145 -0
- package/lib/admin/FileAdminStore.d.ts +157 -0
- package/lib/admin/FileAdminStore.js +349 -0
- package/lib/admin/FleetDirectory.d.ts +323 -0
- package/lib/admin/FleetDirectory.js +489 -0
- package/lib/admin/FleetLeader.d.ts +43 -0
- package/lib/admin/FleetLeader.js +89 -0
- package/lib/admin/FleetRoutes.d.ts +55 -0
- package/lib/admin/FleetRoutes.js +465 -0
- package/lib/admin/HealthWarnings.d.ts +10 -0
- package/lib/admin/HealthWarnings.js +95 -0
- package/lib/admin/LeaderboardRoutes.d.ts +8 -0
- package/lib/admin/LeaderboardRoutes.js +569 -0
- package/lib/admin/LogBuffer.d.ts +60 -0
- package/lib/admin/LogBuffer.js +92 -0
- package/lib/admin/LogRedaction.d.ts +15 -0
- package/lib/admin/LogRedaction.js +72 -0
- package/lib/admin/LogRoutes.d.ts +45 -0
- package/lib/admin/LogRoutes.js +307 -0
- package/lib/admin/MaintenanceMode.d.ts +41 -0
- package/lib/admin/MaintenanceMode.js +74 -0
- package/lib/admin/MaintenanceWindows.d.ts +91 -0
- package/lib/admin/MaintenanceWindows.js +207 -0
- package/lib/admin/MetricsRoutes.d.ts +8 -0
- package/lib/admin/MetricsRoutes.js +209 -0
- package/lib/admin/MetricsSampler.d.ts +124 -0
- package/lib/admin/MetricsSampler.js +211 -0
- package/lib/admin/OneTimeCodes.d.ts +60 -0
- package/lib/admin/OneTimeCodes.js +162 -0
- package/lib/admin/OpenIdConnect.d.ts +146 -0
- package/lib/admin/OpenIdConnect.js +335 -0
- package/lib/admin/Passkeys.d.ts +130 -0
- package/lib/admin/Passkeys.js +432 -0
- package/lib/admin/PlayerRoutes.d.ts +20 -0
- package/lib/admin/PlayerRoutes.js +676 -0
- package/lib/admin/RedisAdminStore.d.ts +114 -0
- package/lib/admin/RedisAdminStore.js +258 -0
- package/lib/admin/RolloutCoordinator.d.ts +99 -0
- package/lib/admin/RolloutCoordinator.js +321 -0
- package/lib/admin/ServerEvents.d.ts +57 -0
- package/lib/admin/ServerEvents.js +89 -0
- package/lib/admin/SessionRoutes.d.ts +113 -0
- package/lib/admin/SessionRoutes.js +386 -0
- package/lib/admin/UserRoutes.d.ts +8 -0
- package/lib/admin/UserRoutes.js +214 -0
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +221 -0
- package/lib/console/assets/index-CI5N7c-T.js +21 -0
- package/lib/console/assets/index-DPitvzLp.css +1 -0
- package/lib/console/favicon.svg +1 -0
- package/lib/console/index.html +16 -0
- package/lib/discord/DiscordOAuth.d.ts +57 -0
- package/lib/discord/DiscordOAuth.js +127 -0
- package/lib/discord/DiscordSessions.d.ts +79 -0
- package/lib/discord/DiscordSessions.js +135 -0
- package/lib/discord/DiscordSignInConstants.d.ts +54 -0
- package/lib/discord/DiscordSignInConstants.js +55 -0
- package/lib/discord/DiscordSignInRoutes.d.ts +51 -0
- package/lib/discord/DiscordSignInRoutes.js +359 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +18 -0
- package/lib/leaderboards/FileLeaderboardRankStore.d.ts +293 -0
- package/lib/leaderboards/FileLeaderboardRankStore.js +808 -0
- package/lib/leaderboards/LeaderboardConstants.d.ts +63 -0
- package/lib/leaderboards/LeaderboardConstants.js +70 -0
- package/lib/leaderboards/LeaderboardError.d.ts +15 -0
- package/lib/leaderboards/LeaderboardError.js +20 -0
- package/lib/leaderboards/LeaderboardIdentity.d.ts +44 -0
- package/lib/leaderboards/LeaderboardIdentity.js +72 -0
- package/lib/leaderboards/LeaderboardPublicApi.d.ts +17 -0
- package/lib/leaderboards/LeaderboardPublicApi.js +389 -0
- package/lib/leaderboards/LeaderboardRankStore.d.ts +296 -0
- package/lib/leaderboards/LeaderboardRankStore.js +18 -0
- package/lib/leaderboards/LeaderboardRules.d.ts +122 -0
- package/lib/leaderboards/LeaderboardRules.js +472 -0
- package/lib/leaderboards/LeaderboardScheduler.d.ts +43 -0
- package/lib/leaderboards/LeaderboardScheduler.js +103 -0
- package/lib/leaderboards/LeaderboardService.d.ts +669 -0
- package/lib/leaderboards/LeaderboardService.js +1667 -0
- package/lib/leaderboards/LeaderboardTransfer.d.ts +35 -0
- package/lib/leaderboards/LeaderboardTransfer.js +580 -0
- package/lib/leaderboards/RedisLeaderboardRankStore.d.ts +282 -0
- package/lib/leaderboards/RedisLeaderboardRankStore.js +691 -0
- package/package.json +64 -4
- package/templates/init/Caddyfile +6 -0
- package/templates/init/DEPLOY.md +233 -0
- package/templates/init/Dockerfile +12 -0
- package/templates/init/app.json +21 -0
- package/templates/init/aws/README.md +118 -0
- package/templates/init/aws/ecs-fargate-alb.yml +207 -0
- package/templates/init/aws/ecs-fargate-nlb-quic.yml +210 -0
- package/templates/init/babylon-game-server.config.mjs +158 -0
- package/templates/init/colyseus-cloud/README.md +133 -0
- package/templates/init/colyseus-cloud/babylon-game-server.config.mjs +36 -0
- package/templates/init/colyseus-cloud/ecosystem.config.cjs +35 -0
- package/templates/init/colyseus-cloud/gitignore +6 -0
- package/templates/init/colyseus-cloud/package.json +15 -0
- package/templates/init/digitalocean-app.yaml +30 -0
- package/templates/init/docker-compose.full.yml +45 -0
- package/templates/init/docker-compose.websocket.yml +41 -0
- package/templates/init/fly.toml +56 -0
- package/templates/init/heroku.yml +11 -0
- package/templates/init/kubernetes/deployment.yaml +71 -0
- package/templates/init/kubernetes/redis.yaml +73 -0
- package/templates/init/kubernetes/service.yaml +24 -0
- package/templates/init/monitoring/grafana-dashboard.json +539 -0
- package/templates/init/monitoring/prometheus-alerts.yml +51 -0
- package/templates/init/monitoring/prometheus-scrape.yml +17 -0
- package/templates/init/railway.json +13 -0
- package/templates/init/render.yaml +19 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// The metrics history of one instance (game-server-admin-console D22, DR §6.7): one MetricsSample every
|
|
2
|
+
// metricsSampleIntervalMs into an in-memory ring of admin.metricsHistoryHours, and with Redis also into the store stream
|
|
3
|
+
// metrics:<processId>, so the fleet's charts survive page reloads and an instance's restart.
|
|
4
|
+
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
5
|
+
import { getLogger } from "../GameServerLogger.js";
|
|
6
|
+
import { EVENT_LOOP_MONITOR_RESOLUTION_MS, METRICS_SAMPLES_PER_HOUR } from "./AdminConstants.js";
|
|
7
|
+
/** Counter values of a sample when metrics are off (D22: process and room figures only). */
|
|
8
|
+
const EMPTY_COUNTERS = {
|
|
9
|
+
joinsAccepted: 0,
|
|
10
|
+
joinsRefused: 0,
|
|
11
|
+
refusalsByReason: {},
|
|
12
|
+
authFailures: 0,
|
|
13
|
+
framesIn: 0,
|
|
14
|
+
framesOut: 0,
|
|
15
|
+
bytesIn: 0,
|
|
16
|
+
bytesOut: 0,
|
|
17
|
+
framesByTransport: { websocket: 0, webtransport: 0 },
|
|
18
|
+
messagesIn: 0,
|
|
19
|
+
messagesOut: 0,
|
|
20
|
+
dedicatedTickOverruns: 0,
|
|
21
|
+
dedicatedFrameSecondsSum: 0,
|
|
22
|
+
dedicatedFrameCount: 0,
|
|
23
|
+
cpuSecondsTotal: 0,
|
|
24
|
+
residentMemoryBytes: 0,
|
|
25
|
+
heapUsedBytes: 0,
|
|
26
|
+
gcSecondsTotal: 0,
|
|
27
|
+
leaderboardScoresAccepted: 0,
|
|
28
|
+
leaderboardScoresRefused: 0
|
|
29
|
+
};
|
|
30
|
+
/** Least time between two "metrics sample failed" warnings, in milliseconds. */
|
|
31
|
+
const SAMPLE_WARNING_INTERVAL_MS = 60000;
|
|
32
|
+
/** Microseconds per second (process.cpuUsage reports microseconds). */
|
|
33
|
+
const MICROSECONDS_PER_SECOND = 1e6;
|
|
34
|
+
/** Nanoseconds per millisecond (the event-loop histogram reports nanoseconds). */
|
|
35
|
+
const NANOSECONDS_PER_MILLISECOND = 1e6;
|
|
36
|
+
/** Milliseconds per second. */
|
|
37
|
+
const MILLISECONDS_PER_SECOND = 1000;
|
|
38
|
+
/**
|
|
39
|
+
* The sampler reads counters at most this fraction of its interval old, so each sample sees fresh counters while the
|
|
40
|
+
* heartbeats reuse the sampler's snapshot (R22).
|
|
41
|
+
*/
|
|
42
|
+
const SNAPSHOT_AGE_FRACTION = 0.5;
|
|
43
|
+
/**
|
|
44
|
+
* The store stream of an instance's samples.
|
|
45
|
+
* @param processId The instance.
|
|
46
|
+
* @returns The stream name.
|
|
47
|
+
*/
|
|
48
|
+
export function metricsStreamOf(processId) {
|
|
49
|
+
return `metrics:${processId}`;
|
|
50
|
+
}
|
|
51
|
+
/** Takes this instance's samples while the console runs. */
|
|
52
|
+
export class MetricsSampler {
|
|
53
|
+
/** The shared core services. */
|
|
54
|
+
services;
|
|
55
|
+
/** The samples, oldest first. */
|
|
56
|
+
ring = [];
|
|
57
|
+
/** How many samples the ring and the store stream keep. */
|
|
58
|
+
capacity;
|
|
59
|
+
/** The sampler's own event-loop delay histogram, so Prometheus' windows are untouched (D22). */
|
|
60
|
+
eventLoopMonitor = null;
|
|
61
|
+
/** The sample timer, while started. */
|
|
62
|
+
sampleTimer = null;
|
|
63
|
+
/** True while a sample is being taken, so a slow store never stacks them. */
|
|
64
|
+
isSampling = false;
|
|
65
|
+
/** The counters of the previous sample, for the dedicated frame mean between samples. */
|
|
66
|
+
previousCounters = null;
|
|
67
|
+
/** Clock time of the last "metrics sample failed" warning. */
|
|
68
|
+
lastSampleWarningAt = Number.NEGATIVE_INFINITY;
|
|
69
|
+
/**
|
|
70
|
+
* Prepares an empty history; nothing is sampled until start().
|
|
71
|
+
* @param services The shared core services.
|
|
72
|
+
*/
|
|
73
|
+
constructor(services) {
|
|
74
|
+
this.services = services;
|
|
75
|
+
this.capacity = Math.max(1, Math.round(services.config.admin.metricsHistoryHours * METRICS_SAMPLES_PER_HOUR));
|
|
76
|
+
}
|
|
77
|
+
/** Starts sampling every metricsSampleIntervalMs. */
|
|
78
|
+
start() {
|
|
79
|
+
this.eventLoopMonitor = monitorEventLoopDelay({ resolution: EVENT_LOOP_MONITOR_RESOLUTION_MS });
|
|
80
|
+
this.eventLoopMonitor.enable();
|
|
81
|
+
this.sampleTimer = this.services.clock.setInterval(() => void this.takeSample(), this.services.metricsSampleIntervalMs);
|
|
82
|
+
}
|
|
83
|
+
/** Stops sampling; the history taken so far stays readable. */
|
|
84
|
+
stop() {
|
|
85
|
+
if (this.sampleTimer != null)
|
|
86
|
+
this.services.clock.clearInterval(this.sampleTimer);
|
|
87
|
+
this.sampleTimer = null;
|
|
88
|
+
this.eventLoopMonitor?.disable();
|
|
89
|
+
this.eventLoopMonitor = null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* This instance's samples of a recent period, from the in-memory ring.
|
|
93
|
+
* @param rangeMilliseconds How far back, in milliseconds.
|
|
94
|
+
* @returns The samples, oldest first.
|
|
95
|
+
*/
|
|
96
|
+
samples(rangeMilliseconds) {
|
|
97
|
+
const cutoff = this.services.clock.now() - rangeMilliseconds;
|
|
98
|
+
return this.ring.filter((sample) => Date.parse(sample.at) >= cutoff);
|
|
99
|
+
}
|
|
100
|
+
/** Takes one sample into the ring and, with Redis, into the store stream; a failure is logged. */
|
|
101
|
+
async takeSample() {
|
|
102
|
+
if (this.isSampling)
|
|
103
|
+
return;
|
|
104
|
+
this.isSampling = true;
|
|
105
|
+
try {
|
|
106
|
+
const sample = await this.buildSample();
|
|
107
|
+
this.ring.push(sample);
|
|
108
|
+
if (this.ring.length > this.capacity)
|
|
109
|
+
this.ring.splice(0, this.ring.length - this.capacity);
|
|
110
|
+
if (this.services.store.kind === "redis")
|
|
111
|
+
await this.storeSample(sample);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
this.warnSampleFailure(error);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
this.isSampling = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Appends a sample to this instance's store stream and trims it to the ring's capacity.
|
|
122
|
+
* @param sample The sample.
|
|
123
|
+
*/
|
|
124
|
+
async storeSample(sample) {
|
|
125
|
+
const stream = metricsStreamOf(this.services.processId());
|
|
126
|
+
await this.services.store.appendRecord(stream, sample);
|
|
127
|
+
await this.services.store.trimRecordsToCount(stream, this.capacity);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* This instance's figures now (DR §6.7).
|
|
131
|
+
* @returns The sample.
|
|
132
|
+
*/
|
|
133
|
+
async buildSample() {
|
|
134
|
+
const counters = await this.readCounters();
|
|
135
|
+
const rooms = Array.from(this.services.state.sessionRooms);
|
|
136
|
+
const memory = process.memoryUsage();
|
|
137
|
+
const cpu = process.cpuUsage();
|
|
138
|
+
const sample = {
|
|
139
|
+
at: new Date(this.services.clock.now()).toISOString(),
|
|
140
|
+
players: rooms.reduce((total, room) => total + room.playerCount, 0),
|
|
141
|
+
sessions: rooms.length,
|
|
142
|
+
dedicatedSessions: rooms.filter((room) => room.isDedicated).length,
|
|
143
|
+
joinsAccepted: counters.joinsAccepted,
|
|
144
|
+
joinsRefused: counters.joinsRefused,
|
|
145
|
+
refusalsByReason: Object.assign({}, counters.refusalsByReason),
|
|
146
|
+
authFailures: counters.authFailures,
|
|
147
|
+
framesIn: counters.framesIn,
|
|
148
|
+
framesOut: counters.framesOut,
|
|
149
|
+
bytesIn: counters.bytesIn,
|
|
150
|
+
bytesOut: counters.bytesOut,
|
|
151
|
+
framesByTransport: Object.assign({}, counters.framesByTransport),
|
|
152
|
+
messagesIn: counters.messagesIn,
|
|
153
|
+
cpuSecondsTotal: (cpu.user + cpu.system) / MICROSECONDS_PER_SECOND,
|
|
154
|
+
residentMemoryBytes: memory.rss,
|
|
155
|
+
heapUsedBytes: memory.heapUsed,
|
|
156
|
+
eventLoopLagMilliseconds: this.sampleEventLoopLag(),
|
|
157
|
+
gcSecondsTotal: counters.gcSecondsTotal,
|
|
158
|
+
dedicatedFrameMillisecondsAverage: this.dedicatedFrameAverage(counters),
|
|
159
|
+
dedicatedTickOverruns: counters.dedicatedTickOverruns,
|
|
160
|
+
leaderboardScoresAccepted: counters.leaderboardScoresAccepted,
|
|
161
|
+
leaderboardScoresRefused: counters.leaderboardScoresRefused
|
|
162
|
+
};
|
|
163
|
+
this.previousCounters = counters;
|
|
164
|
+
return sample;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The metrics counters, read fresh for this sample (R22), or zeros when metrics are off.
|
|
168
|
+
* @returns The counters.
|
|
169
|
+
*/
|
|
170
|
+
async readCounters() {
|
|
171
|
+
const metrics = this.services.routeContext.metrics();
|
|
172
|
+
if (metrics == null)
|
|
173
|
+
return EMPTY_COUNTERS;
|
|
174
|
+
return metrics.snapshot(this.services.metricsSampleIntervalMs * SNAPSHOT_AGE_FRACTION);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The mean dedicated frame time since the previous sample.
|
|
178
|
+
* @param counters The counters now.
|
|
179
|
+
* @returns The mean, in milliseconds (0 when no dedicated frame ran).
|
|
180
|
+
*/
|
|
181
|
+
dedicatedFrameAverage(counters) {
|
|
182
|
+
const previousSum = this.previousCounters?.dedicatedFrameSecondsSum ?? 0;
|
|
183
|
+
const previousCount = this.previousCounters?.dedicatedFrameCount ?? 0;
|
|
184
|
+
const frameCount = counters.dedicatedFrameCount - previousCount;
|
|
185
|
+
if (frameCount <= 0)
|
|
186
|
+
return 0;
|
|
187
|
+
return ((counters.dedicatedFrameSecondsSum - previousSum) / frameCount) * MILLISECONDS_PER_SECOND;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The mean event-loop delay since the previous sample; the histogram restarts afterwards.
|
|
191
|
+
* @returns The delay, in milliseconds (0 before any measurement).
|
|
192
|
+
*/
|
|
193
|
+
sampleEventLoopLag() {
|
|
194
|
+
if (this.eventLoopMonitor == null)
|
|
195
|
+
return 0;
|
|
196
|
+
const meanNanoseconds = this.eventLoopMonitor.mean;
|
|
197
|
+
this.eventLoopMonitor.reset();
|
|
198
|
+
return Number.isFinite(meanNanoseconds) ? meanNanoseconds / NANOSECONDS_PER_MILLISECOND : 0;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Logs a failed sample at warn, at most once per SAMPLE_WARNING_INTERVAL_MS.
|
|
202
|
+
* @param error What failed.
|
|
203
|
+
*/
|
|
204
|
+
warnSampleFailure(error) {
|
|
205
|
+
const now = this.services.clock.now();
|
|
206
|
+
if (now - this.lastSampleWarningAt < SAMPLE_WARNING_INTERVAL_MS)
|
|
207
|
+
return;
|
|
208
|
+
this.lastSampleWarningAt = now;
|
|
209
|
+
getLogger().warn("a metrics sample failed", { error: String(error) });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encodes bytes as base32 without padding.
|
|
3
|
+
* @param bytes The bytes.
|
|
4
|
+
* @returns The base32 text (upper case).
|
|
5
|
+
*/
|
|
6
|
+
export declare function encodeBase32(bytes: Uint8Array): string;
|
|
7
|
+
/**
|
|
8
|
+
* Decodes base32 (any case, padding and spaces ignored).
|
|
9
|
+
* @param text The base32 text.
|
|
10
|
+
* @returns The bytes.
|
|
11
|
+
* @throws Error on a character outside the alphabet.
|
|
12
|
+
*/
|
|
13
|
+
export declare function decodeBase32(text: string): Buffer;
|
|
14
|
+
/**
|
|
15
|
+
* A new random TOTP secret (TOTP_SECRET_BYTES bytes).
|
|
16
|
+
* @returns The secret in base32.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateTotpSecret(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Computes the 6-digit TOTP code of a counter (a 30-second step since the Unix epoch).
|
|
21
|
+
* @param secretBase32 The shared secret in RFC 4648 base32.
|
|
22
|
+
* @param counter The time step number.
|
|
23
|
+
* @returns The zero-padded 6-digit code.
|
|
24
|
+
*/
|
|
25
|
+
export declare function totpCodeAt(secretBase32: string, counter: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* Checks a TOTP code: steps from TOTP_ALLOWED_DRIFT_STEPS before to after now are accepted, but never a step at or
|
|
28
|
+
* below the last one used (each code works once, D8).
|
|
29
|
+
* @param secretBase32 The shared secret.
|
|
30
|
+
* @param code The code given.
|
|
31
|
+
* @param lastUsedStep The last step this secret signed in with (0 for none).
|
|
32
|
+
* @param nowMilliseconds The current time.
|
|
33
|
+
* @returns ok and the matched step (to store as the new lastUsedStep), or ok false and step 0.
|
|
34
|
+
*/
|
|
35
|
+
export declare function verifyTotp(secretBase32: string, code: string, lastUsedStep: number, nowMilliseconds: number): {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
step: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The otpauth:// URI an authenticator app reads from the QR code (DR §8).
|
|
41
|
+
* @param username The account's username.
|
|
42
|
+
* @param secretBase32 The secret.
|
|
43
|
+
* @returns The URI.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildOtpauthUri(username: string, secretBase32: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* New recovery codes (`xxxx-xxxx`, base32 lower case) and their stored hashes.
|
|
48
|
+
* @returns The codes to show once, and their SHA-256 hashes to store.
|
|
49
|
+
*/
|
|
50
|
+
export declare function generateRecoveryCodes(): {
|
|
51
|
+
codes: string[];
|
|
52
|
+
hashes: string[];
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Uses up a recovery code.
|
|
56
|
+
* @param code The code given (case and spaces ignored).
|
|
57
|
+
* @param hashes The account's unused code hashes.
|
|
58
|
+
* @returns The hashes left after removing the match, or null when no code matches.
|
|
59
|
+
*/
|
|
60
|
+
export declare function consumeRecoveryCode(code: string, hashes: string[]): string[] | null;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Authenticator codes and recovery codes (game-server-admin-console D8, DR §8): RFC 6238 TOTP with SHA-1, 6 digits and
|
|
2
|
+
// 30-second steps; RFC 4648 base32 secrets; ten single-use recovery codes stored as SHA-256 hashes.
|
|
3
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
4
|
+
import { sha256Hex, textsMatch } from "./AdminCrypto.js";
|
|
5
|
+
import { RECOVERY_CODE_COUNT, TOTP_ALLOWED_DRIFT_STEPS, TOTP_DIGITS, TOTP_SECRET_BYTES, TOTP_STEP_SECONDS } from "./AdminConstants.js";
|
|
6
|
+
/** The RFC 4648 base32 alphabet. */
|
|
7
|
+
const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
8
|
+
/** Bits one base32 character carries. */
|
|
9
|
+
const BITS_PER_BASE32_CHARACTER = 5;
|
|
10
|
+
/** Bits in a byte. */
|
|
11
|
+
const BITS_PER_BYTE = 8;
|
|
12
|
+
/** Mask of the low five bits. */
|
|
13
|
+
const FIVE_BIT_MASK = 0x1f;
|
|
14
|
+
/** Keeps the bit buffer small: only its newest 16 bits are ever read. */
|
|
15
|
+
const BIT_BUFFER_MASK = 0xffff;
|
|
16
|
+
/** Mask of a byte. */
|
|
17
|
+
const BYTE_MASK = 0xff;
|
|
18
|
+
/** Mask of the dynamic truncation offset (RFC 4226). */
|
|
19
|
+
const TRUNCATION_OFFSET_MASK = 0x0f;
|
|
20
|
+
/** Mask that clears the sign bit of the truncated code (RFC 4226). */
|
|
21
|
+
const SIGN_BIT_MASK = 0x7fffffff;
|
|
22
|
+
/** Bytes of a TOTP counter. */
|
|
23
|
+
const COUNTER_BYTES = 8;
|
|
24
|
+
/** Milliseconds per second. */
|
|
25
|
+
const MILLISECONDS_PER_SECOND = 1000;
|
|
26
|
+
/** Issuer shown by authenticator apps. */
|
|
27
|
+
const OTPAUTH_ISSUER = "Babylon Game Server";
|
|
28
|
+
/** Random bytes of one recovery code: 5 bytes = 8 base32 characters. */
|
|
29
|
+
const RECOVERY_CODE_BYTES = 5;
|
|
30
|
+
/** Characters of each half of a recovery code (`xxxx-xxxx`). */
|
|
31
|
+
const RECOVERY_CODE_HALF_LENGTH = 4;
|
|
32
|
+
/**
|
|
33
|
+
* Encodes bytes as base32 without padding.
|
|
34
|
+
* @param bytes The bytes.
|
|
35
|
+
* @returns The base32 text (upper case).
|
|
36
|
+
*/
|
|
37
|
+
export function encodeBase32(bytes) {
|
|
38
|
+
let bitCount = 0;
|
|
39
|
+
let bitBuffer = 0;
|
|
40
|
+
let text = "";
|
|
41
|
+
for (const byte of bytes) {
|
|
42
|
+
bitBuffer = ((bitBuffer << BITS_PER_BYTE) | byte) & BIT_BUFFER_MASK;
|
|
43
|
+
bitCount += BITS_PER_BYTE;
|
|
44
|
+
while (bitCount >= BITS_PER_BASE32_CHARACTER) {
|
|
45
|
+
text += BASE32_ALPHABET[(bitBuffer >>> (bitCount - BITS_PER_BASE32_CHARACTER)) & FIVE_BIT_MASK];
|
|
46
|
+
bitCount -= BITS_PER_BASE32_CHARACTER;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (bitCount > 0)
|
|
50
|
+
text += BASE32_ALPHABET[(bitBuffer << (BITS_PER_BASE32_CHARACTER - bitCount)) & FIVE_BIT_MASK];
|
|
51
|
+
return text;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Decodes base32 (any case, padding and spaces ignored).
|
|
55
|
+
* @param text The base32 text.
|
|
56
|
+
* @returns The bytes.
|
|
57
|
+
* @throws Error on a character outside the alphabet.
|
|
58
|
+
*/
|
|
59
|
+
export function decodeBase32(text) {
|
|
60
|
+
const clean = text.replace(/[\s=]/g, "").toUpperCase();
|
|
61
|
+
let bitCount = 0;
|
|
62
|
+
let bitBuffer = 0;
|
|
63
|
+
const bytes = [];
|
|
64
|
+
for (const character of clean) {
|
|
65
|
+
const value = BASE32_ALPHABET.indexOf(character);
|
|
66
|
+
if (value < 0)
|
|
67
|
+
throw new Error("not base32: " + character);
|
|
68
|
+
bitBuffer = ((bitBuffer << BITS_PER_BASE32_CHARACTER) | value) & BIT_BUFFER_MASK;
|
|
69
|
+
bitCount += BITS_PER_BASE32_CHARACTER;
|
|
70
|
+
if (bitCount >= BITS_PER_BYTE) {
|
|
71
|
+
bytes.push((bitBuffer >>> (bitCount - BITS_PER_BYTE)) & BYTE_MASK);
|
|
72
|
+
bitCount -= BITS_PER_BYTE;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return Buffer.from(bytes);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A new random TOTP secret (TOTP_SECRET_BYTES bytes).
|
|
79
|
+
* @returns The secret in base32.
|
|
80
|
+
*/
|
|
81
|
+
export function generateTotpSecret() {
|
|
82
|
+
return encodeBase32(randomBytes(TOTP_SECRET_BYTES));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Computes the 6-digit TOTP code of a counter (a 30-second step since the Unix epoch).
|
|
86
|
+
* @param secretBase32 The shared secret in RFC 4648 base32.
|
|
87
|
+
* @param counter The time step number.
|
|
88
|
+
* @returns The zero-padded 6-digit code.
|
|
89
|
+
*/
|
|
90
|
+
export function totpCodeAt(secretBase32, counter) {
|
|
91
|
+
const counterBytes = Buffer.alloc(COUNTER_BYTES);
|
|
92
|
+
counterBytes.writeBigUInt64BE(BigInt(counter));
|
|
93
|
+
const digest = createHmac("sha1", decodeBase32(secretBase32)).update(counterBytes).digest();
|
|
94
|
+
const offset = digest[digest.length - 1] & TRUNCATION_OFFSET_MASK;
|
|
95
|
+
const binaryCode = digest.readUInt32BE(offset) & SIGN_BIT_MASK;
|
|
96
|
+
return String(binaryCode % 10 ** TOTP_DIGITS).padStart(TOTP_DIGITS, "0");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Checks a TOTP code: steps from TOTP_ALLOWED_DRIFT_STEPS before to after now are accepted, but never a step at or
|
|
100
|
+
* below the last one used (each code works once, D8).
|
|
101
|
+
* @param secretBase32 The shared secret.
|
|
102
|
+
* @param code The code given.
|
|
103
|
+
* @param lastUsedStep The last step this secret signed in with (0 for none).
|
|
104
|
+
* @param nowMilliseconds The current time.
|
|
105
|
+
* @returns ok and the matched step (to store as the new lastUsedStep), or ok false and step 0.
|
|
106
|
+
*/
|
|
107
|
+
export function verifyTotp(secretBase32, code, lastUsedStep, nowMilliseconds) {
|
|
108
|
+
const cleanCode = code.replace(/\s/g, "");
|
|
109
|
+
if (cleanCode.length !== TOTP_DIGITS)
|
|
110
|
+
return { ok: false, step: 0 };
|
|
111
|
+
const currentStep = Math.floor(nowMilliseconds / MILLISECONDS_PER_SECOND / TOTP_STEP_SECONDS);
|
|
112
|
+
for (let step = currentStep - TOTP_ALLOWED_DRIFT_STEPS; step <= currentStep + TOTP_ALLOWED_DRIFT_STEPS; step++) {
|
|
113
|
+
if (step <= lastUsedStep)
|
|
114
|
+
continue;
|
|
115
|
+
if (textsMatch(totpCodeAt(secretBase32, step), cleanCode))
|
|
116
|
+
return { ok: true, step: step };
|
|
117
|
+
}
|
|
118
|
+
return { ok: false, step: 0 };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The otpauth:// URI an authenticator app reads from the QR code (DR §8).
|
|
122
|
+
* @param username The account's username.
|
|
123
|
+
* @param secretBase32 The secret.
|
|
124
|
+
* @returns The URI.
|
|
125
|
+
*/
|
|
126
|
+
export function buildOtpauthUri(username, secretBase32) {
|
|
127
|
+
const label = encodeURIComponent(OTPAUTH_ISSUER + ":" + username);
|
|
128
|
+
return "otpauth://totp/" + label + "?secret=" + secretBase32 + "&issuer=" + encodeURIComponent(OTPAUTH_ISSUER) + "&algorithm=SHA1&digits=" + TOTP_DIGITS + "&period=" + TOTP_STEP_SECONDS;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* New recovery codes (`xxxx-xxxx`, base32 lower case) and their stored hashes.
|
|
132
|
+
* @returns The codes to show once, and their SHA-256 hashes to store.
|
|
133
|
+
*/
|
|
134
|
+
export function generateRecoveryCodes() {
|
|
135
|
+
const codes = [];
|
|
136
|
+
for (let i = 0; i < RECOVERY_CODE_COUNT; i++) {
|
|
137
|
+
const characters = encodeBase32(randomBytes(RECOVERY_CODE_BYTES)).toLowerCase();
|
|
138
|
+
codes.push(characters.substring(0, RECOVERY_CODE_HALF_LENGTH) + "-" + characters.substring(RECOVERY_CODE_HALF_LENGTH));
|
|
139
|
+
}
|
|
140
|
+
return { codes: codes, hashes: codes.map(hashRecoveryCode) };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Uses up a recovery code.
|
|
144
|
+
* @param code The code given (case and spaces ignored).
|
|
145
|
+
* @param hashes The account's unused code hashes.
|
|
146
|
+
* @returns The hashes left after removing the match, or null when no code matches.
|
|
147
|
+
*/
|
|
148
|
+
export function consumeRecoveryCode(code, hashes) {
|
|
149
|
+
const codeHash = hashRecoveryCode(code);
|
|
150
|
+
const index = hashes.findIndex((storedHash) => textsMatch(storedHash, codeHash));
|
|
151
|
+
if (index < 0)
|
|
152
|
+
return null;
|
|
153
|
+
return [...hashes.slice(0, index), ...hashes.slice(index + 1)];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The stored hash of a recovery code.
|
|
157
|
+
* @param code The code (normalized to lower case without spaces).
|
|
158
|
+
* @returns SHA-256 hex.
|
|
159
|
+
*/
|
|
160
|
+
function hashRecoveryCode(code) {
|
|
161
|
+
return sha256Hex(code.replace(/\s/g, "").toLowerCase());
|
|
162
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { AdminAccounts } from "./AdminAccounts.js";
|
|
2
|
+
import type { AdminClock } from "./AdminClock.js";
|
|
3
|
+
import type { AdminUserRecord } from "./AdminRecords.js";
|
|
4
|
+
import type { AdminStore } from "./AdminStore.js";
|
|
5
|
+
import type { AdminOpenIdConfig } from "../GameServerConfig.js";
|
|
6
|
+
/** The provider fields the console uses (the provider's own names, R32). */
|
|
7
|
+
export interface OpenIdDiscovery {
|
|
8
|
+
issuer: string;
|
|
9
|
+
authorization_endpoint: string;
|
|
10
|
+
token_endpoint: string;
|
|
11
|
+
jwks_uri: string;
|
|
12
|
+
}
|
|
13
|
+
/** How a callback ended: signed in, a second factor asked, or refused with a reason code for the sign-in page. */
|
|
14
|
+
export type OpenIdOutcome = {
|
|
15
|
+
kind: "signedIn";
|
|
16
|
+
user: AdminUserRecord;
|
|
17
|
+
note: string | null;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "needsSecondFactor";
|
|
20
|
+
pendingToken: string;
|
|
21
|
+
stage: "secondFactor" | "enrolSecondFactor";
|
|
22
|
+
user: AdminUserRecord;
|
|
23
|
+
note: string | null;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "refused";
|
|
26
|
+
reason: OpenIdRefusalReason;
|
|
27
|
+
};
|
|
28
|
+
/** Why a single sign-on was refused (the sign-in page shows #/sign-in?error=<reason>). */
|
|
29
|
+
export type OpenIdRefusalReason = "no-role" | "account-disabled" | "sign-in-failed";
|
|
30
|
+
/** The console's OpenID Connect client. */
|
|
31
|
+
export declare class OpenIdConnectClient {
|
|
32
|
+
/** admin.oidc. */
|
|
33
|
+
private readonly config;
|
|
34
|
+
/** The admin store (pending sign-ins, accounts). */
|
|
35
|
+
private readonly store;
|
|
36
|
+
/** The clock of the discovery cache and challenge expiries. */
|
|
37
|
+
private readonly clock;
|
|
38
|
+
/** The admin accounts. */
|
|
39
|
+
private readonly accounts;
|
|
40
|
+
/** The cached discovery document and when it was fetched, or null. */
|
|
41
|
+
private cachedDiscovery;
|
|
42
|
+
/** The provider's key set, per jwks_uri (jose caches the keys). */
|
|
43
|
+
private keySet;
|
|
44
|
+
/**
|
|
45
|
+
* Creates the client.
|
|
46
|
+
* @param config admin.oidc.
|
|
47
|
+
* @param store The admin store.
|
|
48
|
+
* @param clock The clock.
|
|
49
|
+
* @param accounts The admin accounts.
|
|
50
|
+
*/
|
|
51
|
+
constructor(config: AdminOpenIdConfig, store: AdminStore, clock: AdminClock, accounts: AdminAccounts);
|
|
52
|
+
/** True when admin.oidc.enabled. */
|
|
53
|
+
get isEnabled(): boolean;
|
|
54
|
+
/** The sign-in button label. */
|
|
55
|
+
get label(): string;
|
|
56
|
+
/**
|
|
57
|
+
* The provider's discovery document, cached for OIDC_DISCOVERY_CACHE_MS.
|
|
58
|
+
* @returns The document.
|
|
59
|
+
* @throws Error when the provider does not answer with one.
|
|
60
|
+
*/
|
|
61
|
+
discovery(): Promise<OpenIdDiscovery>;
|
|
62
|
+
/**
|
|
63
|
+
* Starts a sign-in (GET auth/oidc/start): stores the state, nonce and PKCE verifier for CHALLENGE_LIFETIME_MS and
|
|
64
|
+
* builds the provider's authorization URL.
|
|
65
|
+
* @param consoleOrigin The console origin (the redirect URI is below it).
|
|
66
|
+
* @returns Where to send the browser.
|
|
67
|
+
*/
|
|
68
|
+
startUrl(consoleOrigin: string): Promise<{
|
|
69
|
+
location: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Finishes a sign-in (GET auth/oidc/callback): checks the state, exchanges the code, verifies the ID token (issuer,
|
|
73
|
+
* audience, signature, nonce), then matches or creates the account and applies the two-factor rule (D26).
|
|
74
|
+
* @param query The callback's query.
|
|
75
|
+
* @returns The outcome.
|
|
76
|
+
*/
|
|
77
|
+
complete(query: URLSearchParams): Promise<OpenIdOutcome>;
|
|
78
|
+
/**
|
|
79
|
+
* Matches or creates the account of verified claims and decides the second factor.
|
|
80
|
+
* @param claims The verified ID token claims.
|
|
81
|
+
* @returns The outcome.
|
|
82
|
+
*/
|
|
83
|
+
private signInWithClaims;
|
|
84
|
+
/**
|
|
85
|
+
* Takes a started sign-in out of the store (single use).
|
|
86
|
+
* @param state The callback's state, or null.
|
|
87
|
+
* @returns The live record, or null for a missing, unknown, used or expired state.
|
|
88
|
+
*/
|
|
89
|
+
private consumeChallenge;
|
|
90
|
+
/**
|
|
91
|
+
* Exchanges the authorization code at the token endpoint (client_secret_post, PKCE verifier).
|
|
92
|
+
* @param code The authorization code.
|
|
93
|
+
* @param challenge The started sign-in.
|
|
94
|
+
* @returns The ID token.
|
|
95
|
+
* @throws Error when the provider refuses or answers without an ID token.
|
|
96
|
+
*/
|
|
97
|
+
private exchangeCode;
|
|
98
|
+
/**
|
|
99
|
+
* Verifies an ID token: signature against the provider's JWKS, issuer, audience (the client id) and nonce.
|
|
100
|
+
* @param idToken The ID token.
|
|
101
|
+
* @param expectedNonce The nonce of the started sign-in.
|
|
102
|
+
* @returns The claims.
|
|
103
|
+
* @throws Error when any check fails.
|
|
104
|
+
*/
|
|
105
|
+
private verifiedClaims;
|
|
106
|
+
/**
|
|
107
|
+
* The provider's key set (one per jwks_uri; jose fetches and caches the keys).
|
|
108
|
+
* @param jwksUri The provider's jwks_uri.
|
|
109
|
+
* @returns The key resolver.
|
|
110
|
+
*/
|
|
111
|
+
private keysOf;
|
|
112
|
+
/**
|
|
113
|
+
* The most powerful console role the claim maps to (admin.oidc.roleMapping "value=role").
|
|
114
|
+
* @param claims The verified claims.
|
|
115
|
+
* @returns The role, or null when no value maps.
|
|
116
|
+
*/
|
|
117
|
+
private mappedRoleOf;
|
|
118
|
+
/**
|
|
119
|
+
* The account of the claims: the one with this subject, else the one with this verified email.
|
|
120
|
+
* @param claims The verified claims.
|
|
121
|
+
* @returns The stored account, or null.
|
|
122
|
+
*/
|
|
123
|
+
private findAccount;
|
|
124
|
+
/**
|
|
125
|
+
* Links an existing account to the subject and applies a mapped role, except that the last enabled owner keeps the
|
|
126
|
+
* owner role (DR §5); that refusal is logged and returned as a note for the audit record.
|
|
127
|
+
* @param stored The account.
|
|
128
|
+
* @param claims The verified claims.
|
|
129
|
+
* @param mappedRole The mapped role, or null to keep the account's role.
|
|
130
|
+
* @returns The account as stored now, and the note.
|
|
131
|
+
*/
|
|
132
|
+
private updateAccount;
|
|
133
|
+
/**
|
|
134
|
+
* Creates the account of a first single sign-on (no password: SSO only).
|
|
135
|
+
* @param claims The verified claims.
|
|
136
|
+
* @param role The mapped role.
|
|
137
|
+
* @returns The new account.
|
|
138
|
+
*/
|
|
139
|
+
private createAccount;
|
|
140
|
+
/**
|
|
141
|
+
* A username not taken yet: the base, else the base with a number.
|
|
142
|
+
* @param base A valid username base.
|
|
143
|
+
* @returns The username.
|
|
144
|
+
*/
|
|
145
|
+
private freeUsername;
|
|
146
|
+
}
|