Players are verified when they're in the configured Steam group (public memberslistxml, refetched every 10 minutes, kept on failure) or have an unexpired override row in gv_verified, written by the web panel. Settings and data live in CS2-SimpleAdmin's MySQL database (gv_verified, gv_settings, gv_migrations), reached through its API. - css_gv_group <groupId64|0>, css_gv_enable, css_gv_disable (@css/root): verification runs only when enabled with a group set. - css_verify <target>: why a player is or isn't verified. Admin and above (by immunity from SimpleAdmin's groups) plus Guardian, unless admin_overrides.json says otherwise. - groupverify:api (shared/GroupVerifyApi): IsVerified, GetStatus, IsActive and OnVerifiedChanged. The API object lives in the shared assembly, so it survives hot reloads of GroupVerify.
227 lines
12 KiB
Markdown
227 lines
12 KiB
Markdown
# GroupVerify
|
|
|
|
Marks players as **verified** when they're in our Steam group, or when staff have given them a
|
|
manual override on the web panel. It gives out nothing itself: other plugins ask it through its
|
|
API (`GroupVerifyApi`, see [For other plugins](#for-other-plugins)) and hand out their own perks.
|
|
|
|
Everything lives in CS2-SimpleAdmin's MySQL database, reached through SimpleAdmin's API
|
|
(`GetConnectionString()`), so there's no config file. Needs CS2-SimpleAdmin on MySQL; on SQLite,
|
|
or without SimpleAdmin, it logs an error and verifies nobody.
|
|
|
|
## Turning it on
|
|
|
|
Verification runs only when it's **enabled and has a Steam group set**. Otherwise nobody is
|
|
verified, including players with an override, and other plugins see `IsActive == false`. A new
|
|
install is enabled with no group, so it's off until you set one:
|
|
|
|
```
|
|
css_gv_group 103582791429521408 set the group (its 64-bit ID) and fetch its members now
|
|
css_gv_group 0 clear the group: verification is off
|
|
css_gv_group show whether it's on, the group, and how old the list is
|
|
css_gv_disable turn it off, keeping the group
|
|
css_gv_enable turn it back on
|
|
```
|
|
|
|
These need `@css/root` (an `admin_overrides.json` entry for a command changes that) and work from
|
|
the server console and RCON. Changes are saved to `gv_settings` and take effect at once: turning
|
|
it off tells other plugins every verified player stopped being verified, and turning it on (or
|
|
changing the group) fetches the member list straight away.
|
|
|
|
The group ID is the number in `<groupID64>` at
|
|
`https://steamcommunity.com/groups/<name>/memberslistxml/?xml=1`. `css_gv_group` refuses anything
|
|
that isn't a Steam group ID.
|
|
|
|
## How it works
|
|
|
|
- **Steam group.** The group's public member list
|
|
(`https://steamcommunity.com/gid/<group_id64>/memberslistxml/?xml=1`, every `nextPageLink` page)
|
|
is fetched off the game thread every `refresh_minutes`. A failed or empty fetch keeps the old
|
|
list. Each good fetch is saved to `gv_verified` as `source = 'group'` rows, and the rows of
|
|
anyone who left are deleted; at startup those rows stand in for the list until Steam answers.
|
|
- **Overrides.** `gv_verified` is reread every `db_reload_seconds`, so web panel changes show up
|
|
without a restart. Expired rows are ignored, and deleted on each reload.
|
|
- **Verified** = in the group list, **or** an unexpired row with `override` = 1.
|
|
- Online players are checked when they connect (once Steam has authorized them), after each group
|
|
fetch, after each database reload and after each settings command. Other plugins get
|
|
`OnVerifiedChanged` only when a player's status changes. Their state is dropped when they
|
|
disconnect.
|
|
|
|
## `css_verify` (`!verify`)
|
|
|
|
`css_verify <target>`: `@all`, `@t`, `@ct`, `@spec`, `@alive`, `@dead`, `@me`, `#userid`, a name
|
|
(an exact match first, then a partial one; ambiguous names are refused), or a SteamID
|
|
(SteamID64, `STEAM_X:Y:Z` or `[U:1:N]`), which works for offline players too. No target means
|
|
`@me`. Works from the server console.
|
|
|
|
If the group list or the database was last loaded more than `min_forced_refresh_seconds` ago,
|
|
both are brought up to date first. Then the targets are re-evaluated, and the answer goes to chat:
|
|
one line per player for up to 5 targets (verified by Steam group, override with its expiry, or
|
|
not verified), a summary for more, and how old the group list and overrides are. Every use is
|
|
logged through SimpleAdmin's `LogCommand` (server log and its Discord log) and the plugin's log.
|
|
|
|
**Who can use it:** Admin and above, plus Guardian. A player qualifies when one of their
|
|
SimpleAdmin groups has at least the immunity of the `verify_min_rank` group (`#rank/admin`), or is
|
|
listed in `verify_extra_ranks` (`#rank/guardian`). Groups are read from the database the same way
|
|
our SimpleAdmin fork works out a rank (`PermissionManager.GetRank`): an unexpired `sa_admins` row for
|
|
this server, whose `sa_admins_flags` entry names an `sa_groups` group that applies to this server.
|
|
So promoting someone on the web panel takes effect at once, and a new rank above Admin passes
|
|
by its immunity without changes here.
|
|
|
|
An `admin_overrides.json` entry for `css_verify` replaces that check: CSS enforces the override's
|
|
flags before the command runs, and the rank check is skipped.
|
|
|
|
## Database
|
|
|
|
Created at load from the embedded `Migrations/*.sql`, tracked in `gv_migrations` (kept apart from
|
|
SimpleAdmin's `sa_migrations`). Times are wall-clock values in the `timezone` setting, which must
|
|
match SimpleAdmin's `Timezone` (as the web panel already assumes for SimpleAdmin's own tables).
|
|
|
|
### `gv_verified`
|
|
|
|
| column | type | notes |
|
|
|--------------|----------------------------|---------------------------------------------------------------|
|
|
| `steamid64` | `bigint` | primary key, with `source` |
|
|
| `source` | `enum('group','manual')` | `group`: written by the plugin. `manual`: the web panel's. |
|
|
| `override` | `tinyint(1)` | 1 verifies the player whether or not they're in the group |
|
|
| `expires_at` | `datetime NULL` | NULL = never. Expired rows are ignored, then deleted. |
|
|
| `added_by` | `varchar(128)` | free text: who added it (`steam-group` for group rows) |
|
|
| `created_at` | `datetime` | first added |
|
|
| `updated_at` | `datetime` | group rows: last seen in the list |
|
|
|
|
One row per player and source, so a manual override and a group row never overwrite each other.
|
|
`override` is backticked in the plugin's SQL; do the same, it's close to a reserved word.
|
|
|
|
The web panel only writes `manual` rows. To add or change an override:
|
|
|
|
```sql
|
|
INSERT INTO gv_verified (steamid64, source, `override`, expires_at, added_by, created_at, updated_at)
|
|
VALUES (76561198000000000, 'manual', 1, '2026-12-31 23:59:00', 'astra (76561198...)', NOW(), NOW())
|
|
ON DUPLICATE KEY UPDATE `override` = 1, expires_at = VALUES(expires_at),
|
|
added_by = VALUES(added_by), updated_at = VALUES(updated_at);
|
|
```
|
|
|
|
`NOW()` is only right if MySQL's time zone matches `timezone`; otherwise pass the time from the
|
|
panel. To remove one: `DELETE FROM gv_verified WHERE steamid64 = ? AND source = 'manual'`. Leave
|
|
`group` rows alone: the next fetch rewrites them, and they never verify anyone on their own.
|
|
|
|
### `gv_settings`
|
|
|
|
`name` → `value` (both strings). Missing names are added with their defaults at each reload.
|
|
`enabled` and `group_id64` are normally set with the commands above. Anything edited by hand
|
|
applies at the next reload (`db_reload_seconds`), timers included.
|
|
|
|
| name | default | meaning |
|
|
|------------------------------|------------------|-----------------------------------------------------------------|
|
|
| `enabled` | `1` | `0` turns verification off (`css_gv_enable`/`css_gv_disable`) |
|
|
| `group_id64` | `0` | the Steam group's 64-bit ID; `0` turns it off (`css_gv_group`) |
|
|
| `refresh_minutes` | `10` | how often the member list is fetched |
|
|
| `min_forced_refresh_seconds` | `60` | `css_verify` refreshes only data older than this |
|
|
| `db_reload_seconds` | `60` | how often `gv_settings` and `gv_verified` are reread |
|
|
| `timezone` | `UTC` | time zone of the `datetime` columns; match SimpleAdmin's |
|
|
| `verify_min_rank` | `#rank/admin` | `css_verify` needs this group's immunity or higher |
|
|
| `verify_extra_ranks` | `#rank/guardian` | comma-separated groups that may also use `css_verify` |
|
|
|
|
## For other plugins
|
|
|
|
GroupVerify registers the capability `groupverify:api` (`IGroupVerifyApi`), from
|
|
`GroupVerifyApi.dll`, which is installed in `addons/counterstrikesharp/shared/GroupVerifyApi/`.
|
|
Use it on the game thread:
|
|
|
|
- `IsVerified(player)` / `IsVerified(steamId64)`: verified right now. False for bots, players
|
|
Steam hasn't authorized yet, and everyone while `IsActive` is false.
|
|
- `GetStatus(steamId64)`: a `VerifyStatus` with `Verified`, `InGroup`, `HasOverride` and
|
|
`OverrideExpires` (null when it never ends).
|
|
- `IsActive`: GroupVerify is loaded, enabled, has a group and has read its database.
|
|
- `OnVerifiedChanged(player, status)`: fired when an online player becomes verified (including
|
|
just after connecting) or stops being verified (left the group, override ran out, or
|
|
verification was turned off). Not fired on disconnect.
|
|
|
|
In the other plugin, copy `GroupVerifyApi.dll` from the server's `shared/GroupVerifyApi/` (or
|
|
`GroupVerifyApi/bin/Release/net10.0/` here) to its `lib/`, and reference it compile-only. It must
|
|
not end up in that plugin's own folder, or that plugin gets its own copy of the types and the
|
|
capability won't match:
|
|
|
|
```xml
|
|
<Reference Include="GroupVerifyApi">
|
|
<HintPath>lib/GroupVerifyApi.dll</HintPath>
|
|
<Private>false</Private>
|
|
</Reference>
|
|
```
|
|
|
|
```csharp
|
|
using GroupVerifyApi;
|
|
|
|
private IGroupVerifyApi? _verify;
|
|
|
|
public override void OnAllPluginsLoaded(bool hotReload)
|
|
{
|
|
try
|
|
{
|
|
_verify = IGroupVerifyApi.Capability.Get();
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
}
|
|
if (_verify == null)
|
|
{
|
|
Logger.LogError("GroupVerify isn't installed, so nobody gets verified perks");
|
|
return;
|
|
}
|
|
_verify.OnVerifiedChanged += OnVerifiedChanged;
|
|
}
|
|
|
|
public override void Unload(bool hotReload)
|
|
{
|
|
if (_verify != null)
|
|
_verify.OnVerifiedChanged -= OnVerifiedChanged;
|
|
}
|
|
|
|
// React to changes...
|
|
private void OnVerifiedChanged(CCSPlayerController player, VerifyStatus status)
|
|
{
|
|
if (status.Verified)
|
|
GiveTag(player);
|
|
else
|
|
RemoveTag(player);
|
|
}
|
|
|
|
// ...or just ask whenever it matters.
|
|
private HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info)
|
|
{
|
|
if (@event.Userid is { IsValid: true } player && _verify?.IsVerified(player) == true)
|
|
GiveVerifiedLoadout(player);
|
|
return HookResult.Continue;
|
|
}
|
|
```
|
|
|
|
Event handlers run one at a time, and one that throws is logged by GroupVerify without stopping
|
|
the others. Make them idempotent: after GroupVerify is hot-reloaded, it fires `true` again for
|
|
everyone verified.
|
|
|
|
**Hot reload.** Unlike SimpleAdmin's, this API survives reloads of GroupVerify. The object handed
|
|
out is `GroupVerifyBridge.Instance`, which lives in the shared assembly, not in the plugin. CSS
|
|
keeps the first capability supplier registered for the whole server run, and GroupVerify registers
|
|
only once, so `Capability.Get()` keeps returning that same object. GroupVerify attaches to it on
|
|
load and detaches on unload, and event subscriptions stay on it. While GroupVerify is unloaded,
|
|
`IsActive` is false and nobody is verified. Changing `GroupVerifyApi` itself needs a server
|
|
restart, and its plugins rebuilt if the interface changed.
|
|
|
|
`IGroupVerifyBackend`, and `GroupVerifyBridge`'s `Attach`, `Detach` and `RaiseVerifiedChanged`,
|
|
are for GroupVerify only.
|
|
|
|
## Building and releasing
|
|
|
|
`cd plugins/GroupVerify && ../../build.sh` builds into `compiled/GroupVerify/`.
|
|
`./release.sh v<ModuleVersion>` (needs `FORGEJO_TOKEN`) publishes `GroupVerify-<tag>.tar.gz`, laid
|
|
out like `game/csgo/`, to the `cs2/GroupVerify` repo on git.zio.sh. It bundles `MySqlConnector.dll`
|
|
(the same version as SimpleAdmin's) and its `Microsoft.Extensions.*` abstractions in the plugin
|
|
folder, and `GroupVerifyApi` in `shared/GroupVerifyApi/`.
|
|
|
|
`build.sh` doesn't put `GroupVerifyApi.dll` in `compiled/GroupVerify/` (it doesn't belong in the
|
|
plugin folder). Its build is in `GroupVerifyApi/bin/Release/net10.0/`; copy it to
|
|
`shared/GroupVerifyApi/` by hand when deploying without a release.
|
|
|
|
`lib/CS2-SimpleAdminApi.dll` is a compile-only copy of the server's `shared/CS2-SimpleAdminApi`.
|
|
|
|
Never hot-reload CS2-SimpleAdmin while this runs: CSS keeps handing out the old instance's API
|
|
until the server restarts. Hot-reloading GroupVerify is fine (see Hot reload above).
|