Skip to content

Commit

Permalink
Allow to configure user registration without email (#134)
Browse files Browse the repository at this point in the history
* Add support of lists in binded variables

* Allow to activate user registration without email

* Update user registration to handle optional email

* Update signup form and add templates to customize it

* Update info message on user registration

* Add users without email support to update profile

* Improve usability of account upgrading

* Handle anonymous users in reset password

* Update messages in testcases

* Add test case to ensure upgrading/downgrading of roles works
  • Loading branch information
lukfor authored Dec 11, 2023
1 parent 6af509a commit 6479c69
Show file tree
Hide file tree
Showing 15 changed files with 407 additions and 55 deletions.
38 changes: 33 additions & 5 deletions src/main/html/webapp/components/core/user/profile/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,37 @@ import templateNewTokenDialog from './dialogs/new.stache'
export default Control.extend({

"init": function(element, options) {

this.emailRequired = options.appState.attr('emailRequired');
$(element).hide();
User.findOne({
user: 'me'
}, function(user) {
$(element).html(template({
user: user
user: user,
anonymousAccount: (!options.appState.attr('emailRequired')),
emailProvided: (user.attr('mail') != "" && user.attr('mail') != undefined),
userEmailDescription: options.appState.attr('userEmailDescription'),
userWithoutEmailDescription: options.appState.attr('userWithoutEmailDescription')
}));
options.user = user;
$(element).fadeIn();
});
},

"#anonymous click" : function(){
if (!this.emailRequired){
var anonymousControl = $(this.element).find("[name='anonymous']");
var anonymous = !anonymousControl.is(':checked');
var mail = $(this.element).find("[name='mail']");
if (anonymous){
mail.attr('disabled','disabled');
} else {
mail.removeAttr('disabled');
}
mail.val("");
}
},

'submit': function(element, event) {
event.preventDefault();
var user = new User();
Expand All @@ -38,10 +56,20 @@ export default Control.extend({
var fullnameError = user.checkName(fullname.val());
this.updateControl(fullname, fullnameError);

var anonymous = false;
if (!this.emailRequired){
var anonymousControl = $(this.element).find("[name='anonymous']");
anonymous = !anonymousControl.is(':checked');
}

// mail
var mail = $(element).find("[name='mail']");
var mailError = user.checkMail(mail.val());
this.updateControl(mail, mailError);
if (!anonymous){
var mailError = user.checkMail(mail.val());
this.updateControl(mail, mailError);
} else {
this.updateControl(mail, undefined);
}

// password if password is not empty. else no password update on server side
var newPassword = $(element).find("[name='new-password']");
Expand Down Expand Up @@ -218,4 +246,4 @@ export default Control.extend({

}

});
});
34 changes: 28 additions & 6 deletions src/main/html/webapp/components/core/user/profile/profile.stache
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,34 @@ Please fill out the form below to change your account settings or your password.
<div class="invalid-feedback"></div>
</div>

{{#is(anonymousAccount, false)}}

<div class="form-group">
<label for="mail" class="control-label">E-Mail:</label>
<input id="mail" name="mail" type="text" value="{{user.mail}}" class="form-control col-sm-3">
<div class="invalid-feedback"></div>
</div>

{{else}}
<hr>
<div class="form-group">
<input type="checkbox" id="anonymous" name="anonymous" value="0" {{#emailProvided}}checked{{/emailProvided}}> <label for="anonymous" class="control-label">E-Mail Address</label>
<div class="form-group" style="margin-left: 30px;">
<p>
{{userEmailDescription}}
</p>
<label for="mail" class="control-label">E-Mail:</label>
<input id="mail" name="mail" type="text" class="form-control col-sm-3" autocomplete="off" {{#is(emailProvided, false)}}disabled{{/is}} value="{{user.mail}}">
<div class="invalid-feedback"></div>
<p><br>
{{userWithoutEmailDescription}}
</p>
</div>
</div>
<hr>

{{/is}}

<h4>Change password</h4>

<div class="form-group">
Expand All @@ -41,9 +63,9 @@ Please fill out the form below to change your account settings or your password.

</div>
</form>

<br>
<hr>

<br>
<h3>API Access</h3>

<p>This service provides a rich RestAPI to submit, monitor and download jobs.</p>
Expand All @@ -58,17 +80,17 @@ Please fill out the form below to change your account settings or your password.
<small class="{{#is(../user.apiTokenValid, false)}}text-danger{{#is}}">{{../user.apiTokenMessage}}</small>
{{else}}
<button class="btn btn-primary" id="create_token">Create API Token</button>
Expires in
Expires in
<select id="token_expiration">
<option value="30">30 days</option>
<option value="60">60 days</option>
</select>


{{/user.hasApiToken}}

<br>
<hr>

<br>
<h3>Delete Account</h3>

<p>Once you delete your user account, there is no going back. Please be certain.</p>
Expand All @@ -77,4 +99,4 @@ Please fill out the form below to change your account settings or your password.
<div class="controls">
<button class="btn btn-danger" id="delete_account">Delete Account</button>
</div>
</div>
</div>
44 changes: 41 additions & 3 deletions src/main/html/webapp/components/core/user/signup/signup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,51 @@ import template from './signup.stache';
export default Control.extend({

"init": function(element, options) {
this.emailRequired = options.appState.attr('emailRequired');
$(element).hide();
$(element).html(template());
$(element).html(template({
emailRequired: options.appState.attr('emailRequired'),
userEmailDescription: options.appState.attr('userEmailDescription'),
userWithoutEmailDescription: options.appState.attr('userWithoutEmailDescription')
}));
$(element).fadeIn();
},

"#anonymous1 click" : function(){
this.updateEmailControl();
},

"#anonymous2 click" : function(){
this.updateEmailControl();
},

"updateEmailControl": function() {
if (!this.emailRequired){
var anonymousControl = $(this.element).find("[name='anonymous']:checked");
var anonymous = (anonymousControl.val() == "1");
var mail = $(this.element).find("[name='mail']");
if (anonymous){
mail.attr('disabled','disabled');
} else {
mail.removeAttr('disabled');
}
}
},

'submit': function(element, event) {
event.preventDefault();

var that = this;
var user = new User();

// anonymous radiobutton
var anonymous = false;

if (!this.emailRequired){
var anonymousControl = $(element).find("[name='anonymous']:checked");
anonymous = (anonymousControl.val() == "1");
}

// username
var username = $(element).find("[name='username']");
var usernameError = user.checkUsername(username.val());
Expand All @@ -32,8 +66,12 @@ export default Control.extend({

// mail
var mail = $(element).find("[name='mail']");
var mailError = user.checkMail(mail.val());
this.updateControl(mail, mailError);
if (!anonymous){
var mailError = user.checkMail(mail.val());
this.updateControl(mail, mailError);
} else {
this.updateControl(mail, undefined);
}

// password
var newPassword = $(element).find("[name='new-password']");
Expand Down
29 changes: 28 additions & 1 deletion src/main/html/webapp/components/core/user/signup/signup.stache
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
<br>

<div class="alert alert-success" id="success-message" style="display: none;">
<b>Well done!</b> An email including the activation code has been sent to your address.
{{#is(emailRequired, true)}}
<b>Well done!</b> An email including the activation code has been sent to your address.
{{else}}
<b>Well done!</b> Your account is now active. <a href="/">Login now</a>.
{{/is}}
<br>
</div>

Expand All @@ -21,13 +25,36 @@
<div class="invalid-feedback"></div>
</div>

{{#is(emailRequired, true)}}

<div class="form-group">
<label for="mail" class="control-label">E-Mail:</label>
<input id="mail" name="mail" type="text" class="form-control col-sm-3" autocomplete="off">
<div class="invalid-feedback"></div>
</div>

{{else}}
<hr>
<div class="form-group">
<input type="radio" id="anonymous1" name="anonymous" value="0" checked> <label for="anonymous1" class="control-label">E-Mail Address</label>
<div class="form-group" style="margin-left: 30px;">
<p>
{{userEmailDescription}}
</p>
<label for="mail" class="control-label">E-Mail:</label>
<input id="mail" name="mail" type="text" class="form-control col-sm-3" autocomplete="off">
<div class="invalid-feedback"></div>
</div>
<input type="radio" id="anonymous2" name="anonymous" value="1"> <label for="anonymous2" class="control-label">I don't want to provide my email address</label>
<div class="form-group" style="margin-left: 30px;">
<p>
{{userWithoutEmailDescription}}
</p>
</div>
</div>
<hr>
{{/is}}

<div class="form-group">
<label for="new-password" class="control-label">Password:</label>
<input id="new-password" name="new-password" type="password" class="form-control col-sm-3" autocomplete="off">
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/cloudgene/mapred/api/v2/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public Representation getServer() {
data.put("background", getSettings().getColors().get("background"));
data.put("foreground", getSettings().getColors().get("foreground"));
data.put("footer", getWebApp().getTemplate(Template.FOOTER));
data.put("emailRequired", getSettings().isEmailRequired());
data.put("userEmailDescription", getWebApp().getTemplate(Template.USER_EMAIL_DESCRIPTION));
data.put("userWithoutEmailDescription", getWebApp().getTemplate(Template.USER_WITHOUT_EMAIL_DESCRIPTION));
if (user != null) {
JSONObject userJson = new JSONObject();
userJson.put("username", user.getUsername());
Expand Down
33 changes: 22 additions & 11 deletions src/main/java/cloudgene/mapred/api/v2/users/RegisterUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
import cloudgene.mapred.util.MailUtil;
import cloudgene.mapred.util.Template;

import java.util.Arrays;

public class RegisterUser extends BaseResource {
private static final Log log = LogFactory.getLog(RegisterUser.class);

public static final String DEFAULT_ROLE = "User";

public static final String DEFAULT_ANONYMOUS_ROLE = "Anonymous_User";

@Post
public Representation post(Representation entity) {

Expand All @@ -27,7 +31,7 @@ public Representation post(Representation entity) {
Form form = new Form(entity);
String username = form.getFirstValue("username");
String fullname = form.getFirstValue("full-name");
String mail = form.getFirstValue("mail").toString();
String mail = form.getFirstValue("mail");
String newPassword = form.getFirstValue("new-password");
String confirmNewPassword = form.getFirstValue("confirm-new-password");

Expand All @@ -41,15 +45,21 @@ public Representation post(Representation entity) {
return new JSONAnswer("Username already exists.", false);
}

// check email
error = User.checkMail(mail);
if (error != null) {
return new JSONAnswer(error, false);
}
if (dao.findByMail(mail) != null) {
return new JSONAnswer("E-Mail is already registered.", false);
boolean mailProvided = (mail != null && !mail.isEmpty());

if (getSettings().isEmailRequired() || mailProvided) {
// check email
error = User.checkMail(mail);
if (error != null) {
return new JSONAnswer(error, false);
}
if (dao.findByMail(mail) != null) {
return new JSONAnswer("E-Mail is already registered.", false);
}
}

String[] roles = new String[] { mailProvided ? DEFAULT_ROLE : DEFAULT_ANONYMOUS_ROLE};

// check password
error = User.checkPassword(newPassword, confirmNewPassword);
if (error != null) {
Expand All @@ -66,15 +76,15 @@ public Representation post(Representation entity) {
newUser.setUsername(username);
newUser.setFullName(fullname);
newUser.setMail(mail);
newUser.setRoles(new String[] { DEFAULT_ROLE });
newUser.setRoles(roles);
newUser.setPassword(HashUtil.hashPassword(newPassword));

try {

// if email server configured, send mails with activation link. Else
// activate user immediately.

if (getSettings().getMail() != null) {
if (getSettings().getMail() != null && mailProvided) {

String activationKey = HashUtil.getActivationHash(newUser);
newUser.setActive(false);
Expand All @@ -95,7 +105,8 @@ public Representation post(Representation entity) {

}

log.info(String.format("Registration: New user %s (ID %s - email %s)", newUser.getUsername(), newUser.getId(), newUser.getMail()));
log.info(String.format("Registration: New user %s (ID %s - email %s - roles %s)", newUser.getUsername(),
newUser.getId(), newUser.getMail(), Arrays.toString(newUser.getRoles())));
MailUtil.notifySlack(getSettings(), "Hi! say hello to " + username + " (" + mail + ") :hugging_face:");

dao.insert(newUser);
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/cloudgene/mapred/api/v2/users/ResetPassword.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public Representation get(Representation entity) {
Form form = new Form(entity);
String username = form.getFirstValue("username");

if (username == null || username.isEmpty()) {
if (username == null || username.trim().isEmpty()) {
return new JSONAnswer("Please enter a valid username or email address.", false);
}

Expand Down Expand Up @@ -66,13 +66,17 @@ public Representation get(Representation entity) {
String body = getWebApp().getTemplate(Template.RECOVERY_MAIL, user.getFullName(), application, link);

try {
log.info(String.format("Password reset link requested for user '%s'", username));
MailUtil.notifySlack(getSettings(), "Hi! " + username + " asked for a new password :key:");

MailUtil.send(getSettings(), user.getMail(), subject, body);
if (user.getMail()!= null && !user.getMail().isEmpty()) {
log.info(String.format("Password reset link requested for user '%s'", username));
MailUtil.notifySlack(getSettings(), "Hi! " + username + " asked for a new password :key:");
MailUtil.send(getSettings(), user.getMail(), subject, body);

return new JSONAnswer(
"Email sent to " + user.getMail() + " with instructions on how to reset your password.", true);
return new JSONAnswer(
"We sent you an email with instructions on how to reset your password.", true);
} else {
return new JSONAnswer("No email address is associated with the provided username. Therefore, password recovery cannot be completed.", false);
}

} catch (Exception e) {

Expand Down
Loading

0 comments on commit 6479c69

Please sign in to comment.