Skip to content

Commit a3bed8b

Browse files
Hide /idp Create Account button in single-user mode (JavaScriptSolidServer#290)
In single-user mode pod creation is disabled — POST /.pods and GET /idp/register both return 403. The /idp landing kept the Create Account button anyway, so a user clicking it on (e.g.) melvin.me landed on a Registration Disabled error page. Thread `singleUser` from idp/index.js into landingPage(ctx). When the flag is set: - Hide the Create Account button entirely. - Subtitle becomes "Single-user pod — sign in from any Solid app." - Sign-in note opens with "Sign in" rather than "Already have an account?" since registration isn't an option here. Pilot stays named as the example client. Pure rendering change — no new routes, no new behaviour, just stops the landing from offering a button it can't honour. Multi-user deployments are unchanged. Test: new "Single-user mode landing" describe block spins a server with `singleUser: true`, asserts the body has no Create Account button or /idp/register link, and confirms the subtitle reflects the single-user shape. Pilot link is still asserted present.
1 parent 3d56094 commit a3bed8b

3 files changed

Lines changed: 67 additions & 7 deletions

File tree

src/idp/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export async function idpPlugin(fastify, options) {
183183
// Pairs with the /idp/auth guard above so a human visitor lands here
184184
// rather than on a raw OIDC error.
185185
fastify.get('/idp', async (request, reply) => {
186-
return reply.type('text/html').send(landingPage({ baseUri: issuer }));
186+
return reply.type('text/html').send(landingPage({ baseUri: issuer, singleUser }));
187187
});
188188

189189
// Token sub-paths

src/idp/views.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -555,12 +555,15 @@ export function errorPage(title, message) {
555555
*
556556
* The OIDC authorization endpoint (/idp/auth) requires a client_id; opening
557557
* /idp manually used to drop the user into a raw OIDC error. This page is
558-
* the human-navigable entry point — Create Account is wired up; Sign In is
559-
* intentionally a description for now (PR-B / #286 will introduce a
560-
* standalone /idp/login form).
558+
* the human-navigable entry point.
559+
*
560+
* In single-user mode (`ctx.singleUser`) the Create Account button is
561+
* suppressed — pod creation is disabled and the button would lead to a
562+
* 403. The sign-in note still names pilot as the example client.
561563
*/
562564
export function landingPage(ctx = {}) {
563565
const issuer = ctx.baseUri || '';
566+
const singleUser = !!ctx.singleUser;
564567
return `
565568
<!DOCTYPE html>
566569
<html lang="en">
@@ -607,13 +610,17 @@ export function landingPage(ctx = {}) {
607610
<div class="container landing">
608611
<div class="landing-header">
609612
<h1>Solid Pod Server</h1>
610-
<p class="subtitle">Create an account, then sign in from any Solid app.</p>
613+
<p class="subtitle">${singleUser
614+
? 'Single-user pod — sign in from any Solid app.'
615+
: 'Create an account, then sign in from any Solid app.'}</p>
611616
</div>
612617
613-
<a href="/idp/register" class="btn btn-primary" style="text-decoration: none;">Create Account</a>
618+
${singleUser
619+
? '' /* Registration is disabled in single-user mode; suppress the dead-end button. */
620+
: '<a href="/idp/register" class="btn btn-primary" style="text-decoration: none;">Create Account</a>'}
614621
615622
<div class="signin-note">
616-
<strong>Already have an account?</strong> Sign in from a Solid app — for example, <a href="https://solid-apps.github.io/pilot/" target="_blank" rel="noopener">pilot</a> is a minimal console you can open right now. Point it at this server and click Sign In.
623+
<strong>${singleUser ? 'Sign in' : 'Already have an account?'}</strong> ${singleUser ? 'from' : 'Sign in from'} a Solid app — for example, <a href="https://solid-apps.github.io/pilot/" target="_blank" rel="noopener">pilot</a> is a minimal console you can open right now. Point it at this server and click Sign In.
617624
</div>
618625
619626
${issuer ? `<div class="issuer">Issuer: ${escapeHtml(issuer.replace(/\/$/, ''))}</div>` : ''}

test/idp.test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,59 @@ describe('Identity Provider - Subdomain mode register validation', () => {
356356
});
357357
});
358358

359+
// Single-user mode: registration is disabled, so the /idp landing must
360+
// suppress the "Create Account" button rather than ship a known 403 trap.
361+
// Regression coverage for #290.
362+
describe('Identity Provider - Single-user mode landing', () => {
363+
let server;
364+
let baseUrl;
365+
const SINGLE_USER_DATA_DIR = './test-data-idp-single-user';
366+
367+
before(async () => {
368+
await fs.remove(SINGLE_USER_DATA_DIR);
369+
await fs.ensureDir(SINGLE_USER_DATA_DIR);
370+
371+
const port = await getAvailablePort();
372+
baseUrl = `http://${TEST_HOST}:${port}`;
373+
374+
server = createServer({
375+
logger: false,
376+
root: SINGLE_USER_DATA_DIR,
377+
idp: true,
378+
idpIssuer: baseUrl,
379+
singleUser: true,
380+
singleUserName: 'me',
381+
forceCloseConnections: true,
382+
});
383+
384+
await server.listen({ port, host: TEST_HOST });
385+
});
386+
387+
after(async () => {
388+
await server.close();
389+
await fs.remove(SINGLE_USER_DATA_DIR);
390+
});
391+
392+
it('GET /idp omits the Create Account button', async () => {
393+
const res = await fetch(`${baseUrl}/idp`);
394+
assert.strictEqual(res.status, 200);
395+
const body = await res.text();
396+
// Sanity: the landing still rendered.
397+
assert.match(body, /Solid Pod Server/);
398+
// Button + register link must be absent — those would 403 in single-user mode.
399+
assert.doesNotMatch(body, /Create Account/);
400+
assert.doesNotMatch(body, /href="\/idp\/register"/);
401+
// Sign-in note should still mention pilot as the example client.
402+
assert.match(body, /solid-apps\.github\.io\/pilot/);
403+
});
404+
405+
it('subtitle reflects the single-user shape', async () => {
406+
const res = await fetch(`${baseUrl}/idp`);
407+
const body = await res.text();
408+
assert.match(body, /Single-user pod/);
409+
});
410+
});
411+
359412
describe('Identity Provider - Accounts', () => {
360413
let server;
361414
let accountsUrl;

0 commit comments

Comments
 (0)