diff --git a/bubble-server/src/main/java/bubble/model/account/Account.java b/bubble-server/src/main/java/bubble/model/account/Account.java
index bcdf1124..e5cce58c 100644
--- a/bubble-server/src/main/java/bubble/model/account/Account.java
+++ b/bubble-server/src/main/java/bubble/model/account/Account.java
@@ -23,8 +23,6 @@ import org.cobbzilla.util.collection.ArrayUtil;
import org.cobbzilla.wizard.filters.auth.TokenPrincipal;
import org.cobbzilla.wizard.model.HashedPassword;
import org.cobbzilla.wizard.model.Identifiable;
-import org.cobbzilla.wizard.model.entityconfig.EntityFieldMode;
-import org.cobbzilla.wizard.model.entityconfig.EntityFieldType;
import org.cobbzilla.wizard.model.entityconfig.IdentifiableBaseParentEntity;
import org.cobbzilla.wizard.model.entityconfig.annotations.*;
import org.cobbzilla.wizard.model.search.SqlViewSearchResult;
@@ -52,6 +50,8 @@ import static org.cobbzilla.util.system.Sleep.sleep;
import static org.cobbzilla.util.time.TimeUtil.formatDuration;
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING;
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD;
+import static org.cobbzilla.wizard.model.entityconfig.EntityFieldMode.readOnly;
+import static org.cobbzilla.wizard.model.entityconfig.EntityFieldType.epoch_time;
import static org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKeySearchDepth.none;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
@@ -82,7 +82,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
public static final String[] UPDATE_FIELDS = {"url", "description", "autoUpdatePolicy"};
public static final String[] ADMIN_UPDATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "suspended", "admin");
- public static final String[] CREATE_FIELDS = ArrayUtil.append(ADMIN_UPDATE_FIELDS, "name", "referralCode");
+ public static final String[] CREATE_FIELDS = ArrayUtil.append(ADMIN_UPDATE_FIELDS, "name", "referralCode", "termsAgreed");
public static final String ROOT_USERNAME = "root";
public static final int NAME_MIN_LENGTH = 4;
@@ -115,7 +115,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
// make this updatable if we ever want accounts to be able to change parents
// there might be a lot more involved in that action though (read-only parent objects that will no longer be visible, must be copied in?)
- @ECForeignKey(entity=Account.class) @ECField(index=20, mode=EntityFieldMode.readOnly)
+ @ECForeignKey(entity=Account.class) @ECField(index=20, mode=readOnly)
@Column(length=UUID_MAXLEN, updatable=false)
@Getter @Setter private String parent;
public boolean hasParent () { return parent != null; }
@@ -148,15 +148,20 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
@Getter @Setter private Boolean locked = false;
public boolean locked () { return bool(locked); }
- @ECIndex @ECSearchable @ECField(index=90, type=EntityFieldType.epoch_time, mode=EntityFieldMode.readOnly)
+ @ECIndex @ECSearchable @ECField(index=90, type=epoch_time, mode=readOnly)
@Getter @Setter private Long deleted;
public boolean deleted () { return deleted != null; }
public Account setDeleted() { return setDeleted(now()); }
- @ECIndex @ECSearchable @ECField(index=100, type=EntityFieldType.epoch_time, mode=EntityFieldMode.readOnly)
+ @ECIndex @ECSearchable @ECField(index=100, type=epoch_time, mode=readOnly)
@Getter @Setter private Long lastLogin;
public Account setLastLogin() { return setLastLogin(now()); }
+ @ECIndex @ECSearchable @ECField(index=110, type=epoch_time, mode=readOnly)
+ @Column(nullable=false)
+ @Getter @Setter private Long termsAgreed;
+ public Account setTermsAgreed() { return setTermsAgreed(now()); }
+
@JsonIgnore @Embedded @Getter @Setter private HashedPassword hashedPassword;
public static final int MIN_PASSWORD_LENGTH = 8;
@@ -221,11 +226,13 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
setAdmin(true);
setDescription(request.hasDescription() ? request.getDescription() : "root user");
setLocale(getDEFAULT_LOCALE());
+ setTermsAgreed(now());
}
public Account(AccountRegistration request) {
setName(request.getName());
setHashedPassword(new HashedPassword(request.getPassword()));
+ setTermsAgreed(request.getTermsAgreed());
}
@Override public Identifiable update(Identifiable other) {
diff --git a/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java b/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java
index 94ccc098..640807c1 100644
--- a/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java
+++ b/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java
@@ -15,4 +15,7 @@ public class AccountRegistration extends Account {
@Getter @Setter private AccountContact contact;
public boolean hasContact () { return contact != null; }
+ @Getter @Setter private Boolean agreeToTerms = null;
+ public boolean agreeToTerms () { return agreeToTerms != null && agreeToTerms; }
+
}
diff --git a/bubble-server/src/main/java/bubble/resources/account/AccountsResource.java b/bubble-server/src/main/java/bubble/resources/account/AccountsResource.java
index e12a3b4e..6fb3a3b2 100644
--- a/bubble-server/src/main/java/bubble/resources/account/AccountsResource.java
+++ b/bubble-server/src/main/java/bubble/resources/account/AccountsResource.java
@@ -100,6 +100,11 @@ public class AccountsResource {
} else {
request.getContact().validate(errors);
}
+ if (!request.agreeToTerms()) {
+ errors.addViolation("err.terms.required", "You must agree to the legal terms to use this service");
+ } else {
+ request.setTermsAgreed();
+ }
if (errors.isInvalid()) return invalid(errors);
final String parentUuid;
diff --git a/bubble-server/src/main/java/bubble/resources/account/AuthResource.java b/bubble-server/src/main/java/bubble/resources/account/AuthResource.java
index 74ab571f..ec109ef8 100644
--- a/bubble-server/src/main/java/bubble/resources/account/AuthResource.java
+++ b/bubble-server/src/main/java/bubble/resources/account/AuthResource.java
@@ -206,6 +206,12 @@ public class AuthResource {
request.getContact().validate(errors);
}
+ if (!request.agreeToTerms()) {
+ errors.addViolation("err.terms.required", "You must agree to the legal terms to use this service");
+ } else {
+ request.setTermsAgreed();
+ }
+
String currency = null;
if (configuration.paymentsEnabled()) {
currency = currencyForLocale(request.getLocale(), getDEFAULT_LOCALE());
diff --git a/bubble-server/src/main/resources/META-INF/bubble/bubble.properties b/bubble-server/src/main/resources/META-INF/bubble/bubble.properties
index 939cdecd..e8423402 100644
--- a/bubble-server/src/main/resources/META-INF/bubble/bubble.properties
+++ b/bubble-server/src/main/resources/META-INF/bubble/bubble.properties
@@ -1 +1 @@
-bubble.version=0.7.2
+bubble.version=0.8.0
diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties
index a368f8a4..4a85034e 100644
--- a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties
+++ b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties
@@ -178,6 +178,7 @@ err.email.invalid=Email is invalid
err.phone.required=SMS Phone is required
err.phone.invalid=SMS Phone is invalid
err.phone.length=SMS Phone is too long
+err.terms.required=You must agree to the legal terms to use this service
err.country.invalid=Country is invalid
err.parent.notFound=Parent account does not exist
err.accountInit.timeout=Timeout initializing new account
@@ -241,6 +242,7 @@ field_label_contactType=Contact Type
field_label_email=Email
field_label_promoCode=Promo Code
field_label_sms=SMS Phone
+field_label_agreeToTerms=By checking this box, you agree to our Privacy Policy and Terms of Service.
field_label_receiveInformationalMessages=Receive informational messages about your Bubble
field_label_receivePromotionalMessages=Receive news about Bubble, including new releases and new features
button_label_login=Login
diff --git a/bubble-server/src/test/resources/models/include/new_account.json b/bubble-server/src/test/resources/models/include/new_account.json
index 23ca7491..ba736863 100644
--- a/bubble-server/src/test/resources/models/include/new_account.json
+++ b/bubble-server/src/test/resources/models/include/new_account.json
@@ -24,6 +24,7 @@
"name": "<>",
"password": "<>",
"admin": "<>",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "<>"}
}
},
diff --git a/bubble-server/src/test/resources/models/include/new_bubble.json b/bubble-server/src/test/resources/models/include/new_bubble.json
index 96902406..2065b76a 100644
--- a/bubble-server/src/test/resources/models/include/new_bubble.json
+++ b/bubble-server/src/test/resources/models/include/new_bubble.json
@@ -67,6 +67,7 @@
"entity": {
"name": "<>",
"password": "<>",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "<>"}
}
},
diff --git a/bubble-server/src/test/resources/models/include/referral_signup.json b/bubble-server/src/test/resources/models/include/referral_signup.json
index 9b758f0a..9e12cdce 100644
--- a/bubble-server/src/test/resources/models/include/referral_signup.json
+++ b/bubble-server/src/test/resources/models/include/referral_signup.json
@@ -21,6 +21,7 @@
"entity": {
"name": "<>",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "<>@example.com"},
"promoCode": "<>"
}
diff --git a/bubble-server/src/test/resources/models/tests/auth/account_registration.json b/bubble-server/src/test/resources/models/tests/auth/account_registration.json
index 01667d43..16752110 100644
--- a/bubble-server/src/test/resources/models/tests/auth/account_registration.json
+++ b/bubble-server/src/test/resources/models/tests/auth/account_registration.json
@@ -9,7 +9,7 @@
},
{
- "comment": "new session, register a user account",
+ "comment": "new session, register a user account, do not agree to terms, fails",
"request": {
"session": "new",
"uri": "auth/register",
@@ -19,6 +19,24 @@
"contact": { "type": "email", "info": "user-{{rand 5}}@example.com" }
}
},
+ "response": {
+ "status": 422,
+ "check": [ {"condition": "json.has('err.terms.required')"} ]
+ }
+ },
+
+ {
+ "comment": "new session, register a user account, agree to terms, succeeds",
+ "request": {
+ "session": "new",
+ "uri": "auth/register",
+ "entity": {
+ "name": "foobar1",
+ "password": "password1!",
+ "agreeToTerms": true,
+ "contact": { "type": "email", "info": "user-{{rand 5}}@example.com" }
+ }
+ },
"response": {
"sessionName": "user1session",
"session": "token"
@@ -141,7 +159,8 @@
"uri": "auth/register",
"entity": {
"name": "foobar1",
- "password": "password1!"
+ "password": "password1!",
+ "agreeToTerms": true
}
},
"response": {
@@ -184,7 +203,8 @@
"uri": "auth/register",
"entity": {
"name": "foobar1",
- "password": "password1!"
+ "password": "password1!",
+ "agreeToTerms": true
}
},
"response": {
@@ -201,6 +221,7 @@
"entity": {
"name": "foobar2",
"password": "password2!",
+ "agreeToTerms": true,
"contact": { "type": "email", "info": "user2-{{rand 5}}@example.com" }
}
},
diff --git a/bubble-server/src/test/resources/models/tests/live/backup_and_restore.json b/bubble-server/src/test/resources/models/tests/live/backup_and_restore.json
index 835b8d52..91e33d64 100644
--- a/bubble-server/src/test/resources/models/tests/live/backup_and_restore.json
+++ b/bubble-server/src/test/resources/models/tests/live/backup_and_restore.json
@@ -101,6 +101,7 @@
"entity": {
"name": "user1",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "user-{{rand 5}}@example.com"}
}
},
diff --git a/bubble-server/src/test/resources/models/tests/network/simple_network.json b/bubble-server/src/test/resources/models/tests/network/simple_network.json
index de5c779d..df622f92 100644
--- a/bubble-server/src/test/resources/models/tests/network/simple_network.json
+++ b/bubble-server/src/test/resources/models/tests/network/simple_network.json
@@ -15,6 +15,7 @@
"entity": {
"name": "test_user_simple_net",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
}
},
@@ -342,6 +343,7 @@
"entity": {
"name": "bubble_user",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "bubble-user@example.com"}
}
}
diff --git a/bubble-server/src/test/resources/models/tests/payment/pay_code.json b/bubble-server/src/test/resources/models/tests/payment/pay_code.json
index b0959551..f4b6495b 100644
--- a/bubble-server/src/test/resources/models/tests/payment/pay_code.json
+++ b/bubble-server/src/test/resources/models/tests/payment/pay_code.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_code",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
}
}
diff --git a/bubble-server/src/test/resources/models/tests/payment/pay_credit.json b/bubble-server/src/test/resources/models/tests/payment/pay_credit.json
index 6f345211..a291a849 100644
--- a/bubble-server/src/test/resources/models/tests/payment/pay_credit.json
+++ b/bubble-server/src/test/resources/models/tests/payment/pay_credit.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_credit",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
}
}
diff --git a/bubble-server/src/test/resources/models/tests/payment/pay_credit_refund_and_restart.json b/bubble-server/src/test/resources/models/tests/payment/pay_credit_refund_and_restart.json
index 9bbf53eb..a0b3bfd9 100644
--- a/bubble-server/src/test/resources/models/tests/payment/pay_credit_refund_and_restart.json
+++ b/bubble-server/src/test/resources/models/tests/payment/pay_credit_refund_and_restart.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_credit_refund_restart",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
}
}
diff --git a/bubble-server/src/test/resources/models/tests/payment/pay_free.json b/bubble-server/src/test/resources/models/tests/payment/pay_free.json
index 5ef241fa..9328bfb4 100644
--- a/bubble-server/src/test/resources/models/tests/payment/pay_free.json
+++ b/bubble-server/src/test/resources/models/tests/payment/pay_free.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_free",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
}
}
diff --git a/bubble-server/src/test/resources/models/tests/payment/plan_apps.json b/bubble-server/src/test/resources/models/tests/payment/plan_apps.json
index cf80c3eb..8f36ff5a 100644
--- a/bubble-server/src/test/resources/models/tests/payment/plan_apps.json
+++ b/bubble-server/src/test/resources/models/tests/payment/plan_apps.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_plan_apps_user",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
}
}
diff --git a/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json b/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json
index 28113d7e..7a2de13a 100644
--- a/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json
+++ b/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_recurring",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
}
}
diff --git a/bubble-server/src/test/resources/models/tests/promo/account_credit.json b/bubble-server/src/test/resources/models/tests/promo/account_credit.json
index 255dba75..6d17d51f 100644
--- a/bubble-server/src/test/resources/models/tests/promo/account_credit.json
+++ b/bubble-server/src/test/resources/models/tests/promo/account_credit.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_small_promo",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_small_promo@example.com"}
}
},
diff --git a/bubble-server/src/test/resources/models/tests/promo/first_month_free.json b/bubble-server/src/test/resources/models/tests/promo/first_month_free.json
index 39e78edb..31d7dd71 100644
--- a/bubble-server/src/test/resources/models/tests/promo/first_month_free.json
+++ b/bubble-server/src/test/resources/models/tests/promo/first_month_free.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_1mo_free",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_1mo_free@example.com"}
}
},
diff --git a/bubble-server/src/test/resources/models/tests/promo/multi_promo.json b/bubble-server/src/test/resources/models/tests/promo/multi_promo.json
index fef92cc2..86a2101a 100644
--- a/bubble-server/src/test/resources/models/tests/promo/multi_promo.json
+++ b/bubble-server/src/test/resources/models/tests/promo/multi_promo.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_referring_multi",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_referring_multi@example.com"}
}
},
diff --git a/bubble-server/src/test/resources/models/tests/promo/referral_month_free.json b/bubble-server/src/test/resources/models/tests/promo/referral_month_free.json
index 57d9b0cc..ae1cca19 100644
--- a/bubble-server/src/test/resources/models/tests/promo/referral_month_free.json
+++ b/bubble-server/src/test/resources/models/tests/promo/referral_month_free.json
@@ -7,6 +7,7 @@
"entity": {
"name": "test_user_referring_free",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_referring_free@example.com"}
}
}
@@ -103,6 +104,7 @@
"entity": {
"name": "test_user_referred_free",
"password": "password1!",
+ "agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_referred_free@example.com"},
"promoCode": "{{referralCodes.[0].name}}"
}
diff --git a/bubble-web b/bubble-web
index 3713e3ef..ace5fcc0 160000
--- a/bubble-web
+++ b/bubble-web
@@ -1 +1 @@
-Subproject commit 3713e3ef8cc63e1d311ec42ba84083afe4366cd8
+Subproject commit ace5fcc0889d4b89af2686c88f783050ff913aad