diff --git a/config/identity/handler/default.json b/config/identity/handler/default.json index aa44e2bfaf..235571fdcf 100644 --- a/config/identity/handler/default.json +++ b/config/identity/handler/default.json @@ -23,7 +23,17 @@ "providerFactory": { "@id": "urn:solid-server:default:IdentityProviderFactory" }, "templateHandler": { "@type": "TemplateHandler", - "templateEngine": { "@type": "EjsTemplateEngine" } + "templateEngine": { + "comment": "Renders the specific page and embeds it into the main HTML body.", + "@type": "ChainedTemplateEngine", + "engines": [ + { "@type": "EjsTemplateEngine" }, + { + "@type": "EjsTemplateEngine", + "template": "$PACKAGE_ROOT/templates/identity/main.html.ejs" + } + ] + } }, "interactionCompleter": { "comment": "Responsible for finishing OIDC interactions.", diff --git a/src/index.ts b/src/index.ts index 4e6992942d..49405f5d90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -304,6 +304,7 @@ export * from './util/locking/SingleThreadedResourceLocker'; export * from './util/locking/WrappedExpiringReadWriteLocker'; // Util/Templates +export * from './util/templates/ChainedTemplateEngine'; export * from './util/templates/EjsTemplateEngine'; export * from './util/templates/HandlebarsTemplateEngine'; export * from './util/templates/TemplateEngine'; diff --git a/src/util/templates/ChainedTemplateEngine.ts b/src/util/templates/ChainedTemplateEngine.ts new file mode 100644 index 0000000000..114f642e26 --- /dev/null +++ b/src/util/templates/ChainedTemplateEngine.ts @@ -0,0 +1,37 @@ +import type { Template, TemplateEngine } from './TemplateEngine'; +import Dict = NodeJS.Dict; + +/** + * Calls the given array of {@link TemplateEngine}s in the order they appear, + * feeding the output of one into the input of the next. + * + * The first engine will be called with the provided contents and template parameters. + * All subsequent engines will be called with no template parameter and contents being `{ body }`, + * with `body` the output of the previous engine. + */ +export class ChainedTemplateEngine = Dict> implements TemplateEngine { + private readonly contentsEngine: TemplateEngine; + private readonly bodyEngines: TemplateEngine<{ body: string }>[]; + + /** + * @param engines - Engines will be executed in the same order as the array. + * Actual expected type is `[ TemplateEngine, ...TemplateEngine<{ body: string }>]` + */ + public constructor(engines: TemplateEngine[]) { + if (engines.length === 0) { + throw new Error('At least 1 engine needs to be provided.'); + } + this.contentsEngine = engines[0]; + this.bodyEngines = engines.slice(1); + } + + public async render(contents: T): Promise; + public async render(contents: TCustom, template: Template): Promise; + public async render(contents: TCustom, template?: Template): Promise { + let body = await this.contentsEngine.render(contents, template!); + for (const engine of this.bodyEngines) { + body = await engine.render({ body }); + } + return body; + } +} diff --git a/templates/identity/email-password/confirm.html.ejs b/templates/identity/email-password/confirm.html.ejs index e6862f55ef..8d8617afe9 100644 --- a/templates/identity/email-password/confirm.html.ejs +++ b/templates/identity/email-password/confirm.html.ejs @@ -1,27 +1,4 @@ - - - - - - Authorize - - - -
- [Solid logo] -

Community Solid Server

-
-
-

Authorize

-
-

-
-
- - - +

Authorize

+
+

+
diff --git a/templates/identity/email-password/email-sent.html.ejs b/templates/identity/email-password/email-sent.html.ejs index e04f86b502..e7f9d20eca 100644 --- a/templates/identity/email-password/email-sent.html.ejs +++ b/templates/identity/email-password/email-sent.html.ejs @@ -1,37 +1,14 @@ - - - - - - Email sent - - - -
- [Solid logo] -

Community Solid Server

-
-
-

Email sent

-
-

If your account exists, an email has been sent with a link to reset your password.

-

If you do not receive your email in a couple of minutes, check your spam folder or click the link below to send another email.

+

Email sent

+ +

If your account exists, an email has been sent with a link to reset your password.

+

If you do not receive your email in a couple of minutes, check your spam folder or click the link below to send another email.

- + -

Back to Log In

+

Back to Log In

-
-

- -

-
-
- - - +
+

+ +

+ diff --git a/templates/identity/email-password/forgot-password.html.ejs b/templates/identity/email-password/forgot-password.html.ejs index 08288989d5..c21282de33 100644 --- a/templates/identity/email-password/forgot-password.html.ejs +++ b/templates/identity/email-password/forgot-password.html.ejs @@ -1,42 +1,19 @@ - - - - - - Forgot password - - - -
- [Solid logo] -

Community Solid Server

-
-
-

Forgot password

-
- <%if (errorMessage) { %> -

<%= errorMessage %>

- <% } %> +

Forgot password

+ + <%if (errorMessage) { %> +

<%= errorMessage %>

+ <% } %> -
-
    -
  1. - - -
  2. -
-
+
+
    +
  1. + + +
  2. +
+
-

+

-

Log in

-
-
- - - +

Log in

+ diff --git a/templates/identity/email-password/login.html.ejs b/templates/identity/email-password/login.html.ejs index 75b9b4a7b3..16752f15da 100644 --- a/templates/identity/email-password/login.html.ejs +++ b/templates/identity/email-password/login.html.ejs @@ -1,53 +1,30 @@ - - - - - - Log in - - - -
- [Solid logo] -

Community Solid Server

-
-
-

Log in

-
- <%if (errorMessage) { %> -

<%= errorMessage %>

- <% } %> +

Log in

+ + <%if (errorMessage) { %> +

<%= errorMessage %>

+ <% } %> -
- Your account -
    -
  1. - - value="<%= prefilled.email %>" <% } %>> -
  2. -
  3. - - -
  4. -
  5. - -
  6. -
-
+
+ Your account +
    +
  1. + + value="<%= prefilled.email %>" <% } %>> +
  2. +
  3. + + +
  4. +
  5. + +
  6. +
+
-

+

- -
-
- - - + + diff --git a/templates/identity/email-password/message.html.ejs b/templates/identity/email-password/message.html.ejs index 2ee0240f8b..6a5178ec60 100644 --- a/templates/identity/email-password/message.html.ejs +++ b/templates/identity/email-password/message.html.ejs @@ -1,24 +1 @@ - - - - - - <%= message %> - - - -
- [Solid logo] -

Community Solid Server

-
-
-

<%= message %>

-
- - - +

<%= message %>

diff --git a/templates/identity/email-password/register-response.html.ejs b/templates/identity/email-password/register-response.html.ejs index 46dee30c79..1c1e5a720d 100644 --- a/templates/identity/email-password/register-response.html.ejs +++ b/templates/identity/email-password/register-response.html.ejs @@ -1,63 +1,40 @@ - - - - - - You are signed up - - - -
- [Solid logo] -

Community Solid Server

-
-
-

You are signed up

-

- Welcome to Solid. - We wish you an exciting experience! -

+

You are signed up

+

+ Welcome to Solid. + We wish you an exciting experience! +

- <% if (createPod) { %> -

Your new Pod

-

- Your new Pod is located at <%= podBaseUrl %>. -
- You can store your documents and data there. -

- <% } %> +<% if (createPod) { %> +

Your new Pod

+

+ Your new Pod is located at <%= podBaseUrl %>. +
+ You can store your documents and data there. +

+<% } %> - <% if (createWebId) { %> -

Your new WebID

-

- Your new WebID is <%= webId %>. -
- You can use this identifier to interact with Solid pods and apps. -

- <% } %> +<% if (createWebId) { %> +

Your new WebID

+

+ Your new WebID is <%= webId %>. +
+ You can use this identifier to interact with Solid pods and apps. +

+<% } %> - <% if (register) { %> -

Your new account

-

- Via your email address <%= email %>, - this server lets you log in to Solid apps - with your WebID <%= webId %> -

- <% if (!createWebId) { %> -

- You will need to add the triple - <%= `<${webId}> <${oidcIssuer}>.`%> - to your existing WebID document <%= webId %> - to indicate that you trust this server as a login provider. -

- <% } %> - <% } %> -
-
+<% if (register) { %> +

Your new account

+

+ Via your email address <%= email %>, + this server lets you log in to Solid apps + with your WebID <%= webId %> +

+ <% if (!createWebId) { %>

- ©2019–2021 Inrupt Inc. - and imec + You will need to add the triple + <%= `<${webId}> <${oidcIssuer}>.`%> + to your existing WebID document <%= webId %> + to indicate that you trust this server as a login provider.

-
- - + <% } %> +<% } %> diff --git a/templates/identity/email-password/register.html.ejs b/templates/identity/email-password/register.html.ejs index 9ffb990ce9..16149d1cd0 100644 --- a/templates/identity/email-password/register.html.ejs +++ b/templates/identity/email-password/register.html.ejs @@ -1,212 +1,189 @@ - - - - - - Register - - - -
- [Solid logo] -

Community Solid Server

-
-
-

Sign up

-
- <% const isBlankForm = !('email' in prefilled); %> +

Sign up

+ + <% const isBlankForm = !('email' in prefilled); %> - <% if (errorMessage) { %> -

Error: <%= errorMessage %>

- <% } %> + <% if (errorMessage) { %> +

Error: <%= errorMessage %>

+ <% } %> -
- Your WebID -

- A WebID is a unique identifier for you - in the form of a URL. -
- You WebID lets you log in to Solid apps - and access non-public data in Pods. +

+ Your WebID +

+ A WebID is a unique identifier for you + in the form of a URL. +
+ You WebID lets you log in to Solid apps + and access non-public data in Pods. +

+
    +
  1. + +

    + Please also create a Pod below, since your WebID will be stored there.

    -
      -
    1. - -

      - Please also create a Pod below, since your WebID will be stored there. -

      +
    2. +
    3. + +
        +
      1. + +
      2. -
      3. +
      4. -
          -
        1. - - -
        2. -
        3. - -
        4. -
      -
+ + +
-
- Your Pod -

- A Pod is a place to store your data. -
- If you create a new WebID, you must also create a Pod to store that WebID. -

-
    -
  1. - -
      -
    1. - - value="<%= prefilled.podName %>" <% } %>> -
    2. -
    +
    + Your Pod +

    + A Pod is a place to store your data. +
    + If you create a new WebID, you must also create a Pod to store that WebID. +

    +
      +
    1. + +
        +
      1. + + value="<%= prefilled.podName %>" <% } %>>
      -
    +
  2. +
+
-
- Your account -
-

- Choose the credentials you want to use to log in to this server in the future. -

-
    -
  1. - - value="<%= prefilled.email %>" <% } %>> -
  2. -
-
    -
  1. - - -
  2. -
  3. - - -
  4. -
-
- -
+
+ Your account +
+

+ Choose the credentials you want to use to log in to this server in the future. +

+
    +
  1. + + value="<%= prefilled.email %>" <% } %>> +
  2. +
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
+
+ +
-

-
+

+ - -
- - - + // Enable all elements on form submission (otherwise their value is not submitted) + elements.mainForm.addEventListener('submit', () => { + for (const child of getDescendants(elements.mainForm)) + child.disabled = false; + }); + elements.mainForm.addEventListener('formdata', updateUI); +})(); + diff --git a/templates/identity/email-password/reset-password.html.ejs b/templates/identity/email-password/reset-password.html.ejs index 788d022945..53daac5dcb 100644 --- a/templates/identity/email-password/reset-password.html.ejs +++ b/templates/identity/email-password/reset-password.html.ejs @@ -1,44 +1,21 @@ - - - - - - Reset password - - - -
- [Solid logo] -

Community Solid Server

-
-
-

Reset password

-
- <%if (errorMessage) { %> -

<%= errorMessage %>

- <% } %> +

Reset password

+ + <%if (errorMessage) { %> +

<%= errorMessage %>

+ <% } %> -
-
    -
  1. - - -
  2. -
  3. - - -
  4. -
-
+
+
    +
  1. + + +
  2. +
  3. + + +
  4. +
+
-

-
-
- - - +

+ diff --git a/templates/identity/main.html.ejs b/templates/identity/main.html.ejs new file mode 100644 index 0000000000..d06a7cfe4b --- /dev/null +++ b/templates/identity/main.html.ejs @@ -0,0 +1,34 @@ + + + + + + <%= extractTitle(body) %> + + + +
+ [Solid logo] +

Community Solid Server

+
+
+ <%- body %> +
+ + + + +<% +function extractTitle(body) { + const match = /^

([^<]*)<\/h1>/u.exec(body); + if (match) { + return match[1]; + } + return 'Solid'; +} +%> diff --git a/test/unit/util/templates/ChainedTemplateEngine.test.ts b/test/unit/util/templates/ChainedTemplateEngine.test.ts new file mode 100644 index 0000000000..100ecac034 --- /dev/null +++ b/test/unit/util/templates/ChainedTemplateEngine.test.ts @@ -0,0 +1,30 @@ +import { ChainedTemplateEngine } from '../../../../src/util/templates/ChainedTemplateEngine'; +import type { TemplateEngine } from '../../../../src/util/templates/TemplateEngine'; + +describe('A ChainedTemplateEngine', (): void => { + const contents = { title: 'myTitle' }; + const template = { templateFile: '/template.tmpl' }; + let engines: jest.Mocked[]; + let engine: ChainedTemplateEngine; + + beforeEach(async(): Promise => { + engines = [ + { render: jest.fn().mockResolvedValue('body1') }, + { render: jest.fn().mockResolvedValue('body2') }, + ]; + + engine = new ChainedTemplateEngine(engines); + }); + + it('errors if no engines are provided.', async(): Promise => { + expect((): any => new ChainedTemplateEngine([])).toThrow('At least 1 engine needs to be provided.'); + }); + + it('chains the engines.', async(): Promise => { + await expect(engine.render(contents, template)).resolves.toEqual('body2'); + expect(engines[0].render).toHaveBeenCalledTimes(1); + expect(engines[0].render).toHaveBeenLastCalledWith(contents, template); + expect(engines[1].render).toHaveBeenCalledTimes(1); + expect(engines[1].render).toHaveBeenLastCalledWith({ body: 'body1' }); + }); +});