Compare commits

..
Author SHA1 Message Date
Void
a4942eef86 auth: register failures in plain words; /register goes to the account service when the switch is on
All checks were successful
Build pull request / Build pull request (pull_request) Successful in 3m9s
The previous commit carried only the fallback split. This one carries the rest of Libby's two blockers and Heimdall's decision:

- config.json keeps only the registration block over dev's; clientDefaultServer is dev's again (the gamecult.org default is #86).
- registerErrorTrouble maps a register error code to registerTroubleMessage (new troubles: password-weak, password-short, unknown); PasswordRegisterForm shows those, never the server's text.
- RegisterRoute: switch on, /register offers the account service link and redirects there; switch off, the in-client form.
- Tests: registerErrors.test.ts (code to words, no Matrix code leaks), RegisterRoute.test.tsx (on, off, unusable url).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 00:25:46 +00:00
Void
4148a52c47 auth: Libby's read: fallback split out, register errors in plain words, /register goes to the account service when the switch is on
- The gamecult.org default homeserver moves to its own PR (#86); config.json and clientDefaultServer are dev's again.
- PasswordRegisterForm no longer shows a server's own error text or the old "Failed to register" lines: registerErrorTrouble maps the error code to registerTroubleMessage (new troubles for weak and short passwords and for the unknown case). A consumer-side test goes through the code to the words and checks no Matrix code leaks.
- Per Heimdall: with the Register switch on, /register is RegisterRoute, which sends a stranger to the account service's page (one way to register); with it off the in-client form stays. Tested through the component, rendered on and off and with an unusable url.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 00:25:46 +00:00
Void
3851d0302b auth: sign-up return page renders; gamecult.org is the default homeserver
/signed-up/ sat inside AuthLayout, which discovers a homeserver and sends paths it does
not know to sign in, so it never rendered. The signed-out routes move to authRoutes.tsx
with the return page beside the layout (its own card, no discovery); a test matches the
real route table and renders the page. Drop the visible notice version from the entry
(it stays in registrationEntry's data and tests) and the unused getSignedUpPath.

clientDefaultServer falls back to gamecult.org, and the repo config.json lists it first
and defaults to it; a config that names its own list (the production pin) is unaffected.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 00:25:46 +00:00
Void
408d47a53d auth: Register entry beside Sign in, behind a config switch that is off
config.registration {enabled, url}: off by default and in config.json. When on with a
http(s) url, the sign-in page shows Register (a link to the account service's own page)
with the consent sentence and its version, both read from trainingNotice.ts
(new TRAINING_NOTICE_VERSION shared with the room notice). /signed-up/ is the return
page: it says to follow the steps from the sign-up page and promises nothing about mail;
with the switch off it redirects to sign in. Off keeps the existing Register link.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 00:25:46 +00:00
5 changed files with 7 additions and 51 deletions

View file

@ -1,13 +1,6 @@
{
"defaultHomeserver": 0,
"homeserverList": [
"gamecult.org",
"converser.eu",
"matrix.org",
"mozilla.org",
"unredacted.org",
"xmr.se"
],
"defaultHomeserver": 1,
"homeserverList": ["converser.eu", "matrix.org", "mozilla.org", "unredacted.org", "xmr.se"],
"allowCustomHomeservers": true,
"featuredCommunities": {
"openAsDefault": false,

View file

@ -1,20 +0,0 @@
import { describe, expect, it } from 'vitest';
import { ClientConfig, clientDefaultServer } from './useClientConfig';
import config from '../../../config.json';
describe('default homeserver', () => {
it('is gamecult.org when the config names none', () => {
expect(clientDefaultServer({})).toBe('gamecult.org');
expect(clientDefaultServer({ homeserverList: [] })).toBe('gamecult.org');
});
it('is the listed server the config points at', () => {
expect(
clientDefaultServer({ homeserverList: ['chat.gamecult.org'], defaultHomeserver: 0 })
).toBe('chat.gamecult.org');
});
it('is gamecult.org in the repo config.json too', () => {
expect(clientDefaultServer(config as ClientConfig)).toBe('gamecult.org');
});
});

View file

@ -53,7 +53,7 @@ export function useClientConfig(): ClientConfig {
}
export const clientDefaultServer = (clientConfig: ClientConfig): string =>
clientConfig.homeserverList?.[clientConfig.defaultHomeserver ?? 0] ?? 'gamecult.org';
clientConfig.homeserverList?.[clientConfig.defaultHomeserver ?? 0] ?? 'matrix.org';
export const clientAllowedServer = (clientConfig: ClientConfig, server: string): boolean => {
const { homeserverList, allowCustomHomeservers } = clientConfig;

View file

@ -1,6 +1,5 @@
import { describe, expect, it } from 'vitest';
import { registrationEntry } from './registrationEntry';
import type { ClientConfig } from '../../hooks/useClientConfig';
import {
PREVIEW_NOTICE_TEXT,
TRAINING_NOTICE_TEXT,
@ -18,7 +17,7 @@ describe('registration entry', () => {
});
it('ships off in config.json', () => {
expect(registrationEntry(config as ClientConfig)).toBeUndefined();
expect(registrationEntry(config)).toBeUndefined();
expect(config.registration.enabled).toBe(false);
});
@ -45,13 +44,4 @@ describe('registration entry', () => {
const entry = registrationEntry({ previewDemo: true, registration: { enabled: true, url } });
expect(entry?.notice).toBe(PREVIEW_NOTICE_TEXT);
});
it('accepts http only for a local development host', () => {
const on = (u: string) => registrationEntry({ registration: { enabled: true, url: u } });
expect(on('http://localhost:8080/register')?.url).toBe('http://localhost:8080/register');
expect(on('http://127.0.0.1:8080/register')).toBeDefined();
expect(on('http://accounts.example/register')).toBeUndefined();
expect(on('http://localhost.evil.example/register')).toBeUndefined();
expect(on('https://accounts.example/register')).toBeDefined();
});
});

View file

@ -3,24 +3,17 @@ import { TRAINING_NOTICE_VERSION, trainingNoticeText } from '../../utils/trainin
export type RegistrationEntry = { url: string; notice: string; noticeVersion: number };
// Plain http is for a developer's own machine only; a stranger's sign-up page is https.
const isLocalHost = (hostname: string): boolean =>
hostname === 'localhost' ||
hostname.endsWith('.localhost') ||
hostname === '127.0.0.1' ||
hostname === '[::1]';
/**
* The sign-up entry the sign-in page may show: the account service's registration page, when the
* switch is on and the configured url is an https address (http only for localhost). Anything else is no entry. The
* switch is on and the configured url is a plain http(s) address. Anything else is no entry. The
* consent sentence and its version are trainingNotice.ts's, the ones a room shows.
*/
export const registrationEntry = (config: ClientConfig): RegistrationEntry | undefined => {
const { enabled, url } = config.registration ?? {};
if (enabled !== true || typeof url !== 'string') return undefined;
try {
const { protocol, hostname } = new URL(url);
if (protocol !== 'https:' && !(protocol === 'http:' && isLocalHost(hostname))) return undefined;
const { protocol } = new URL(url);
if (protocol !== 'https:' && protocol !== 'http:') return undefined;
} catch {
return undefined;
}