diff --git a/docs/content/rctf/configuration.md b/docs/content/rctf/configuration.md index e070e96..502f264 100644 --- a/docs/content/rctf/configuration.md +++ b/docs/content/rctf/configuration.md @@ -81,6 +81,7 @@ Configuration for advanced users - sane defaults are automatically set by the in | `proxy.trust` | _(none)_ | yes | `false` | boolean, string, string array, or integer | X-Forwarded-For trust: the trust parameter to [proxy-addr](https://www.npmjs.com/package/proxy-addr) | | `loginTimeout` | `RCTF_LOGIN_TIMEOUT` | yes | 3600000 | integer | lifetime of registration, email update, and recovery links, in milliseconds | | `userMembers` | `RCTF_USER_MEMBERS` | yes | `true` | boolean | whether to allow a user to provide emails for individual members | +| `registrationsEnabled` | _(none)_ | yes | `true` | boolean | whether to allow new teams creation | | `database.migrate` | `RCTF_DATABASE_MIGRATE` | yes | `never` | `before` \| `only` \| `never` | how to run postgreSQL migrations. [documentation](management/migration.md) | | `instanceType` | `RCTF_INSTANCE_TYPE` | yes | `all` | `all` \| `frontend` \| `leaderboard` | what type of instance to run. [documentation](management/scaling.md) | | `challengeProvider` | _(none)_ | yes | `database` | provider | provider for challenges. [documentation](providers/challenges/index.md) | diff --git a/packages/api-types/src/responses/badRegistrationsDisabled.yaml b/packages/api-types/src/responses/badRegistrationsDisabled.yaml new file mode 100644 index 0000000..3ac4686 --- /dev/null +++ b/packages/api-types/src/responses/badRegistrationsDisabled.yaml @@ -0,0 +1,2 @@ +status: 400 +message: The registrations are disabled. diff --git a/packages/api-types/src/responses/goodClientConfig.yml b/packages/api-types/src/responses/goodClientConfig.yml index 492e46e..d1b994d 100644 --- a/packages/api-types/src/responses/goodClientConfig.yml +++ b/packages/api-types/src/responses/goodClientConfig.yml @@ -49,6 +49,8 @@ data: type: string emailEnabled: type: boolean + registrationsEnabled: + type: boolean ctftime: type: object properties: diff --git a/packages/api-types/src/routes/auth/register/post.yml b/packages/api-types/src/routes/auth/register/post.yml index e8b6d53..926476a 100644 --- a/packages/api-types/src/routes/auth/register/post.yml +++ b/packages/api-types/src/routes/auth/register/post.yml @@ -28,3 +28,4 @@ responses: - badKnownCtftimeId - badKnownEmail - badKnownName + - badRegistrationsDisabled diff --git a/packages/client/src/api/auth.js b/packages/client/src/api/auth.js index 3b3a78e..cead164 100644 --- a/packages/client/src/api/auth.js +++ b/packages/client/src/api/auth.js @@ -82,6 +82,7 @@ export const register = async ({ case 'badEmail': case 'badKnownEmail': case 'badCompetitionNotAllowed': + case 'badRegistrationsDisabled': return { errors: { email: resp.message, diff --git a/packages/client/src/app.js b/packages/client/src/app.js index d599109..c74b484 100644 --- a/packages/client/src/app.js +++ b/packages/client/src/app.js @@ -27,19 +27,25 @@ import { ToastProvider } from './components/toast' import { navigateRef } from './history-hack' import { hasChallsReadPermission } from './util/permissions' +import config from './config' const LoggedOutRedir = const LoggedInRedir = function App({ classes }) { const loggedOut = !localStorage.token + const registerItems = config.registrationsEnabled + ? [ + { + element: , + path: '/register', + name: 'Register', + }, + ] + : [] const loggedOutPaths = [ - { - element: , - path: '/register', - name: 'Register', - }, + ...registerItems, { element: , path: '/login', diff --git a/packages/server/src/api/auth/register.js b/packages/server/src/api/auth/register.js index b4f87f8..fca3cd4 100644 --- a/packages/server/src/api/auth/register.js +++ b/packages/server/src/api/auth/register.js @@ -10,6 +10,10 @@ import { getUserByNameOrEmail } from '../../database/users' import { sendVerification } from '../../email' export default makeFastifyRoute(authRegisterPost, async ({ req, res }) => { + if (!config.registrationsEnabled) { + return res.badRegistrationsDisabled() + } + let email let ctftimeId if (req.body.ctftimeToken !== undefined) { diff --git a/packages/server/src/config/client.ts b/packages/server/src/config/client.ts index de80622..e94545a 100644 --- a/packages/server/src/config/client.ts +++ b/packages/server/src/config/client.ts @@ -15,6 +15,7 @@ const config: ClientConfig = { faviconUrl: server.faviconUrl, emailEnabled: server.email != null, userMembers: server.userMembers, + registrationsEnabled: server.registrationsEnabled, ctftime: server.ctftime == null ? undefined diff --git a/packages/server/src/config/load.ts b/packages/server/src/config/load.ts index 5e3c26a..f87f471 100644 --- a/packages/server/src/config/load.ts +++ b/packages/server/src/config/load.ts @@ -131,6 +131,7 @@ export const defaultConfig: PartialDeep = { }, instanceType: 'all', userMembers: true, + registrationsEnabled: true, sponsors: [], homeContent: '', faviconUrl: 'https://redpwn.storage.googleapis.com/branding/rctf-favicon.ico', diff --git a/packages/server/src/config/types.ts b/packages/server/src/config/types.ts index 58dc142..75f13d7 100644 --- a/packages/server/src/config/types.ts +++ b/packages/server/src/config/types.ts @@ -49,6 +49,8 @@ export interface ServerConfig { } userMembers: boolean + registrationsEnabled: boolean + sponsors: Sponsor[] homeContent: string ctfName: string @@ -98,6 +100,7 @@ export type ClientConfig = Pick< | 'startTime' | 'endTime' | 'userMembers' + | 'registrationsEnabled' | 'faviconUrl' > & { emailEnabled: boolean diff --git a/packages/server/test/integration/auth.js b/packages/server/test/integration/auth.js index af80b48..a42dd71 100644 --- a/packages/server/test/integration/auth.js +++ b/packages/server/test/integration/auth.js @@ -10,6 +10,7 @@ import { goodRegister, goodToken, goodUserUpdate, + badRegistrationsDisabled, } from '@rctf/api-types/responses' import * as auth from '../../src/auth' @@ -139,3 +140,18 @@ test('succeeds with goodUserUpdate', async () => { expect(respUser.email).toBe(testUser.email) expect(respUser.division).toBe(nextUser.division) }) + +test('fails with badRegistrationsDisabled', async () => { + const oldRegistrations = config.registrationsEnabled + config.registrationsEnabled = false + + const resp = await request(app.server) + .post(process.env.API_ENDPOINT + '/auth/register') + .send({ + ...testUser, + }) + .expect(badRegistrationsDisabled.status) + + expect(resp.body.kind).toBe('badRegistrationsDisabled') + config.registrationsEnabled = oldRegistrations +})