Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/idp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export async function idpPlugin(fastify, options) {
// Pairs with the /idp/auth guard above so a human visitor lands here
// rather than on a raw OIDC error.
fastify.get('/idp', async (request, reply) => {
return reply.type('text/html').send(landingPage({ baseUri: issuer }));
return reply.type('text/html').send(landingPage({ baseUri: issuer, singleUser }));
});

// Token sub-paths
Expand Down
19 changes: 13 additions & 6 deletions src/idp/views.js
Original file line number Diff line number Diff line change
Expand Up @@ -555,12 +555,15 @@ export function errorPage(title, message) {
*
* The OIDC authorization endpoint (/idp/auth) requires a client_id; opening
* /idp manually used to drop the user into a raw OIDC error. This page is
* the human-navigable entry point — Create Account is wired up; Sign In is
* intentionally a description for now (PR-B / #286 will introduce a
* standalone /idp/login form).
* the human-navigable entry point.
*
* In single-user mode (`ctx.singleUser`) the Create Account button is
* suppressed — pod creation is disabled and the button would lead to a
* 403. The sign-in note still names pilot as the example client.
*/
export function landingPage(ctx = {}) {
const issuer = ctx.baseUri || '';
const singleUser = !!ctx.singleUser;
return `
<!DOCTYPE html>
<html lang="en">
Expand Down Expand Up @@ -607,13 +610,17 @@ export function landingPage(ctx = {}) {
<div class="container landing">
<div class="landing-header">
<h1>Solid Pod Server</h1>
<p class="subtitle">Create an account, then sign in from any Solid app.</p>
<p class="subtitle">${singleUser
? 'Single-user pod — sign in from any Solid app.'
: 'Create an account, then sign in from any Solid app.'}</p>
</div>

<a href="/idp/register" class="btn btn-primary" style="text-decoration: none;">Create Account</a>
${singleUser
? '' /* Registration is disabled in single-user mode; suppress the dead-end button. */
: '<a href="/idp/register" class="btn btn-primary" style="text-decoration: none;">Create Account</a>'}

<div class="signin-note">
<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.
<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.
</div>

${issuer ? `<div class="issuer">Issuer: ${escapeHtml(issuer.replace(/\/$/, ''))}</div>` : ''}
Expand Down
53 changes: 53 additions & 0 deletions test/idp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,59 @@ describe('Identity Provider - Subdomain mode register validation', () => {
});
});

// Single-user mode: registration is disabled, so the /idp landing must
// suppress the "Create Account" button rather than ship a known 403 trap.
// Regression coverage for #290.
describe('Identity Provider - Single-user mode landing', () => {
let server;
let baseUrl;
const SINGLE_USER_DATA_DIR = './test-data-idp-single-user';

before(async () => {
await fs.remove(SINGLE_USER_DATA_DIR);
await fs.ensureDir(SINGLE_USER_DATA_DIR);

const port = await getAvailablePort();
baseUrl = `http://${TEST_HOST}:${port}`;

server = createServer({
logger: false,
root: SINGLE_USER_DATA_DIR,
idp: true,
idpIssuer: baseUrl,
singleUser: true,
singleUserName: 'me',
forceCloseConnections: true,
});

await server.listen({ port, host: TEST_HOST });
});

after(async () => {
await server.close();
await fs.remove(SINGLE_USER_DATA_DIR);
});

it('GET /idp omits the Create Account button', async () => {
const res = await fetch(`${baseUrl}/idp`);
assert.strictEqual(res.status, 200);
const body = await res.text();
// Sanity: the landing still rendered.
assert.match(body, /Solid Pod Server/);
// Button + register link must be absent — those would 403 in single-user mode.
assert.doesNotMatch(body, /Create Account/);
assert.doesNotMatch(body, /href="\/idp\/register"/);
// Sign-in note should still mention pilot as the example client.
assert.match(body, /solid-apps\.github\.io\/pilot/);
});

it('subtitle reflects the single-user shape', async () => {
const res = await fetch(`${baseUrl}/idp`);
const body = await res.text();
assert.match(body, /Single-user pod/);
});
});

describe('Identity Provider - Accounts', () => {
let server;
let accountsUrl;
Expand Down