Skip to content

Commit 31082b7

Browse files
authored
feat: add terms of service to registration/login flow (#28)
See SwissDataScienceCenter/renku-ui#2954
1 parent 2aebe38 commit 31082b7

7 files changed

Lines changed: 216 additions & 5 deletions

File tree

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@
22

33
This project defines the keycloak theme shown on RenkuLab.
44

5-
# Production
5+
## Production
66

77
In production, the Docker image is mounted into container running Keycloak. See the keycloak section of the helm chart/values file: https://github.com/SwissDataScienceCenter/renku/blob/master/helm-chart/renku/values.yaml. Search for `theme` to find the relevant lines.
88

9-
# Development
9+
## Configuration
10+
11+
Keycloak can be configured to prompt a user to accept the **terms of service** on their first login. In Keycloack version 20, this can be done in the admin UI under the `Authentication` section for the realm.
12+
13+
Select the `Required Actions` tab, make sure `Terms and Conditions` is enabled and set it as default action as well.
14+
15+
For a Keycloak version different from 20, consult the admin guide, since the configuration may be different.
16+
17+
## Development
1018

1119
For development, there is a `Dockerfile.dev` that can be built and run locally to shorten the feedback loop.
1220

@@ -27,6 +35,28 @@ From here, you can click _Sign In_ to get to the login UI.
2735

2836
You can make changes to the theme and refresh in your browser to see the updates. You need to do a hard refresh (e.g., Shift-Refresh on Safari) or ensure the cache is disabled to see your changes.
2937

38+
### Configuration
39+
40+
Keycloak can be configured to prompt a user to accept the **terms of service** on their first login. This is off by default, but to enable it, modify the following section in `renku-realm.json`:
41+
42+
```
43+
{
44+
"alias": "terms_and_conditions",
45+
"name": "Terms and Conditions",
46+
"providerId": "terms_and_conditions",
47+
"enabled": false,
48+
"defaultAction": true,
49+
"priority": 20,
50+
"config": {}
51+
}
52+
```
53+
54+
and set `enabled` to `true`.
55+
56+
```
57+
"enabled": true,
58+
```
59+
3060
## Local Testing
3161

3262
After the initial theme is set up, you should make some configuration changes to test several scenarios:

renku-theme-dev/renku-realm.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,7 @@
13251325
"name": "Terms and Conditions",
13261326
"providerId": "terms_and_conditions",
13271327
"enabled": false,
1328-
"defaultAction": false,
1328+
"defaultAction": true,
13291329
"priority": 20,
13301330
"config": {}
13311331
},

renku_theme/login/login.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
33
<#if section = "header">
44
${msg("loginAccountTitle")}
5+
<div id="renku-login-terms-container">
6+
<div id="renku-login-terms-text">
7+
${kcSanitize(msg("termsText"))?no_esc}
8+
</div>
9+
</div>
510
<#elseif section = "socialProviders" >
611
<#if realm.password && social.providers??>
712
<div id="kc-social-providers" class="${properties.kcFormSocialAccountSectionClass!}">

renku_theme/login/messages/messages_en.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ loginFormLabel=Log in with your RenkuLab account
33

44
# Identity provider
55
identity-provider-login-label=Sign up or Log in with
6+
termsText=By continuing, you agree to the RenkuLab <a target="_blank" rel="noreferrer noopener" href="/help/tos">Terms of Service</a> and acknowledge that the <a target="_blank" rel="noreferrer noopener" href="/help/privacy">Privacy Policy</a> applies to you.

renku_theme/login/register.ftl

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<#import "template.ftl" as layout>
2+
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section>
3+
<#if section = "header">
4+
${msg("registerTitle")}
5+
<div id="renku-login-terms-container">
6+
<div id="renku-login-terms-text">
7+
${kcSanitize(msg("termsText"))?no_esc}
8+
</div>
9+
</div>
10+
<#elseif section = "form">
11+
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
12+
<div class="${properties.kcFormGroupClass!}">
13+
<div class="${properties.kcLabelWrapperClass!}">
14+
<label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
15+
</div>
16+
<div class="${properties.kcInputWrapperClass!}">
17+
<input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName"
18+
value="${(register.formData.firstName!'')}"
19+
aria-invalid="<#if messagesPerField.existsError('firstName')>true</#if>"
20+
/>
21+
22+
<#if messagesPerField.existsError('firstName')>
23+
<span id="input-error-firstname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
24+
${kcSanitize(messagesPerField.get('firstName'))?no_esc}
25+
</span>
26+
</#if>
27+
</div>
28+
</div>
29+
30+
<div class="${properties.kcFormGroupClass!}">
31+
<div class="${properties.kcLabelWrapperClass!}">
32+
<label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
33+
</div>
34+
<div class="${properties.kcInputWrapperClass!}">
35+
<input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName"
36+
value="${(register.formData.lastName!'')}"
37+
aria-invalid="<#if messagesPerField.existsError('lastName')>true</#if>"
38+
/>
39+
40+
<#if messagesPerField.existsError('lastName')>
41+
<span id="input-error-lastname" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
42+
${kcSanitize(messagesPerField.get('lastName'))?no_esc}
43+
</span>
44+
</#if>
45+
</div>
46+
</div>
47+
48+
<div class="${properties.kcFormGroupClass!}">
49+
<div class="${properties.kcLabelWrapperClass!}">
50+
<label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
51+
</div>
52+
<div class="${properties.kcInputWrapperClass!}">
53+
<input type="text" id="email" class="${properties.kcInputClass!}" name="email"
54+
value="${(register.formData.email!'')}" autocomplete="email"
55+
aria-invalid="<#if messagesPerField.existsError('email')>true</#if>"
56+
/>
57+
58+
<#if messagesPerField.existsError('email')>
59+
<span id="input-error-email" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
60+
${kcSanitize(messagesPerField.get('email'))?no_esc}
61+
</span>
62+
</#if>
63+
</div>
64+
</div>
65+
66+
<#if !realm.registrationEmailAsUsername>
67+
<div class="${properties.kcFormGroupClass!}">
68+
<div class="${properties.kcLabelWrapperClass!}">
69+
<label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
70+
</div>
71+
<div class="${properties.kcInputWrapperClass!}">
72+
<input type="text" id="username" class="${properties.kcInputClass!}" name="username"
73+
value="${(register.formData.username!'')}" autocomplete="username"
74+
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
75+
/>
76+
77+
<#if messagesPerField.existsError('username')>
78+
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
79+
${kcSanitize(messagesPerField.get('username'))?no_esc}
80+
</span>
81+
</#if>
82+
</div>
83+
</div>
84+
</#if>
85+
86+
<#if passwordRequired??>
87+
<div class="${properties.kcFormGroupClass!}">
88+
<div class="${properties.kcLabelWrapperClass!}">
89+
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
90+
</div>
91+
<div class="${properties.kcInputWrapperClass!}">
92+
<input type="password" id="password" class="${properties.kcInputClass!}" name="password"
93+
autocomplete="new-password"
94+
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
95+
/>
96+
97+
<#if messagesPerField.existsError('password')>
98+
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
99+
${kcSanitize(messagesPerField.get('password'))?no_esc}
100+
</span>
101+
</#if>
102+
</div>
103+
</div>
104+
105+
<div class="${properties.kcFormGroupClass!}">
106+
<div class="${properties.kcLabelWrapperClass!}">
107+
<label for="password-confirm"
108+
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
109+
</div>
110+
<div class="${properties.kcInputWrapperClass!}">
111+
<input type="password" id="password-confirm" class="${properties.kcInputClass!}"
112+
name="password-confirm"
113+
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
114+
/>
115+
116+
<#if messagesPerField.existsError('password-confirm')>
117+
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
118+
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
119+
</span>
120+
</#if>
121+
</div>
122+
</div>
123+
</#if>
124+
125+
<#if recaptchaRequired??>
126+
<div class="form-group">
127+
<div class="${properties.kcInputWrapperClass!}">
128+
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
129+
</div>
130+
</div>
131+
</#if>
132+
133+
<div class="${properties.kcFormGroupClass!}">
134+
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
135+
<div class="${properties.kcFormOptionsWrapperClass!}">
136+
<span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
137+
</div>
138+
</div>
139+
140+
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
141+
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
142+
</div>
143+
</div>
144+
</form>
145+
</#if>
146+
</@layout.registrationLayout>

renku_theme/login/resources/css/login.css

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ input[type="submit"] {
165165
}
166166

167167
input[type="submit"]:hover {
168-
background: #007A6C;
168+
background: #007a6c;
169169
color: #ffffff;
170170
}
171171

@@ -325,7 +325,7 @@ div#kc-social-providers a {
325325
}
326326

327327
div#kc-social-providers a:hover {
328-
background: #007A6C;
328+
background: #007a6c;
329329
color: #ffffff;
330330
}
331331

@@ -355,6 +355,18 @@ div#kc-social-providers a:active {
355355
background-color: #ffffff7d;
356356
}
357357

358+
div#renku-login-terms-container {
359+
display: flex;
360+
font-size: 12px;
361+
font-weight: normal;
362+
margin-top: 20px;
363+
justify-content: center;
364+
}
365+
366+
div#renku-login-terms-text {
367+
max-width: 400px;
368+
}
369+
358370
@media only screen and (max-width: 1080px) {
359371
#kc-content-wrapper {
360372
align-items: center;

renku_theme/login/terms.ftl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<#import "template.ftl" as layout>
2+
<@layout.registrationLayout displayMessage=false; section>
3+
<#if section = "header">
4+
${msg("termsTitle")}
5+
<#elseif section = "form">
6+
<div id="renku-terms-wrapper">
7+
<div id="kc-terms-text">
8+
${kcSanitize(msg("termsText"))?no_esc}
9+
</div>
10+
<form class="form-actions" action="${url.loginAction}" method="POST">
11+
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-decline" type="submit" value="${msg("doDecline")}"/>
12+
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="accept" id="kc-accept" type="submit" value="${msg("doAccept")}"/>
13+
</form>
14+
<div class="clearfix"></div>
15+
</div>
16+
</#if>
17+
</@layout.registrationLayout>

0 commit comments

Comments
 (0)