mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
[plus] discord role verifier to grant RR plus
This commit is contained in:
@@ -101,6 +101,30 @@ export interface Account {
|
||||
* `runx admin grant-moderator`. Absent/false means no role.
|
||||
*/
|
||||
isModerator?: boolean
|
||||
/**
|
||||
* Whether this account has Rec Room Plus — the paid tier the client's API calls a
|
||||
* `CampusCard`. Nothing SELLS one here. Absent/false means no Plus.
|
||||
*
|
||||
* This flag ALONE is what confers it, and it stands on its own: two things set it, and
|
||||
* neither is a precondition of the other.
|
||||
*
|
||||
* - the website's benefits claim (`www` `POST /api/benefits/claim`), where a player
|
||||
* proves a qualifying role in the community Discord. That path also links their
|
||||
* Discord identity into `platform_account` as a `PlatformType.Discord` row — but the
|
||||
* link exists to keep the CLAIM once-only per Discord user, not to justify the flag.
|
||||
* - an operator, via `runx admin grant-plus`, with no Discord anywhere in sight.
|
||||
*
|
||||
* So never read a Discord link as a precondition for Plus, and never revoke one because
|
||||
* the other is missing: a manually granted account has `hasPlus` and no link at all, and
|
||||
* that is a normal, supported state.
|
||||
*
|
||||
* Nothing reads this per request. `auth` stamps it into every token it mints as the
|
||||
* `rn.plus` claim, and `econ` decides the CampusCard and the subscriber discount from
|
||||
* that claim alone — so setting it takes effect on the account's NEXT login, not
|
||||
* immediately. Tokens last a day and the client never refreshes them, so that lag is
|
||||
* real: the website's claim page warns about it, and so does `grant-plus`.
|
||||
*/
|
||||
hasPlus?: boolean
|
||||
}
|
||||
|
||||
interface AccountRow {
|
||||
|
||||
@@ -5,6 +5,39 @@
|
||||
* the tsconfig sets `isolatedModules` (which disallows `const enum` across files).
|
||||
*/
|
||||
|
||||
/**
|
||||
* PlatformType, the client's platform enum. Declaration order is wire order. The
|
||||
* `platform` form field is posted as the integer; a token's `platform` claim carries it
|
||||
* too. `auth` re-exports this as the source for its OpenAPI schema and description.
|
||||
*
|
||||
* A plain `as const` object rather than an `enum` like its neighbours, and deliberately
|
||||
* so: `auth` builds `PlatformTypeSchema`'s description by walking `Object.entries`, and a
|
||||
* numeric TS enum also emits a REVERSE mapping (`{ '0': 'Steam', Steam: 0, … }`), which
|
||||
* would double every member in the generated spec.
|
||||
*
|
||||
* Everything from `Steam` to `Pico` is a real Rec Room client platform, numbered by the
|
||||
* client. `Discord` is OURS — it is not a platform anyone signs in from, and the client
|
||||
* never sends it. It exists so a verified Discord identity can be stored as an account
|
||||
* link like any other external identity (see `auth`'s platform-db and the website's
|
||||
* benefits claim); it sits at 101, well clear of the client's range, so a future client
|
||||
* platform can be added without colliding with it.
|
||||
*/
|
||||
export const PlatformType = {
|
||||
All: -1,
|
||||
Steam: 0,
|
||||
Oculus: 1,
|
||||
PlayStation: 2,
|
||||
Xbox: 3,
|
||||
RecNet: 4,
|
||||
IOS: 5,
|
||||
GooglePlay: 6,
|
||||
Standalone: 7,
|
||||
Pico: 8,
|
||||
Discord: 101,
|
||||
} as const
|
||||
|
||||
export type PlatformType = (typeof PlatformType)[keyof typeof PlatformType]
|
||||
|
||||
/** The kind of a room instance (live session), matching the client's `RoomInstanceType`. */
|
||||
export enum RoomInstanceType {
|
||||
Public = 0,
|
||||
|
||||
@@ -12,9 +12,21 @@
|
||||
const ITERATIONS = 100_000
|
||||
|
||||
const b64 = (bytes: Uint8Array): string => btoa(String.fromCharCode(...bytes))
|
||||
const fromB64 = (s: string): Uint8Array => Uint8Array.from(atob(s), (ch) => ch.charCodeAt(0))
|
||||
const fromB64 = (s: string): Uint8Array<ArrayBuffer> =>
|
||||
Uint8Array.from(atob(s), (ch) => ch.charCodeAt(0))
|
||||
|
||||
async function deriveBits(password: string, salt: Uint8Array): Promise<Uint8Array> {
|
||||
/**
|
||||
* The salt is `Uint8Array<ArrayBuffer>` rather than a bare `Uint8Array` because the latter
|
||||
* is `Uint8Array<ArrayBufferLike>`, which admits a `SharedArrayBuffer` — and the DOM lib's
|
||||
* `BufferSource` does not. Both callers already produce a plain-ArrayBuffer view
|
||||
* (`getRandomValues` and `fromB64`), so this only writes down what was always true; without
|
||||
* it, any worker whose tsconfig includes the DOM lib (`www`, for its React client) fails to
|
||||
* compile on the `deriveBits` call below.
|
||||
*/
|
||||
async function deriveBits(
|
||||
password: string,
|
||||
salt: Uint8Array<ArrayBuffer>
|
||||
): Promise<Uint8Array<ArrayBuffer>> {
|
||||
const keyMaterial = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
new TextEncoder().encode(password),
|
||||
|
||||
Reference in New Issue
Block a user