mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Full rework of account management
Complete rewrite of the account management and related systems. Makes the architecture more modular, allowing for easier extensions and configurations.
This commit is contained in:
@@ -32,7 +32,7 @@ the [changelog](https://github.com/CommunitySolidServer/CommunitySolidServer/blo
|
||||
* [Quickly starting the server](usage/starting-server.md)
|
||||
* [Basic example HTTP requests](usage/example-requests.md)
|
||||
* [Editing the metadata of a resource](usage/metadata.md)
|
||||
* [How to use the Identity Provider](usage/identity-provider.md)
|
||||
* [How to use the Identity Provider and accounts](usage/identity-provider.md)
|
||||
* [How to automate authentication](usage/client-credentials.md)
|
||||
* [How to automatically seed pods on startup](usage/seeding-pods.md)
|
||||
* [Receiving notifications when resources change](usage/notifications.md)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# JSON API controls
|
||||
|
||||
A large part of every response of the JSON API is the `controls` block.
|
||||
These are generated by using nested `ControlHandler` objects.
|
||||
These take as input a key/value with the values being either routes or other interaction handlers.
|
||||
These will then be executed to determine the values of the output JSON object, with the same keys.
|
||||
By using other `ControlHandler`s in the input map, we can create nested objects.
|
||||
|
||||
The default structure of these handlers is as follows:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
RootControlHandler("<strong>RootControlHandler</strong><br>ControlHandler")
|
||||
RootControlHandler --controls--> ControlHandler("<strong>ControlHandler</strong><br>ControlHandler")
|
||||
ControlHandler --main--> MainControlHandler("<strong>MainControlHandler</strong><br>ControlHandler")
|
||||
ControlHandler --account--> AccountControlHandler("<strong>AccountControlHandler</strong><br>ControlHandler")
|
||||
ControlHandler --password--> PasswordControlHandler("<strong>PasswordControlHandler</strong><br>ControlHandler")
|
||||
ControlHandler --"oidc"--> OidcControlHandler("<strong>OidcControlHandler</strong><br>OidcControlHandler")
|
||||
ControlHandler --html--> HtmlControlHandler("<strong>HtmlControlHandler</strong><br>ControlHandler")
|
||||
|
||||
HtmlControlHandler --main--> MainHtmlControlHandler("<strong>MainHtmlControlHandler</strong><br>ControlHandler")
|
||||
HtmlControlHandler --account--> AccountHtmlControlHandler("<strong>AccountHtmlControlHandler</strong><br>ControlHandler")
|
||||
HtmlControlHandler --password--> PasswordHtmlControlHandler("<strong>PasswordHtmlControlHandler</strong><br>ControlHandler")
|
||||
```
|
||||
|
||||
Each of these control handlers then has a map of routes which link to the actual API endpoints.
|
||||
How to add these can be seen [here](routes.md#adding-the-necessary-controls).
|
||||
@@ -0,0 +1,58 @@
|
||||
# Account management
|
||||
|
||||
The main entry point is the `IdentityProviderHandler`,
|
||||
which routes all requests targeting a resource starting with `/.account/` into this handler,
|
||||
after which it goes through similar parsing handlers as described [here](../protocol/overview.md),
|
||||
the flow of which is shown below:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Handler("<strong>IdentityProviderHandler</strong><br>RouterHandler")
|
||||
ParsingHandler("<strong>IdentityProviderParsingHandler</strong><br>AuthorizingHttpHandler")
|
||||
AuthorizingHandler("<strong>IdentityProviderAuthorizingHandler</strong><br>AuthorizingHttpHandler")
|
||||
|
||||
Handler --> ParsingHandler
|
||||
ParsingHandler --> AuthorizingHandler
|
||||
AuthorizingHandler --> HttpHandler("<strong>IdentityProviderHttpHandler</strong><br>IdentityProviderHttpHandler")
|
||||
```
|
||||
|
||||
The `IdentityProviderHttpHandler` is where the actual differentiation of this component starts.
|
||||
It handles identifying the account based on the supplied cookie and determining the active OIDC interaction,
|
||||
after which it calls an `InteractionHandler` with this additional input.
|
||||
The `InteractionHandler` is many handlers chained together as follows:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
HttpHandler("<strong>IdentityProviderHttpHandler</strong><br>IdentityProviderHttpHandler")
|
||||
HttpHandler --> InteractionHandler("<strong>InteractionHandler</strong><br>WaterfallHandler")
|
||||
InteractionHandler --> InteractionHandlerArgs
|
||||
|
||||
subgraph InteractionHandlerArgs[" "]
|
||||
HtmlViewHandler("<strong>HtmlViewHandler</strong><br>HtmlViewHandler")
|
||||
LockingInteractionHandler("<strong>LockingInteractionHandler</strong><br>LockingInteractionHandler")
|
||||
end
|
||||
|
||||
LockingInteractionHandler --> JsonConversionHandler("<strong>JsonConversionHandler</strong><br>JsonConversionHandler")
|
||||
JsonConversionHandler --> VersionHandler("<strong>VersionHandler</strong><br>VersionHandler")
|
||||
VersionHandler --> CookieInteractionHandler("<strong>CookieInteractionHandler</strong><br>CookieInteractionHandler")
|
||||
CookieInteractionHandler --> RootControlHandler("<strong>RootControlHandler</strong><br>ControlHandler")
|
||||
RootControlHandler --> LocationInteractionHandler("<strong>LocationInteractionHandler</strong><br>LocationInteractionHandler")
|
||||
LocationInteractionHandler --> InteractionRouteHandler("<strong>InteractionRouteHandler</strong><br>WaterfallHandler")
|
||||
```
|
||||
|
||||
The `HtmlViewHandler` catches all request that request an HTML output.
|
||||
This class keeps a list of HTML pages and their corresponding URL and returns them when needed.
|
||||
|
||||
If the request is for the JSON API,
|
||||
the request goes through a chain of handlers, each responsible for a specific step in the API process.
|
||||
We'll list and summarize these here:
|
||||
|
||||
* `LockingInteractionHandler`: In case the request is authenticated,
|
||||
this requests a lock on that account to prevent simultaneous operations on the same account.
|
||||
* `JsonConversionHandler`: Converts the streaming input into a JSON object.
|
||||
* `VersionHandler`: Adds a version number to all output.
|
||||
* `CookieInteractionHandler`: Refreshes the cookie if necessary and adds relevant cookie metadata to the output.
|
||||
* `RootControlHandler`: Responsible for adding all the [controls](controls.md) to the output.
|
||||
Will take as input multiple other control handlers which create the nested values in the `controls` field.
|
||||
* `LocationInteractionHandler`: Catches redirect errors and converts them to JSON objects with a `location` field.
|
||||
* `InteractionRouteHandler`: A `WaterfallHandler` containing an entry for every supported API [route](routes.md).
|
||||
126
documentation/markdown/architecture/features/accounts/routes.md
Normal file
126
documentation/markdown/architecture/features/accounts/routes.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Account API routes
|
||||
|
||||
All entries contained in the `urn:solid-server:default:InteractionRouteHandler` have a similar structure:
|
||||
an `InteractionRouteHandler`, or `AuthorizedRouteHandler` for authenticated requests,
|
||||
which checks if the request targets a specific URL
|
||||
and redirects the request to its source if there is a match.
|
||||
Its source is quite often a `ViewInteractionHandler`,
|
||||
which returns a specific view on GET requests and performs an operation on POST requests,
|
||||
but other handlers can also occur.
|
||||
|
||||
Below we will give an example of one API route and all the components that are necessary to add it to the server.
|
||||
|
||||
## Route handler
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:AccountWebIdRouter",
|
||||
"@type": "AuthorizedRouteHandler",
|
||||
"route": {
|
||||
"@id": "urn:solid-server:default:AccountWebIdRoute",
|
||||
"@type": "RelativePathInteractionRoute",
|
||||
"base": { "@id": "urn:solid-server:default:AccountIdRoute" },
|
||||
"relativePath": "webid/"
|
||||
},
|
||||
"source": { "@id": "urn:solid-server:default:WebIdHandler" }
|
||||
}
|
||||
```
|
||||
|
||||
The main entry point is the route handler,
|
||||
which determines the URL necessary to reach this API.
|
||||
In this case we create a new route, relative to the `urn:solid-server:default:AccountIdRoute`.
|
||||
That route specifically matches URLs of the format `http://localhost:3000/.account/account/<accountId>/`.
|
||||
Here we create a route relative to that one by appending `webid`,
|
||||
so the resulting route would match `http://localhost:3000/.account/account/<accountId>/webid/`.
|
||||
Since an `AuthorizedRouteHandler` is used here,
|
||||
the request also needs to be authenticated using an account cookie.
|
||||
If there is match, the request will be sent to the `urn:solid-server:default:WebIdHandler`.
|
||||
|
||||
## Interaction handler
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:WebIdHandler",
|
||||
"@type": "ViewInteractionHandler",
|
||||
"source": {
|
||||
"@id": "urn:solid-server:default:LinkWebIdHandler",
|
||||
"@type": "LinkWebIdHandler",
|
||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"ownershipValidator": { "@id": "urn:solid-server:default:OwnershipValidator" },
|
||||
"accountStore": { "@id": "urn:solid-server:default:AccountStore" },
|
||||
"webIdStore": { "@id": "urn:solid-server:default:WebIdStore" },
|
||||
"identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The interaction handler is the class that performs the necessary operation based on the request.
|
||||
Often these are wrapped in a `ViewInteractionHandler`,
|
||||
which allows classes to have different support for GET and POST requests.
|
||||
|
||||
## Exposing the API
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:InteractionRouteHandler",
|
||||
"@type": "WaterfallHandler",
|
||||
"handlers": [
|
||||
{ "@id": "urn:solid-server:default:AccountWebIdRouter" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To make sure the API can be accessed,
|
||||
it needs to be added to the list of `urn:solid-server:default:InteractionRouteHandler`.
|
||||
This is the main handler that contains entries for all the APIs.
|
||||
This block of Components.js adds the route handler defined above to that list.
|
||||
|
||||
## Adding the necessary controls
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:AccountControlHandler",
|
||||
"@type": "ControlHandler",
|
||||
"controls": [{
|
||||
"ControlHandler:_controls_key": "webId",
|
||||
"ControlHandler:_controls_value": { "@id": "urn:solid-server:default:AccountWebIdRoute" }
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
To make sure people can find the API,
|
||||
it is necessary to link it through the associated `controls` object.
|
||||
This API is related to account management,
|
||||
so we add its route in the account controls with the key `webId`.
|
||||
More information about controls can be found [here](controls.md).
|
||||
|
||||
## Adding HTML
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:HtmlViewHandler",
|
||||
"@type": "HtmlViewHandler",
|
||||
"templates": [{
|
||||
"@id": "urn:solid-server:default:LinkWebIdHtml",
|
||||
"@type": "HtmlViewEntry",
|
||||
"filePath": "@css:templates/identity/account/link-webid.html.ejs",
|
||||
"route": { "@id": "urn:solid-server:default:AccountWebIdRoute" }
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Some API routes also have an associated HTML page,
|
||||
in which case the page needs to be added to the `urn:solid-server:default:HtmlViewHandler`,
|
||||
which is what we do here.
|
||||
Usually you will also want to add HTML controls so the page can be found.
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:AccountHtmlControlHandler",
|
||||
"@type": "ControlHandler",
|
||||
"controls": [{
|
||||
"ControlHandler:_controls_key": "linkWebId",
|
||||
"ControlHandler:_controls_value": { "@id": "urn:solid-server:default:AccountWebIdRoute" }
|
||||
}]
|
||||
}
|
||||
```
|
||||
@@ -88,8 +88,9 @@ More on this can be found in the [identity provider](../../../usage/identity-pro
|
||||
|
||||
The `urn:solid-server:default:IdentityProviderHttpHandler` handles everything
|
||||
related to our custom identity provider API, such as registering, logging in, returning the relevant HTML pages, etc.
|
||||
All these requests are identified by being on the `/idp/` subpath.
|
||||
All these requests are identified by being on the `/.account/` subpath.
|
||||
More information on the API can be found in the [identity provider](../../../usage/identity-provider) documentation
|
||||
The architectural overview can be found [here](accounts/overview.md).
|
||||
|
||||
## LdpHandler
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Below is a simplified view of how these handlers are linked.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
LdpHandler("<strong>LdpHandler</strong><br>ParsingHttphandler")
|
||||
LdpHandler("<strong>LdpHandler</strong><br>ParsingHttpHandler")
|
||||
LdpHandler --> AuthorizingHttpHandler("<br>AuthorizingHttpHandler")
|
||||
AuthorizingHttpHandler --> OperationHandler("<strong>OperationHandler</strong><br><i>OperationHandler</i>")
|
||||
OperationHandler --> ResourceStore("<strong>ResourceStore</strong><br><i>ResourceStore</i>")
|
||||
|
||||
281
documentation/markdown/usage/account/json-api.md
Normal file
281
documentation/markdown/usage/account/json-api.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Account management JSON API
|
||||
|
||||
Everything related to account management is done through a JSON API,
|
||||
of which we will describe all paths below.
|
||||
There are also HTML pages available to handle account management
|
||||
that use these APIs internally.
|
||||
Links to these can be found in the HTML controls
|
||||
All APIs expect JSON as input, and will return JSON objects as output.
|
||||
|
||||
## Finding API URLs
|
||||
|
||||
All URLs below are relative to the index account API URL, which by default is `http://localhost:3000/.account/`.
|
||||
Every response of an API request will contain a `controls` object,
|
||||
containing all the URLs of the other API endpoints.
|
||||
It is generally advised to make use of these controls instead of hardcoding the URLs.
|
||||
Only the initial index URL needs to be known then to find the controls.
|
||||
Certain controls will be missing if those features are disabled in the configuration.
|
||||
|
||||
## API requests
|
||||
|
||||
Many APIs require a POST request to perform an action.
|
||||
When doing a GET request on these APIs they will return an object describing what input is expected for the POST.
|
||||
|
||||
## Authorization
|
||||
|
||||
After logging in, the API will return a `set-cookie` header.
|
||||
This cookie is necessary to have access to many of the APIs.
|
||||
When including this cookie, the controls object will also be extended with new URLs that are now accessible.
|
||||
When logging in, the response body JSON body will also contain a `cookie` field containing the cookie value.
|
||||
Instead of using cookies,
|
||||
this value can also be used in an `Authorization` header with auth scheme `CSS-Account-Cookie`
|
||||
to achieve the same result.
|
||||
|
||||
The expiration time of this cookie will be refreshed
|
||||
every time there is a successful request to the server with that cookie.
|
||||
|
||||
## Redirecting
|
||||
|
||||
As redirects through status codes 3xx can make working with JSON APIs more difficult,
|
||||
the API will never make use of this.
|
||||
Instead, if a redirect is required after an action,
|
||||
the response JSON object will return a `location` field.
|
||||
This is the next URL that should be fetched.
|
||||
This is mostly relevant in OIDC interactions as these cause the interaction to progress.
|
||||
|
||||
## Controls
|
||||
|
||||
Below is an overview of all the keys in a controls object returned by the server,
|
||||
with all features enabled.
|
||||
An example of what such an object looks like can be found at the [bottom](#example) of the page.
|
||||
|
||||
### controls.main
|
||||
|
||||
General controls that require no authentication.
|
||||
|
||||
#### controls.main.index
|
||||
|
||||
General entrypoint to the API.
|
||||
Returns an empty object, including the controls, on all GET requests.
|
||||
|
||||
#### controls.main.logins
|
||||
|
||||
Returns an overview of all login systems available on the server in `logins` object.
|
||||
Keys are a string description of the login system and values are links to their login pages.
|
||||
This can be used to let users choose how they want to log in.
|
||||
By default, the object only contains the email/password login system.
|
||||
|
||||
### controls.account
|
||||
|
||||
All controls related to account management.
|
||||
All of these require authorization, except for the create action.
|
||||
|
||||
#### controls.account.create
|
||||
|
||||
Creates a new account on empty POST requests.
|
||||
The response contains the necessary cookie values to log and a `resource` field containing the URL of the account.
|
||||
This account can not be used until a login method has been added to it.
|
||||
All other interactions will fail until this is the case.
|
||||
See the [controls.password.create](#controlspasswordcreate) section below for more information on how to do this.
|
||||
This account will expire after some time if no login method is added.
|
||||
|
||||
#### controls.account.logout
|
||||
|
||||
Logs the account out on an empty POST request.
|
||||
Invalidates the cookie that was used.
|
||||
|
||||
#### controls.account.webId
|
||||
|
||||
POST requests link a WebID to the account,
|
||||
allowing the account to identify as that WebID during an OIDC authentication interaction.
|
||||
Expected input is an object containing a `webId` field.
|
||||
|
||||
If the chosen WebID is contained within a Solid pod associated with this account,
|
||||
the request will succeed immediately.
|
||||
If not, an error will be thrown,
|
||||
asking the user to add a specific triple to the WebID to confirm that they are the owner.
|
||||
After this triple is added, a second request will be successful.
|
||||
|
||||
#### controls.account.pod
|
||||
|
||||
Creates a Solid pod for the account on POST requests.
|
||||
The only required field is `name`, which will determine the name of the pod.
|
||||
|
||||
Additionally, a `settings` object can be sent along,
|
||||
the values of which will be sent to the templates used when generating the pod.
|
||||
If this `settings` object contains a `webId` field,
|
||||
that WebID will be the WebID that has initial access to the pod.
|
||||
|
||||
If no WebID value is provided,
|
||||
a WebID will be generated in the pod and immediately linked to the account
|
||||
as described in [controls.account.webID](#controlsaccountwebid).
|
||||
This WebID will then be the WebID that has initial access.
|
||||
|
||||
#### controls.account.clientCredentials
|
||||
|
||||
Creates a client credentials token on POST requests.
|
||||
More information on these tokens can be found [here](../client-credentials.md).
|
||||
Expected input is an object containing a `name` and `webId` field.
|
||||
The name is optional and will be used to name the token,
|
||||
the WebID determines which WebID you will identify as when using that token.
|
||||
It needs to be a WebID linked to the account as described in [controls.account.webID](#controlsaccountwebid).
|
||||
|
||||
#### controls.account.account
|
||||
|
||||
This value corresponds to the resource URL of the account you received when creating it.
|
||||
This returns all resources linked to this account, such as login methods, WebIDs, pods, and client credentials tokens.
|
||||
|
||||
Below is an example response object:
|
||||
|
||||
```json
|
||||
{
|
||||
"logins": {
|
||||
"password": {
|
||||
"test@example.com": "http://localhost:3000/.account/account/c63c9e6f-48f8-40d0-8fec-238da893a7f2/login/password/test%40example.com/"
|
||||
}
|
||||
},
|
||||
"pods": {
|
||||
"http://localhost:3000/test/": "http://localhost:3000/.account/account/c63c9e6f-48f8-40d0-8fec-238da893a7f2/pod/7def7830df1161e422537db594ad2b7412ffb735e0e2320cf3e90db19cd969f9/"
|
||||
},
|
||||
"webIds": {
|
||||
"http://localhost:3000/test/profile/card#me": "http://localhost:3000/.account/account/c63c9e6f-48f8-40d0-8fec-238da893a7f2/webid/5c1b70d3ffaa840394dda86889ed1569cf897ef3d6041fb4c9513f82144cbb7f/"
|
||||
},
|
||||
"clientCredentials": {
|
||||
"token_562cdeb5-d4b2-4905-9e62-8969ac10daaa": "http://localhost:3000/.account/account/c63c9e6f-48f8-40d0-8fec-238da893a7f2/client-credentials/token_562cdeb5-d4b2-4905-9e62-8969ac10daaa/"
|
||||
},
|
||||
"settings": {}
|
||||
}
|
||||
```
|
||||
|
||||
In each of the sub-objects, the key is always the unique identifier of whatever is being described,
|
||||
while the value is the resource URL that can potentially be used to modify the resource.
|
||||
Removing an entry can be done by sending a DELETE request to the resource URL,
|
||||
except for pods, which cannot be deleted.
|
||||
Login methods can only be deleted if the account has at least 1 login method remaining afterwards.
|
||||
|
||||
The password login resource URL can also be used to modify the password,
|
||||
which can be done by sending a POST request to it with the body containing an `oldPassword` and a `newPassword` field.
|
||||
|
||||
### controls.password
|
||||
|
||||
Controls related to managing the email/password login method.
|
||||
|
||||
#### controls.password.create
|
||||
|
||||
POST requests create an email/password login and adds it to the account you are logged in as.
|
||||
Expects `email` and `password` fields.
|
||||
|
||||
#### controls.password.login
|
||||
|
||||
POST requests log a user in and return the relevant cookie values.
|
||||
Expected fields are `email`, `password`, and optionally a `remember` boolean.
|
||||
The `remember` value determines if the returned cookie is only valid for the session,
|
||||
or for a longer time.
|
||||
|
||||
#### controls.password.forgot
|
||||
|
||||
Can be used when a user forgets their password.
|
||||
POST requests with an `email` field will send an email with a link to reset the password.
|
||||
|
||||
#### controls.password.reset
|
||||
|
||||
Used to handle reset password URLs generated when a user forgets their password.
|
||||
Expected input values for the POST request are `recordId`,
|
||||
which was generated when sending the reset mail,
|
||||
and `password` with the new password value.
|
||||
|
||||
### controls.oidc
|
||||
|
||||
These controls are related to completing OIDC interactions.
|
||||
|
||||
#### controls.oidc.cancel
|
||||
|
||||
Sending a POST request to this API will cancel the OIDC interaction
|
||||
and return the user to the client that started the interaction.
|
||||
|
||||
#### controls.oidc.prompt
|
||||
|
||||
This API is used to determine what the next necessary step is in the OIDC interaction.
|
||||
The response will contain a `location` field,
|
||||
containing the URL to the next page the user should go to,
|
||||
and a `prompt` field,
|
||||
indicating the next step that is necessary to progress the OIDC interaction.
|
||||
The three possible prompts are the following:
|
||||
|
||||
* **account**: The user needs to log in, so they have an account cookie.
|
||||
* **login**: The user needs to pick the WebID they want to use in the resulting OIDC token.
|
||||
* **consent**: The user needs to consent to the interaction.
|
||||
|
||||
#### controls.oidc.webId
|
||||
|
||||
Relevant for solving the **login** prompt.
|
||||
GET request will return a list of WebIDs the user can choose from.
|
||||
This is the same result as requesting the account information and looking at the linked WebIDs.
|
||||
The POST requests expects a `webId` value and optionally a `remember` boolean.
|
||||
The latter determines if the server should remember the picked WebID for later interactions.
|
||||
|
||||
#### controls.oidc.forgetWebId
|
||||
|
||||
POST requests to this API will cause the OIDC interaction to forget the picked WebID
|
||||
so a new one can be picked by the user.
|
||||
|
||||
#### controls.oidc.consent
|
||||
|
||||
A GET request to this API will return all the relevant information about the client doing the request.
|
||||
A POST requests causes the OIDC interaction to finish.
|
||||
It can have an optional `remember` value, which allows for refresh tokens if it is set to true.
|
||||
|
||||
#### controls.html
|
||||
|
||||
All these controls link to HTML pages and are thus mostly relevant to provide links to let the user navigate around.
|
||||
|
||||
## Example
|
||||
|
||||
Below is an example of a controls object in a response.
|
||||
|
||||
```json
|
||||
{
|
||||
"main": {
|
||||
"index": "http://localhost:3000/.account/",
|
||||
"logins": "http://localhost:3000/.account/login/"
|
||||
},
|
||||
"account": {
|
||||
"create": "http://localhost:3000/.account/account/",
|
||||
"logout": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/logout/",
|
||||
"webId": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/webid/",
|
||||
"pod": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/pod/",
|
||||
"clientCredentials": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/client-credentials/",
|
||||
"account": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/"
|
||||
},
|
||||
"password": {
|
||||
"create": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/login/password/",
|
||||
"login": "http://localhost:3000/.account/login/password/",
|
||||
"forgot": "http://localhost:3000/.account/login/password/forgot/",
|
||||
"reset": "http://localhost:3000/.account/login/password/reset/"
|
||||
},
|
||||
"oidc": {
|
||||
"cancel": "http://localhost:3000/.account/oidc/cancel/",
|
||||
"prompt": "http://localhost:3000/.account/oidc/prompt/",
|
||||
"webId": "http://localhost:3000/.account/oidc/pick-webid/",
|
||||
"forgetWebId": "http://localhost:3000/.account/oidc/forget-webid/",
|
||||
"consent": "http://localhost:3000/.account/oidc/consent/"
|
||||
},
|
||||
"html": {
|
||||
"main": {
|
||||
"login": "http://localhost:3000/.account/login/"
|
||||
},
|
||||
"account": {
|
||||
"createClientCredentials": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/client-credentials/",
|
||||
"createPod": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/pod/",
|
||||
"linkWebId": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/webid/",
|
||||
"account": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/"
|
||||
},
|
||||
"password": {
|
||||
"register": "http://localhost:3000/.account/login/password/register/",
|
||||
"login": "http://localhost:3000/.account/login/password/",
|
||||
"create": "http://localhost:3000/.account/account/ade5c046-e882-4b56-80f4-18cb16433360/login/password/",
|
||||
"forgot": "http://localhost:3000/.account/login/password/forgot/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
118
documentation/markdown/usage/account/login-method.md
Normal file
118
documentation/markdown/usage/account/login-method.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Adding a new login method
|
||||
|
||||
By default, the server allows users to use email/password combinations to identify as the owner of their account.
|
||||
But, just like with many other parts of the server,
|
||||
this can be extended so other login methods can be used.
|
||||
Here we'll cover everything that is necessary.
|
||||
|
||||
## Components
|
||||
|
||||
These are the components that are needed for adding a new login method.
|
||||
Not all of these are mandatory,
|
||||
but they can make the life of the user easier when trying to find and use the new method.
|
||||
Also have a look at the general [structure](../../architecture/features/accounts/routes.md)
|
||||
of new API components to see what is expected of such a component.
|
||||
|
||||
### Create component
|
||||
|
||||
There needs to be one or more components that allow a user
|
||||
to create an instance of the new login method and assign it to their account.
|
||||
The `CreatePasswordHandler` can be used as an example.
|
||||
This does not necessarily have to happen in a single request,
|
||||
potentially multiple requests can be used if the user has to perform actions on an external site for example.
|
||||
The only thing that matters is that at the end there is a new entry in the account's `logins` object.
|
||||
|
||||
When adding logins of your method a new key will need to be chosen to group these logins together.
|
||||
The email/password method uses `password` for example.
|
||||
|
||||
A new storage will probably need to be created to storage relevant metadata about this login method entry.
|
||||
Below is an example of how the `PasswordStore` is created:
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:PasswordStore",
|
||||
"@type": "BasePasswordStore",
|
||||
"storage": {
|
||||
"@id": "urn:solid-server:default:PasswordStorage",
|
||||
"@type": "EncodingPathStorage",
|
||||
"relativePath": "/accounts/logins/password/",
|
||||
"source": {
|
||||
"@id": "urn:solid-server:default:KeyValueStorage"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Login component
|
||||
|
||||
After creating a login instance, a user needs to be able to log in using the new method.
|
||||
This can again be done with multiple API calls if necessary,
|
||||
but the final one needs to be one that handles the necessary actions
|
||||
such as creating a cookie and finishing the OIDC interaction if necessary.
|
||||
The `ResolveLoginHandler` can be extended to take care of most of this,
|
||||
the `PasswordLoginHandler` provides an example of this.
|
||||
|
||||
### Additional components
|
||||
|
||||
Besides creating a login instance and logging in,
|
||||
it is always possible to offer additional functionality specific to this login method.
|
||||
The email/password method, for example, also has components for password recovery and updating a password.
|
||||
|
||||
### HTML pages
|
||||
|
||||
To make the life easier for users,
|
||||
at the very least you probably want to make an HTML page which people can use
|
||||
to create an instance of your login method.
|
||||
Besides that you could also make a page where people can combine creating an account with creating a login instance.
|
||||
The `templates/identity` folder contains all the pages the server has by default,
|
||||
which can be used as inspiration.
|
||||
|
||||
These pages need to be linked to the `urn:solid-server:default:HtmlViewHandler`.
|
||||
Below is an example of this:
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:HtmlViewHandler",
|
||||
"@type": "HtmlViewHandler",
|
||||
"templates": [{
|
||||
"@id": "urn:solid-server:default:CreatePasswordHtml",
|
||||
"@type": "HtmlViewEntry",
|
||||
"filePath": "@css:templates/identity/password/create.html.ejs",
|
||||
"route": {
|
||||
"@id": "urn:solid-server:default:AccountPasswordRoute"
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Updating the login handler
|
||||
|
||||
The `urn:solid-server:default:LoginHandler` returns a list of available login methods,
|
||||
which are used to offer users a choice of which login method they want to use on the default login page.
|
||||
If you want the new method to also be offered you will have to add similar Components.js configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"@id": "urn:solid-server:default:LoginHandler",
|
||||
"@type": "ControlHandler",
|
||||
"controls": [
|
||||
{
|
||||
"ControlHandler:_controls_key": "Email/password combination",
|
||||
"ControlHandler:_controls_value": {
|
||||
"@id": "urn:solid-server:default:LoginPasswordRoute"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Controls
|
||||
|
||||
All new relevant API endpoints should be added to the controls object,
|
||||
otherwise there is no way for users to find out where to send their requests.
|
||||
Similarly, links to the HTML pages should also be in the controls, so they can be navigated to.
|
||||
Examples of how to do this can be found [here](../../architecture/features/accounts/routes.md).
|
||||
|
||||
The default account overview page makes some assumptions about the controls when building the page.
|
||||
Specifically, it checks if `controls.html.<LOGIN_METHOD>.create` exists,
|
||||
if yes, it automatically creates a link on the page so users can create new login instances for their account.
|
||||
60
documentation/markdown/usage/account/migration.md
Normal file
60
documentation/markdown/usage/account/migration.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Migrating account data from v6 to v7
|
||||
|
||||
Below is a description of the changes that are necessary to migration account data from v6 to v7 of the server.
|
||||
Note that the resource identifier values are bas64 encoded before being appended to the storage location.
|
||||
|
||||
* "Forgot password" records
|
||||
* **Storage location**
|
||||
* Old: `.internal/forgot-password/`
|
||||
* New: `.internal/accounts/login/password/forgot/`
|
||||
* **Resource identifiers**
|
||||
* Old: `"forgot-password-resource-identifier/" + recordId`
|
||||
* New: `recordId`
|
||||
* **Data format**
|
||||
* Old: `{ recordId, email }`
|
||||
* New: `email`
|
||||
* **Notes**
|
||||
* Just deleting all existing records is an acceptable solution as these do not contain important information.
|
||||
* Client credentials tokens
|
||||
* **Storage location**
|
||||
* Old: `.internal/accounts/credentials/`
|
||||
* New: `.internal/accounts/client-credentials/`
|
||||
* **Resource identifiers**
|
||||
* No change
|
||||
* **Data format**
|
||||
* Old: `{ webId, secret }`
|
||||
* New: `{ accountId, webId, secret }`
|
||||
* **Notes**
|
||||
* Account IDs will need to be generated first before these can be transferred.
|
||||
* Account and password data
|
||||
* **Storage location**
|
||||
* Old: `.internal/accounts/`
|
||||
* New: Split up over the following:
|
||||
* `.internal/accounts/data/`
|
||||
* `.internal/accounts/webIds/`
|
||||
* `.internal/accounts/logins/password/`
|
||||
* **Resource identifiers**
|
||||
* Old: `"account/" + encodeURIComponent(email)` or `webId`
|
||||
* New:
|
||||
* `.internal/accounts/data/`: Newly generated account ID.
|
||||
* `.internal/accounts/webIds/`: `webID`
|
||||
* `.internal/accounts/logins/password/`: `encodeURIComponent(email.toLowerCase())`
|
||||
* **Data format**
|
||||
* Old: `{ webId, email, password, verified }` or `{ useIdp, podBaseUrl?, clientCredentials? }`
|
||||
* New:
|
||||
* `.internal/accounts/data/`: `{ id, logins: { password }, pods, webIds, clientCredentials }`
|
||||
* `.internal/accounts/webIds/`: `accountId[]`
|
||||
* `.internal/accounts/logins/password/`: `{ accountId, password, verified }`
|
||||
* **Notes**
|
||||
* First account IDs need to be generated,
|
||||
then login/pod/webId/clientCredentials resources need to be generated,
|
||||
and then the account needs to be updated with those resources.
|
||||
* Resource URLs are generated as follows:
|
||||
* Passwords: `<baseUrl>/.account/account/<accountID>/login/password/<encodeURIComponent(email.toLowerCase())>`
|
||||
* Pods: `<baseUrl>/.account/account/<accountID>/pod/<sha256Hash(podBaseUrl)>`
|
||||
* WebIds: `<baseUrl>/.account/account/<accountID>/webid/<sha256Hash(webId)>`
|
||||
* Client Credentials: `<baseUrl>/.account/account/<accountID>/client-credentials/<token>`
|
||||
* The above URLs are the values in all the account objects,
|
||||
the keys are the corresponding (lowercase) email, pod base URL, webID, and token name.
|
||||
* Only WebIDs where `useIdp` is `true` need to be linked to the account.
|
||||
* In the previous version, a WebID will be linked to exactly 1 account.
|
||||
@@ -8,48 +8,18 @@ It is recommended to use the latest version
|
||||
of the [Solid authentication client](https://github.com/inrupt/solid-client-authn-js)
|
||||
to interact with the server.
|
||||
|
||||
The links here assume the server is hosted at `http://localhost:3000/`.
|
||||
It also provides account management options for creating pods and WebIDs to be used during authentication,
|
||||
which are discussed more in-depth below.
|
||||
The links on this page assume the server is hosted at `http://localhost:3000/`.
|
||||
|
||||
## Registering an account
|
||||
|
||||
To register an account, you can go to `http://localhost:3000/idp/register/` if this feature is enabled,
|
||||
which it is on most configurations we provide.
|
||||
Currently our registration page ties 3 features together on the same page:
|
||||
|
||||
* Creating an account on the server.
|
||||
* Creating or linking a WebID to your account.
|
||||
* Creating a pod on the server.
|
||||
|
||||
### Account
|
||||
|
||||
To create an account you need to provide an email address and password.
|
||||
To register an account, you can go to `http://localhost:3000/.account/password/register/`, if this feature is enabled.
|
||||
There you can create an account with the email/password login method.
|
||||
The password will be salted and hashed before being stored.
|
||||
As of now, the account is only used to log in and identify yourself to the IDP
|
||||
when you want to do an authenticated request,
|
||||
but in future the plan is to also use this for account/pod management.
|
||||
Afterwards you will be redirected to the account page where you can create pods and link WebIDs to your account.
|
||||
|
||||
### WebID
|
||||
|
||||
We require each account to have a corresponding WebID.
|
||||
You can either let the server create a WebID for you in a pod,
|
||||
which will also need to be created then,
|
||||
or you can link an already existing WebID you have on an external server.
|
||||
|
||||
In case you try to link your own WebID, you can choose if you want to be able
|
||||
to use this server as your IDP for this WebID.
|
||||
If not, you can still create a pod,
|
||||
but you will not be able to direct the authentication client to this server to identify yourself.
|
||||
|
||||
Additionally, if you try to register with an external WebID,
|
||||
the first attempt will return an error indicating you need to add an identification triple to your WebID.
|
||||
After doing that you can try to register again.
|
||||
This is how we verify you are the owner of that WebID.
|
||||
After registration the next page will inform you
|
||||
that you have to add an additional triple to your WebID if you want to use the server as your IDP.
|
||||
|
||||
All of the above is automated if you create the WebID on the server itself.
|
||||
|
||||
### Pod
|
||||
### Creating a pod
|
||||
|
||||
To create a pod you simply have to fill in the name you want your pod to have.
|
||||
This will then be used to generate the full URL of your pod.
|
||||
@@ -57,23 +27,42 @@ For example, if you choose the name `test`,
|
||||
your pod would be located at `http://localhost:3000/test/`
|
||||
and your generated WebID would be `http://localhost:3000/test/profile/card#me`.
|
||||
|
||||
If you fill in a WebID when creating the pod,
|
||||
that WebID will be the one that has access to all data in the pod.
|
||||
If you don't, a WebID will be created in the pod and immediately linked to your account,
|
||||
allowing you to use it for authentication and accessing the data in that pod
|
||||
|
||||
The generated name also depends on the configuration you chose for your server.
|
||||
If you are using the subdomain feature,
|
||||
such as being done in the `config/memory-subdomains.json` configuration,
|
||||
the generated pod URL would be `http://test.localhost:3000/`.
|
||||
|
||||
### WebIDs
|
||||
|
||||
To use Solid authentication,
|
||||
you need to link at least one WebID to your account.
|
||||
This can happen automatically when creating a pod as mentioned above,
|
||||
or can be done manually with external WebIDs.
|
||||
|
||||
If you try to link an external WebID,
|
||||
the first attempt will return an error indicating you need to add an identification triple to your WebID.
|
||||
After doing that you can try to register again.
|
||||
This is how we verify you are the owner of that WebID.
|
||||
Afterwards the page will inform you
|
||||
that you have to add a triple to your WebID if you want to use the server as your IDP.
|
||||
|
||||
## Logging in
|
||||
|
||||
When using an authenticating client,
|
||||
you will be redirected to a login screen asking for your email and password.
|
||||
After that you will be redirected to a page showing some basic information about the client.
|
||||
There you need to consent that this client is allowed to identify using your WebID.
|
||||
After that you will be redirected to a page showing some basic information about the client
|
||||
where you can pick the WebID you want to use.
|
||||
There you need to consent that this client is allowed to identify using that WebID.
|
||||
As a result the server will send a token back to the client
|
||||
that contains all the information needed to use your WebID.
|
||||
|
||||
## Forgot password
|
||||
|
||||
If you forgot your password, you can recover it by going to `http://localhost:3000/idp/forgotpassword/`.
|
||||
If you forgot your password, you can recover it by going to `http://localhost:3000/.account/login/password/forgot/`.
|
||||
There you can enter your email address to get a recovery mail to reset your password.
|
||||
This feature only works if a mail server was configured,
|
||||
which by default is not the case.
|
||||
@@ -81,63 +70,11 @@ which by default is not the case.
|
||||
## JSON API
|
||||
|
||||
All of the above happens through HTML pages provided by the server.
|
||||
By default, the server uses the templates found in `/templates/identity/email-password/`
|
||||
By default, the server uses the templates found in `/templates/identity/`
|
||||
but different templates can be used through configuration.
|
||||
|
||||
These templates all make use of a JSON API exposed by the server.
|
||||
For example, when doing a GET request to `http://localhost:3000/idp/register/`
|
||||
with a JSON accept header, the following JSON is returned:
|
||||
|
||||
```json
|
||||
{
|
||||
"required": {
|
||||
"email": "string",
|
||||
"password": "string",
|
||||
"confirmPassword": "string",
|
||||
"createWebId": "boolean",
|
||||
"register": "boolean",
|
||||
"createPod": "boolean",
|
||||
"rootPod": "boolean"
|
||||
},
|
||||
"optional": {
|
||||
"webId": "string",
|
||||
"podName": "string",
|
||||
"template": "string"
|
||||
},
|
||||
"controls": {
|
||||
"register": "http://localhost:3000/idp/register/",
|
||||
"index": "http://localhost:3000/idp/",
|
||||
"prompt": "http://localhost:3000/idp/prompt/",
|
||||
"login": "http://localhost:3000/idp/login/",
|
||||
"forgotPassword": "http://localhost:3000/idp/forgotpassword/"
|
||||
},
|
||||
"apiVersion": "0.3"
|
||||
}
|
||||
```
|
||||
|
||||
The `required` and `optional` fields indicate which input fields are expected by the API.
|
||||
These correspond to the fields of the HTML registration page.
|
||||
To register a user, you can do a POST request with a JSON body containing the correct fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "test@example.com",
|
||||
"password": "secret",
|
||||
"confirmPassword": "secret",
|
||||
"createWebId": true,
|
||||
"register": true,
|
||||
"createPod": true,
|
||||
"rootPod": false,
|
||||
"podName": "test"
|
||||
}
|
||||
```
|
||||
|
||||
Two fields here that are not covered on the HTML page above are `rootPod` and `template`.
|
||||
`rootPod` tells the server to put the pod in the root of the server instead of a location based on the `podName`.
|
||||
By default the server will reject requests where this is `true`.
|
||||
`template` is only used by servers running the `config/dynamic.json` configuration,
|
||||
which is a very custom setup where every pod can have a different Components.js configuration,
|
||||
so this value can usually be ignored.
|
||||
A full description of this API can be found [here](account/json-api.md).
|
||||
|
||||
## IDP configuration
|
||||
|
||||
@@ -175,14 +112,31 @@ which you will need to copy over to your base configuration and then remove the
|
||||
There is only one option here. This import contains all the core components necessary to make the IDP work.
|
||||
In case you need to make some changes to core IDP settings, this is where you would have to look.
|
||||
|
||||
### interaction
|
||||
|
||||
Here you determine which features of account management are available.
|
||||
`default.json` allows everything, while `no-accounts.json` and `no-pods.json`
|
||||
disable account and pod creation respectively.
|
||||
Taking one of those latter options will disable the relevant JSON APIs and HTML pages.
|
||||
|
||||
### pod
|
||||
|
||||
The `pod` options determines how pods are created. `static.json` is the expected pod behaviour as described above.
|
||||
`dynamic.json` is an experimental feature that allows users
|
||||
to have a custom Components.js configuration for their own pod.
|
||||
When using such a setup, a JSON file will be written containing all the information of the user pods
|
||||
When using such a configuration, a JSON file will be written containing all the information of the user pods,
|
||||
so they can be recreated when the server restarts.
|
||||
|
||||
### registration
|
||||
## Adding a new login method to the server
|
||||
|
||||
This setting allows you to enable/disable registration on the server.
|
||||
Due to its modular nature,
|
||||
it is possible to add new login methods to the server,
|
||||
allowing users to log in different ways than just the standard email/password combination.
|
||||
More information on what is required can be found [here](account/login-method.md).
|
||||
|
||||
## Data migration
|
||||
|
||||
Going from v6 to v7 of the server, the account management is completely rewritten,
|
||||
including how account data is stored on the server.
|
||||
More information about how account data of an existing server can be migrated to the newer version
|
||||
can be found [here](account/migration.md).
|
||||
|
||||
@@ -1,39 +1,36 @@
|
||||
# How to seed Accounts and Pods
|
||||
|
||||
If you need to seed accounts and pods,
|
||||
the `--seededPodConfigJson` command line option can be used
|
||||
the `--seedConfig` command line option can be used
|
||||
with as value the path to a JSON file containing configurations for every required pod.
|
||||
The file needs to contain an array of JSON objects,
|
||||
with each object containing at least a `podName`, `email`, and `password` field.
|
||||
with each object containing at least an `email`, and `password` field.
|
||||
Multiple pod objects can also be assigned to such an object in the `pods` array to create pods for the account,
|
||||
with contents being the same as its corresponding JSON [API](account/json-api.md#controlsaccountpod).
|
||||
|
||||
For example:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"podName": "example",
|
||||
"email": "hello@example.com",
|
||||
"password": "abc123"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
You may optionally specify other parameters
|
||||
as described in the [Identity Provider documentation](identity-provider.md#json-api).
|
||||
|
||||
For example, to set up a pod without registering the generated WebID with the Identity Provider:
|
||||
|
||||
```json
|
||||
[
|
||||
},
|
||||
{
|
||||
"podName": "example",
|
||||
"email": "hello@example.com",
|
||||
"password": "abc123",
|
||||
"webId": "https://id.inrupt.com/example",
|
||||
"register": false
|
||||
"email": "hello2@example.com",
|
||||
"password": "123abc",
|
||||
"pods": [
|
||||
{ "name": "pod1" },
|
||||
{ "name": "pod2" }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
This feature cannot be used to register pods with pre-existing WebIDs,
|
||||
which requires an interactive validation step.
|
||||
which requires an interactive validation step,
|
||||
unless you disable the WebID ownership check in your server configuration.
|
||||
|
||||
Note that pod seeding is made for a default server setup with standard email/password login.
|
||||
If you [add a new login method](account/login-method.md)
|
||||
you will need to create a new implementation of pod seeding if you want to use it.
|
||||
|
||||
@@ -61,7 +61,7 @@ to some commonly used settings:
|
||||
| `--sparqlEndpoint, -s` | | URL of the SPARQL endpoint, when using a quadstore-based configuration. |
|
||||
| `--showStackTrace, -t` | false | Enables detailed logging on error output. |
|
||||
| `--podConfigJson` | `./pod-config.json` | Path to the file that keeps track of dynamic Pod configurations. Only relevant when using `@css:config/dynamic.json`. |
|
||||
| `--seededPodConfigJson` | | Path to the file that keeps track of seeded Pod configurations. |
|
||||
| `--seedConfig` | | Path to the file that keeps track of seeded account configurations. |
|
||||
| `--mainModulePath, -m` | | Path from where Components.js will start its lookup when initializing configurations. |
|
||||
| `--workers, -w` | `1` | Run in multithreaded mode using workers. Special values are `-1` (scale to `num_cores-1`), `0` (scale to `num_cores`) and 1 (singlethreaded). |
|
||||
|
||||
|
||||
@@ -80,10 +80,15 @@ nav:
|
||||
- Usage:
|
||||
- Example request: usage/example-requests.md
|
||||
- Metadata: usage/metadata.md
|
||||
- Identity provider: usage/identity-provider.md
|
||||
- Identity provider:
|
||||
- Overview: usage/identity-provider.md
|
||||
- JSON API: usage/account/json-api.md
|
||||
- New login method: usage/account/login-method.md
|
||||
- Data migration: usage/account/migration.md
|
||||
- Client credentials: usage/client-credentials.md
|
||||
- Seeding pods: usage/seeding-pods.md
|
||||
- Notifications: usage/notifications.md
|
||||
- Development server: usage/dev-configuration.md
|
||||
- Architecture:
|
||||
- Overview: architecture/overview.md
|
||||
- Dependency injection: architecture/dependency-injection.md
|
||||
@@ -97,6 +102,10 @@ nav:
|
||||
- Parsing: architecture/features/protocol/parsing.md
|
||||
- Authorization: architecture/features/protocol/authorization.md
|
||||
- Resource Store: architecture/features/protocol/resource-store.md
|
||||
- Account management:
|
||||
- Overview: architecture/features/accounts/overview.md
|
||||
- Controls: architecture/features/accounts/controls.md
|
||||
- Routes: architecture/features/accounts/routes.md
|
||||
- Notifications: architecture/features/notifications.md
|
||||
- Contributing:
|
||||
- Pull requests: contributing/making-changes.md
|
||||
|
||||
Reference in New Issue
Block a user