9.3 KiB
MSC3012: Post-registration terms of service API
MSC1692 introduces a concept of "terms of service" for new users to accept during registration, though does not deal with the problem of terms changing. Originally the MSC did handle such behaviour, however the proposed solution wasn't quite worked out well enough and thus has been broken out here for review. As of writing, server operators have been operating okay with MSC1692's core functionality without this MSC's proposed solution to terms changing after registration.
Proposal
See MSC1692 for details on the guiding principles and goals of this MSC - they are the same. MSC1692 also defines a lot of the structure which leads into this MSC.
Login
Given the Client-Server specification doesn't use UI auth on the login routes, mimicking the process used for registration is more difficult. The homeserver would not be aware of who is logging in prior to the user submitting their details (eg: token, password, etc) and therefore cannot provide exact details on which policies the user must accept as part of the initial login process. Instead, the login endpoints should be brought closer to how UI auth works using the following process:
- The client requests the login flows (unchanged behaviour) - policies are not referenced yet.
- The client submits the user's details (eg: password) to the homeserver (unchanged behaviour).
- If the credentials are valid, the homeserver checks if the user has any required policies to accept. If the user does not have any pending policies, skip to step 6.
- The homeserver responds with a 401 as per the UI auth requirements (an
errcode
ofM_FORBIDDEN
anderror
saying something like"Please accept the terms and conditions"
). Thecompleted
stages would have the user's already-accepted step listed (eg:m.login.password
). Them.login.terms
authentication type shares the same data structure as the one used during registration. - The client submits the request again as if it were following the UI auth requirements (ie: for
the purpose of
m.login.terms
, submitting an empty auth dict). - The homeserver logs the user in by generating a token and returning it to the client as per the specification.
This process is chosen to begin introducing the UI auth mechanism to login without breaking clients. In the future, the login endpoints should be migrated fully to use UI auth instead of a different approach. By nature of having the homeserver return a 401 when the user must accept terms before continuing, older clients will not be able to work around the limitation. Modern clients would be expected to support the UI auth flow (after the initial password submission), therefore allowing the user to actually log in.
Asking for acceptance after login/registration
The homeserver should maintain a read-only m.terms
account data event with content
similar
to:
{
"accepted": {
"terms_of_service": {
"version": "1.2",
"en": {
"name": "Terms of Service",
"url": "https://example.org/somewhere/terms-1.2-en.html"
},
"fr": {
"name": "Conditions d'utilisation",
"url": "https://example.org/somewhere/terms-1.2-fr.html"
}
},
"privacy_policy": {
"version": "1.2",
"en": {
"name": "Privacy Policy",
"url": "https://example.org/somewhere/privacy-1.2-en.html"
},
"fr": {
"name": "Politique de confidentialité",
"url": "https://example.org/somewhere/privacy-1.2-fr.html"
}
}
},
"pending": {
"terms_of_service": {
"version": "2.0",
"required": true,
"en": {
"name": "Terms of Service",
"url": "https://example.org/somewhere/terms-2.0-en.html"
},
"fr": {
"name": "Conditions d'utilisation",
"url": "https://example.org/somewhere/terms-2.0-fr.html"
}
},
"code_of_conduct": {
"version": "1.0",
"required": false,
"en": {
"name": "Code of Conduct",
"url": "https://example.org/somewhere/code-of-conduct-1.0-en.html"
},
"fr": {
"name": "Code de conduite",
"url": "https://example.org/somewhere/code-of-conduct-1.0-fr.html"
}
}
}
}
In this example, the user has already accepted the Terms of Service v1.2 and Privacy Policy v1.2,
but has not signed the new Terms of Service or accepted the Code of Conduct. This provides homeservers
with a way to communicate new policies as well as updates to policies. Because the name
is unique
amongst policies, the client can determine whether a policy is being updated or introduced. The
homeserver will push this account data event to the client as it would for any other account data
event.
Note: MSC2140 requires the client to treat URLs for policies returned by this API to be de-duplicated. The expectation is that clients will continue to use this terms of service API to manage approval of policies with their homeserver and consider duplicated policies from the identity server/integration manager where appropriate. Ultimately, the client may end up making several requests to all 3 servers despite only rendering a single checkbox for the user to click. This only applies to a post-MSC2140 world and does not otherwise affect this proposal.
The required
boolean indicates whether the homeserver is going to prevent use of the account (without
logging the user out) by responding with a 401 M_TERMS_NOT_SIGNED
error, including setting a soft_logout
flag as per MSC1466. Clients should not log
out the user if they understand the error code, instead opting to prompt the user to accept the new
policies required by the homeserver. The error will include an additional policies
property like in
the following example:
{
"errcode": "M_TERMS_NOT_SIGNED",
"error": "Please sign the terms of service",
"soft_logout": true,
"policies": {
"terms_of_service": {
"version": "2.0",
"en": {
"name": "Terms of Service",
"url": "https://example.org/somewhere/terms-2.0-en.html"
},
"fr": {
"name": "Conditions d'utilisation",
"url": "https://example.org/somewhere/terms-2.0-fr.html"
}
}
}
}
The homeserver should not prevent the use of /sync
(and similar endpoints) but may withhold any
event that is not an update to the m.terms
account data event. If a server is witholding events,
it MUST set withheld: true
as a top level field in the sync response. This is to ensure
that clients are not left in the dark when another client for the user accepts the terms of service,
or when the user accepts the terms elsewhere.
Note: withheld
was chosen over limited
due to limited
already having special meaning in
the context of sync.
In addition, the homeserver should not prevent the use of the special "terms acceptance" API described in the next section.
Terms acceptance API
One way to accept the terms of service is to force the user to log in again, however that is
likely to be less than desireable for most users. Instead, the client may make a request to
/_matrix/client/r0/terms
using the user-interactive authentication approach with a valid
access token. The homeserver may decide which additional stages it wants the user to complete
but must at least include the m.login.terms
stage in every flow. The authentication type's
behaviour is exactly the same as how it works for registering an account: the policies the
homeserver wants the user to accept are provided as the parameters for the stage, which should
include policies that are "required": false
in the user's account data. This is because the
same API is used by the client to acknowledge a non-blocking policy (such as the Code of Conduct
in the prior example).
Why use account data and not a special /sync property or polling API?
The user's account data was picked because (almost) every client is aware of the concept and requires few changes to support it. Many clients additionally have optimized the lookup for account data via caching, making the information essentially always available. If MSC1339 were to be approved, this lookup becomes even easier.
Clients are almost certainly going to want to show the information somewhere in their UI, sometimes due to a legal requirement. The information should therefore be easily accessible.
A special key in /sync
was not picked because it leads to clients having to support yet
another field on the sync response. It would also require clients that don't sync to sync,
which should not be the case (although currently they would until MSC1339 lands anyways).
A dedicated API for the client to poll against was also not picked due to similar reasons. The client shouldn't have to poll yet another API just for policies.